openssh_cert: add serial_number param (#54653)

* [openssh_cert] cleanup the returned certificate info

- Drop the certificate path - it is already present in rc.filename.
- Drop the leading whitespace for all lines.

Signed-off-by: Jakob Ackermann <das7pad@outlook.com>

* [openssh_cert] add support for a certificate serial number

Signed-off-by: Jakob Ackermann <das7pad@outlook.com>

* [openssh_cert] fix lint error

Signed-off-by: Jakob Ackermann <das7pad@outlook.com>

* [openssh_cert] drop explicit default value

Signed-off-by: Jakob Ackermann <das7pad@outlook.com>

* [openssh_cert] enforce the specified or missing serial number

Signed-off-by: Jakob Ackermann <das7pad@outlook.com>

* [openssh_cert] passing no explicit serial number ignores any present one

Signed-off-by: Jakob Ackermann <das7pad@outlook.com>
This commit is contained in:
Jakob Ackermann 2019-04-01 13:18:33 +02:00 committed by John R Barker
parent fa47bed71c
commit 21c8650180
2 changed files with 138 additions and 5 deletions

View file

@ -108,6 +108,14 @@ options:
description: description:
- Specify the key identity when signing a public key. The identifier that is logged by the server when the certificate is used for authentication. - Specify the key identity when signing a public key. The identifier that is logged by the server when the certificate is used for authentication.
type: str type: str
serial_number:
description:
- "Specify the certificate serial number.
The serial number is logged by the server when the certificate is used for authentication.
The certificate serial number may be used in a KeyRevocationList.
The serial number may be omitted for checks, but must be specified again for a new certificate.
Note: The default value set by ssh-keygen is 0."
type: int
extends_documentation_fragment: files extends_documentation_fragment: files
''' '''
@ -216,6 +224,7 @@ class Certificate(object):
self.public_key = module.params['public_key'] self.public_key = module.params['public_key']
self.path = module.params['path'] self.path = module.params['path']
self.identifier = module.params['identifier'] self.identifier = module.params['identifier']
self.serial_number = module.params['serial_number']
self.valid_from = module.params['valid_from'] self.valid_from = module.params['valid_from']
self.valid_to = module.params['valid_to'] self.valid_to = module.params['valid_to']
self.valid_at = module.params['valid_at'] self.valid_at = module.params['valid_at']
@ -290,6 +299,9 @@ class Certificate(object):
else: else:
args.extend(['-I', ""]) args.extend(['-I', ""])
if self.serial_number is not None:
args.extend(['-z', str(self.serial_number)])
if self.principals: if self.principals:
args.extend(['-n', ','.join(self.principals)]) args.extend(['-n', ','.join(self.principals)])
@ -377,6 +389,7 @@ class Certificate(object):
if principals == ["(none)"]: if principals == ["(none)"]:
principals = None principals = None
cert_type = re.findall("( user | host )", proc[1])[0].strip() cert_type = re.findall("( user | host )", proc[1])[0].strip()
serial_number = re.search(r"Serial: (\d+)", proc[1]).group(1)
validity = re.findall("(from (\\d{4}-\\d{2}-\\d{2}T\\d{2}(:\\d{2}){2}) to (\\d{4}-\\d{2}-\\d{2}T\\d{2}(:\\d{2}){2}))", proc[1]) validity = re.findall("(from (\\d{4}-\\d{2}-\\d{2}T\\d{2}(:\\d{2}){2}) to (\\d{4}-\\d{2}-\\d{2}T\\d{2}(:\\d{2}){2}))", proc[1])
if validity: if validity:
if validity[0][1]: if validity[0][1]:
@ -402,6 +415,11 @@ class Certificate(object):
file_args = module.load_file_common_arguments(module.params) file_args = module.load_file_common_arguments(module.params)
return not module.set_fs_attributes_if_different(file_args, False) return not module.set_fs_attributes_if_different(file_args, False)
def _check_serial_number():
if self.serial_number is None:
return True
return self.serial_number == int(serial_number)
def _check_type(): def _check_type():
return self.type == cert_type return self.type == cert_type
@ -441,10 +459,10 @@ class Certificate(object):
return False return False
if not perms_required: if perms_required and not _check_perms(module):
return _check_type() and _check_principals() and _check_validity(module) return False
return _check_perms(module) and _check_type() and _check_principals() and _check_validity(module) return _check_type() and _check_principals() and _check_validity(module) and _check_serial_number()
def dump(self): def dump(self):
@ -456,9 +474,12 @@ class Certificate(object):
for word in arr: for word in arr:
if word in keywords: if word in keywords:
concated.append(string) concated.append(string)
string = "" string = word
string += " " + word else:
string += " " + word
concated.append(string) concated.append(string)
# drop the certificate path
concated.pop(0)
return concated return concated
def format_cert_info(): def format_cert_info():
@ -512,6 +533,7 @@ def main():
public_key=dict(type='path'), public_key=dict(type='path'),
path=dict(type='path', required=True), path=dict(type='path', required=True),
identifier=dict(type='str'), identifier=dict(type='str'),
serial_number=dict(type='int'),
valid_from=dict(type='str'), valid_from=dict(type='str'),
valid_to=dict(type='str'), valid_to=dict(type='str'),
valid_at=dict(type='str'), valid_at=dict(type='str'),

View file

@ -239,6 +239,117 @@
- "clear" - "clear"
valid_from: "2001-01-21" valid_from: "2001-01-21"
valid_to: "2019-01-21" valid_to: "2019-01-21"
- name: Generate cert without serial
openssh_cert:
type: user
signing_key: '{{ output_dir }}/id_key'
public_key: '{{ output_dir }}/id_key.pub'
path: '{{ output_dir }}/id_cert_no_serial'
valid_from: always
valid_to: forever
register: rc_no_serial_number
- name: check default serial
assert:
that:
- "'Serial: 0' in rc_no_serial_number.info"
msg: OpenSSH user certificate contains the default serial number.
- name: Generate cert without serial (idempotent)
openssh_cert:
type: user
signing_key: '{{ output_dir }}/id_key'
public_key: '{{ output_dir }}/id_key.pub'
path: '{{ output_dir }}/id_cert_no_serial'
valid_from: always
valid_to: forever
register: rc_no_serial_number_idempotent
- name: check idempotent
assert:
that:
- rc_no_serial_number_idempotent is not changed
msg: OpenSSH certificate generation without serial number is idempotent.
- name: Generate cert with serial 42
openssh_cert:
type: user
signing_key: '{{ output_dir }}/id_key'
public_key: '{{ output_dir }}/id_key.pub'
path: '{{ output_dir }}/id_cert_serial_42'
valid_from: always
valid_to: forever
serial_number: 42
register: rc_serial_number
- name: check serial 42
assert:
that:
- "'Serial: 42' in rc_serial_number.info"
msg: OpenSSH user certificate contains the serial number from the params.
- name: Generate cert with serial 42 (idempotent)
openssh_cert:
type: user
signing_key: '{{ output_dir }}/id_key'
public_key: '{{ output_dir }}/id_key.pub'
path: '{{ output_dir }}/id_cert_serial_42'
valid_from: always
valid_to: forever
serial_number: 42
register: rc_serial_number_idempotent
- name: check idempotent
assert:
that:
- rc_serial_number_idempotent is not changed
msg: OpenSSH certificate generation with serial number is idempotent.
- name: Generate cert with changed serial number
openssh_cert:
type: user
signing_key: '{{ output_dir }}/id_key'
public_key: '{{ output_dir }}/id_key.pub'
path: '{{ output_dir }}/id_cert_serial_42'
valid_from: always
valid_to: forever
serial_number: 1337
register: rc_serial_number_changed
- name: check changed
assert:
that:
- rc_serial_number_changed is changed
msg: OpenSSH certificate regenerated upon serial number change.
- name: Generate cert with removed serial number
openssh_cert:
type: user
signing_key: '{{ output_dir }}/id_key'
public_key: '{{ output_dir }}/id_key.pub'
path: '{{ output_dir }}/id_cert_serial_42'
valid_from: always
valid_to: forever
serial_number: 0
register: rc_serial_number_removed
- name: check changed
assert:
that:
- rc_serial_number_removed is changed
msg: OpenSSH certificate regenerated upon serial number removal.
- name: Generate a new cert with serial number
openssh_cert:
type: user
signing_key: '{{ output_dir }}/id_key'
public_key: '{{ output_dir }}/id_key.pub'
path: '{{ output_dir }}/id_cert_serial_ignore'
valid_from: always
valid_to: forever
serial_number: 42
- name: Generate cert again, omitting the parameter serial_number (idempotent)
openssh_cert:
type: user
signing_key: '{{ output_dir }}/id_key'
public_key: '{{ output_dir }}/id_key.pub'
path: '{{ output_dir }}/id_cert_serial_ignore'
valid_from: always
valid_to: forever
register: rc_serial_number_ignored
- name: check idempotent
assert:
that:
- rc_serial_number_ignored is not changed
msg: OpenSSH certificate generation with omitted serial number is idempotent.
- name: Remove certificate (check mode) - name: Remove certificate (check mode)
openssh_cert: openssh_cert:
state: absent state: absent