From fe8f6478ba0fc8b400321887a6d45a43f91e3635 Mon Sep 17 00:00:00 2001 From: Sergio Oliveira Campos Date: Mon, 1 Jun 2020 18:24:35 -0300 Subject: [PATCH] Added support to kerberos principal --- examples/principal.yml | 13 +++ library/certificate_request.py | 13 +++ module_utils/certificate/providers/base.py | 92 ++++++++++++++++++- .../certificate/providers/certmonger.py | 7 ++ pylint_extra_requirements.txt | 1 + tasks/main.yml | 1 + tests/tasks/assert_certificate_parameters.yml | 3 +- vars/Debian.yml | 1 + vars/RedHat-7.yml | 1 + vars/default.yml | 1 + 10 files changed, 128 insertions(+), 5 deletions(-) create mode 100644 examples/principal.yml diff --git a/examples/principal.yml b/examples/principal.yml new file mode 100644 index 0000000..25363b2 --- /dev/null +++ b/examples/principal.yml @@ -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 diff --git a/library/certificate_request.py b/library/certificate_request.py index 65a0413..f123888 100644 --- a/library/certificate_request.py +++ b/library/certificate_request.py @@ -45,6 +45,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) """ @@ -65,6 +69,14 @@ - www.example.com - example.com 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 = "" @@ -100,6 +112,7 @@ def _get_argument_spec(): ca=dict(type="str", required=True), directory=dict(type="str", default="/etc/pki/tls"), provider=dict(type="str", default="certmonger"), + principal=dict(type="list"), ) @property diff --git a/module_utils/certificate/providers/base.py b/module_utils/certificate/providers/base.py index 120bb4d..f778be5 100644 --- a/module_utils/certificate/providers/base.py +++ b/module_utils/certificate/providers/base.py @@ -8,12 +8,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. @@ -36,7 +103,7 @@ def load_from_params(cls, module, params): # pylint: disable=protected-access cert_like = cls(module) - map_attrs = ["dns", "ip"] + map_attrs = ["dns", "ip", "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) @@ -100,7 +167,7 @@ def _get_info_from_x509(self, x509_obj): info["dns"] = self._get_san_values(x509.DNSName) info["ip"] = self._get_san_values(x509.IPAddress) info["common_name"] = self._get_subject_values(NameOID.COMMON_NAME) - + info["principal"] = self._get_san_values(x509.OtherName, KRB5PrincipalName) return info @property @@ -118,16 +185,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): diff --git a/module_utils/certificate/providers/certmonger.py b/module_utils/certificate/providers/certmonger.py index a0ec009..d2cf8fc 100644 --- a/module_utils/certificate/providers/certmonger.py +++ b/module_utils/certificate/providers/certmonger.py @@ -110,6 +110,7 @@ def exists_in_certmonger(self): def request_certificate(self): """Issue or update a certificate using certmonger.""" + # pylint: disable=useless-else-on-loop getcert_bin = self.module.get_bin_path("getcert", required=True) command = [getcert_bin] @@ -141,5 +142,11 @@ def request_certificate(self): # Don't attempt to renew when near to expiration command += ["-R"] + # 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 diff --git a/pylint_extra_requirements.txt b/pylint_extra_requirements.txt index e795665..675139d 100644 --- a/pylint_extra_requirements.txt +++ b/pylint_extra_requirements.txt @@ -1,3 +1,4 @@ # SPDX-License-Identifier: MIT dbus-python +pyasn1 diff --git a/tasks/main.yml b/tasks/main.yml index 695510c..f5187b3 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -55,5 +55,6 @@ dns: "{{ item.dns | default(omit) }}" ca: "{{ item.ca | default(omit) }}" provider: "{{ item.provider | default(omit) }}" + principal: "{{ item.principal | default(omit) }}" directory: "{{ __certificate_default_directory }}" loop: "{{ certificate_requests }}" diff --git a/tests/tasks/assert_certificate_parameters.yml b/tests/tasks/assert_certificate_parameters.yml index 79525de..d83caad 100644 --- a/tests/tasks/assert_certificate_parameters.yml +++ b/tests/tasks/assert_certificate_parameters.yml @@ -41,7 +41,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 diff --git a/vars/Debian.yml b/vars/Debian.yml index 2e00a15..63d2ed6 100644 --- a/vars/Debian.yml +++ b/vars/Debian.yml @@ -3,6 +3,7 @@ # Put internal variables here with Debian 10 specific values. __certificate_packages: + - python-pyasn1 - python-cryptography - python-dbus diff --git a/vars/RedHat-7.yml b/vars/RedHat-7.yml index e06a5c0..d7ad423 100644 --- a/vars/RedHat-7.yml +++ b/vars/RedHat-7.yml @@ -3,6 +3,7 @@ # Put internal variables here with Red Hat Enterprise Linux 7 specific values. __certificate_packages: + - python-pyasn1 - python-cryptography - python-dbus diff --git a/vars/default.yml b/vars/default.yml index 5ad116c..9fb8881 100644 --- a/vars/default.yml +++ b/vars/default.yml @@ -3,6 +3,7 @@ # Put internal variables here with values for unrecognized distributions. __certificate_packages: + - python3-pyasn1 - python3-cryptography - python3-dbus