Skip to content

Commit

Permalink
Merge pull request #9 from seocam/principal
Browse files Browse the repository at this point in the history
Support to Kerberos principal
  • Loading branch information
seocam committed Jun 3, 2020
2 parents 248b554 + f681a64 commit 045203b
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 5 deletions.
13 changes: 13 additions & 0 deletions examples/principal.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
- hosts: all
become: true

vars:
certificate_requests:
- name: mycert
dns: www.example.com
principal: HTTP/www.example.com@EXAMPLE.com
ca: self-sign

roles:
- linux-system-roles.certificate
13 changes: 13 additions & 0 deletions library/certificate_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@
if I(name) is not an absolute path.
required: false
default: /etc/pki/tls
principal:
description:
- Kerberos principal.
required: false
author:
- Sergio Oliveira Campos (@seocam)
"""
Expand Down Expand Up @@ -130,6 +134,14 @@
owner: ftp
group: ftp
ca: self-sign
# Certificate with Kerberos principal
- name: Ensure certificate exists with principal
certificate_request:
name: single-example
dns: www.example.com
principal: HTTP/www.example.com@EXAMPLE.com
ca: self-sign
"""

RETURN = ""
Expand Down Expand Up @@ -171,6 +183,7 @@ def _get_argument_spec():
key_size=dict(type="int", default=2048),
owner=dict(type="str"),
group=dict(type="str"),
principal=dict(type="list"),
)

@property
Expand Down
92 changes: 88 additions & 4 deletions module_utils/certificate/providers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,79 @@
from cryptography.hazmat.backends import default_backend
from cryptography.x509.oid import NameOID

from pyasn1.codec.der import decoder
from pyasn1.type import char, namedtype, tag, univ

from ansible.module_utils import six

if six.PY2:
FileNotFoundError = IOError # pylint: disable=redefined-builtin


class _PrincipalName(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType(
"name-type",
univ.Integer().subtype(
explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)
),
),
namedtype.NamedType(
"name-string",
univ.SequenceOf(char.GeneralString()).subtype(
explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)
),
),
)


class _KRB5PrincipalName(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType(
"realm",
char.GeneralString().subtype(
explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)
),
),
namedtype.NamedType(
"principalName",
_PrincipalName().subtype(
explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)
),
),
)


class KRB5PrincipalName(x509.OtherName):
"""Kerberos Principal x509 OtherName implementation."""

# pylint: disable=too-few-public-methods

oid = "1.3.6.1.5.2.2"

def __init__(self, type_id, value):
super(KRB5PrincipalName, self).__init__(type_id, value)
self.name = self._decode_krb5principalname(value)

@staticmethod
def _decode_krb5principalname(data):
# pylint: disable=unsubscriptable-object
principal = decoder.decode(data, asn1Spec=_KRB5PrincipalName())[0]
realm = six.ensure_text(
str(principal["realm"]).replace("\\", "\\\\").replace("@", "\\@")
)
name = principal["principalName"]["name-string"]
name = u"/".join(
six.ensure_text(str(n))
.replace("\\", "\\\\")
.replace("/", "\\/")
.replace("@", "\\@")
for n in name
)
name = u"%s@%s" % (name, realm)
return name


class CertificateProxy:
"""Proxy class that represents certificate-like objects.
Expand All @@ -37,7 +104,7 @@ def load_from_params(cls, module, params):
# pylint: disable=protected-access
cert_like = cls(module)

map_attrs = ["dns", "ip", "email"]
map_attrs = ["dns", "ip", "email", "principal"]
info = {k: v for k, v in params.items() if k in map_attrs}

info["common_name"] = cert_like._get_common_name_from_params(params)
Expand Down Expand Up @@ -111,7 +178,7 @@ def _get_info_from_x509(self, x509_obj):
info["ip"] = self._get_san_values(x509.IPAddress)
info["email"] = self._get_san_values(x509.RFC822Name)
info["common_name"] = self._get_subject_values(NameOID.COMMON_NAME)

info["principal"] = self._get_san_values(x509.OtherName, KRB5PrincipalName)
return info

@property
Expand All @@ -134,16 +201,33 @@ def common_name(self):
"""Return the certificate common_name."""
return self.cert_data.get("common_name")

@property
def principal(self):
"""Return the Kerberos principal."""
return self.cert_data.get("principal") or []

def _get_subject_values(self, oid):
values = self._x509_obj.subject.get_attributes_for_oid(oid)
if values:
return values[0].value
return None

def _get_san_values(self, san_type):
def _get_san_values(self, san_type, san_class=None):
if not self._subject_alternative_names:
return []
return self._subject_alternative_names.value.get_values_for_type(san_type)
san_values = self._subject_alternative_names.value.get_values_for_type(
san_type,
)
if san_values and san_class:
values = []
for obj in san_values:
if obj.type_id.dotted_string == san_class.oid:
name = san_class(obj.type_id, obj.value).name
if name not in values:
values.append(name)
san_values = values

return san_values

@property
def _subject_alternative_names(self):
Expand Down
7 changes: 7 additions & 0 deletions module_utils/certificate/providers/certmonger.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,5 +158,12 @@ def request_certificate(self):
command += ["-g", str(self.module.params.get("key_size"))]

self.module.debug("Certmonger command: {}".format(command))

# Set Kerberos principal
for principal in self.csr.principal:
command += ["-K", principal]
else:
command += ["-K", ""]

self._run_command(command, check_rc=True)
self.changed = True
1 change: 1 addition & 0 deletions pylint_extra_requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: MIT

dbus-python
pyasn1
1 change: 1 addition & 0 deletions tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
group: "{{ item.group | default(omit) }}"
ca: "{{ item.ca | default(omit) }}"
provider: "{{ item.provider | default(omit) }}"
principal: "{{ item.principal | default(omit) }}"
directory: "{{ __certificate_default_directory }}"
key_size: "{{ item.key_size | default(omit) }}"
loop: "{{ certificate_requests }}"
3 changes: 2 additions & 1 deletion tests/tasks/assert_certificate_parameters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@
grep 'Subject Alternative Name' -A1 |
tail -1 |
tr , '\n' |
sed 's/^\s\+//g'
sed 's/^\s\+//g' |
grep -v "othername"
register: result
changed_when: false

Expand Down
1 change: 1 addition & 0 deletions vars/Debian.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Put internal variables here with Debian 10 specific values.

__certificate_packages:
- python-pyasn1
- python-cryptography
- python-dbus

Expand Down
1 change: 1 addition & 0 deletions vars/RedHat-7.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Put internal variables here with Red Hat Enterprise Linux 7 specific values.

__certificate_packages:
- python-pyasn1
- python-cryptography
- python-dbus

Expand Down
1 change: 1 addition & 0 deletions vars/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Put internal variables here with values for unrecognized distributions.

__certificate_packages:
- python3-pyasn1
- python3-cryptography
- python3-dbus

Expand Down

0 comments on commit 045203b

Please sign in to comment.