Skip to content

Commit

Permalink
Added support to kerberos principal
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergio Oliveira Campos committed Jun 3, 2020
1 parent e66b1aa commit fe8f647
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/[email protected]
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 @@ -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)
"""
Expand All @@ -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/[email protected]
ca: self-sign
"""

RETURN = ""
Expand Down Expand Up @@ -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
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 @@ -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.
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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):
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 @@ -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]

Expand Down Expand Up @@ -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
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 @@ -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 }}"
3 changes: 2 additions & 1 deletion tests/tasks/assert_certificate_parameters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

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 fe8f647

Please sign in to comment.