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:
parent
fa47bed71c
commit
21c8650180
2 changed files with 138 additions and 5 deletions
|
@ -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'),
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue