ACME modules: simplify code, refactor argspec handling code, move csr/csr_content to own docs fragment (#750)
* Fix bug in argspec module util. * Move csr / csr_content to new docs fragment. * Simplify code. * Refactor ACME argspec creation. Add with_certificate argument for new CERTIFICATE docs fragment.pull/751/head
parent
f3c9cb7a8a
commit
aa82575a78
|
@ -284,4 +284,34 @@ notes:
|
||||||
or enabled with the O(select_crypto_backend) option. Note that using
|
or enabled with the O(select_crypto_backend) option. Note that using
|
||||||
the C(openssl) binary will be slower."
|
the C(openssl) binary will be slower."
|
||||||
options: {}
|
options: {}
|
||||||
|
'''
|
||||||
|
|
||||||
|
CERTIFICATE = r'''
|
||||||
|
options:
|
||||||
|
csr:
|
||||||
|
description:
|
||||||
|
- "File containing the CSR for the new certificate."
|
||||||
|
- "Can be created with M(community.crypto.openssl_csr)."
|
||||||
|
- "The CSR may contain multiple Subject Alternate Names, but each one
|
||||||
|
will lead to an individual challenge that must be fulfilled for the
|
||||||
|
CSR to be signed."
|
||||||
|
- "B(Note): the private key used to create the CSR B(must not) be the
|
||||||
|
account key. This is a bad idea from a security point of view, and
|
||||||
|
the CA should not accept the CSR. The ACME server should return an
|
||||||
|
error in this case."
|
||||||
|
- Precisely one of O(csr) or O(csr_content) must be specified.
|
||||||
|
type: path
|
||||||
|
csr_content:
|
||||||
|
description:
|
||||||
|
- "Content of the CSR for the new certificate."
|
||||||
|
- "Can be created with M(community.crypto.openssl_csr_pipe)."
|
||||||
|
- "The CSR may contain multiple Subject Alternate Names, but each one
|
||||||
|
will lead to an individual challenge that must be fulfilled for the
|
||||||
|
CSR to be signed."
|
||||||
|
- "B(Note): the private key used to create the CSR B(must not) be the
|
||||||
|
account key. This is a bad idea from a security point of view, and
|
||||||
|
the CA should not accept the CSR. The ACME server should return an
|
||||||
|
error in this case."
|
||||||
|
- Precisely one of O(csr) or O(csr_content) must be specified.
|
||||||
|
type: str
|
||||||
'''
|
'''
|
||||||
|
|
|
@ -420,45 +420,60 @@ class ACMEClient(object):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def get_default_argspec(with_account=True):
|
def get_default_argspec():
|
||||||
'''
|
'''
|
||||||
Provides default argument spec for the options documented in the acme doc fragment.
|
Provides default argument spec for the options documented in the acme doc fragment.
|
||||||
|
|
||||||
|
DEPRECATED: will be removed in community.crypto 3.0.0
|
||||||
'''
|
'''
|
||||||
argspec = dict(
|
return dict(
|
||||||
acme_directory=dict(type='str', required=True),
|
acme_directory=dict(type='str', required=True),
|
||||||
acme_version=dict(type='int', required=True, choices=[1, 2]),
|
acme_version=dict(type='int', required=True, choices=[1, 2]),
|
||||||
validate_certs=dict(type='bool', default=True),
|
validate_certs=dict(type='bool', default=True),
|
||||||
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'openssl', 'cryptography']),
|
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'openssl', 'cryptography']),
|
||||||
request_timeout=dict(type='int', default=10),
|
request_timeout=dict(type='int', default=10),
|
||||||
)
|
|
||||||
if with_account:
|
|
||||||
argspec.update(dict(
|
|
||||||
account_key_src=dict(type='path', aliases=['account_key']),
|
account_key_src=dict(type='path', aliases=['account_key']),
|
||||||
account_key_content=dict(type='str', no_log=True),
|
account_key_content=dict(type='str', no_log=True),
|
||||||
account_key_passphrase=dict(type='str', no_log=True),
|
account_key_passphrase=dict(type='str', no_log=True),
|
||||||
account_uri=dict(type='str'),
|
account_uri=dict(type='str'),
|
||||||
))
|
)
|
||||||
return argspec
|
|
||||||
|
|
||||||
|
|
||||||
def create_default_argspec(with_account=True, require_account_key=True):
|
def create_default_argspec(
|
||||||
|
with_account=True,
|
||||||
|
require_account_key=True,
|
||||||
|
with_certificate=False,
|
||||||
|
):
|
||||||
'''
|
'''
|
||||||
Provides default argument spec for the options documented in the acme doc fragment.
|
Provides default argument spec for the options documented in the acme doc fragment.
|
||||||
'''
|
'''
|
||||||
result = ArgumentSpec(
|
result = ArgumentSpec(
|
||||||
get_default_argspec(with_account=with_account),
|
argument_spec=dict(
|
||||||
|
acme_directory=dict(type='str', required=True),
|
||||||
|
acme_version=dict(type='int', required=True, choices=[1, 2]),
|
||||||
|
validate_certs=dict(type='bool', default=True),
|
||||||
|
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'openssl', 'cryptography']),
|
||||||
|
request_timeout=dict(type='int', default=10),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
if with_account:
|
if with_account:
|
||||||
|
result.update_argspec(
|
||||||
|
account_key_src=dict(type='path', aliases=['account_key']),
|
||||||
|
account_key_content=dict(type='str', no_log=True),
|
||||||
|
account_key_passphrase=dict(type='str', no_log=True),
|
||||||
|
account_uri=dict(type='str'),
|
||||||
|
)
|
||||||
if require_account_key:
|
if require_account_key:
|
||||||
result.update(
|
result.update(required_one_of=[['account_key_src', 'account_key_content']])
|
||||||
required_one_of=[
|
result.update(mutually_exclusive=[['account_key_src', 'account_key_content']])
|
||||||
['account_key_src', 'account_key_content'],
|
if with_certificate:
|
||||||
],
|
result.update_argspec(
|
||||||
|
csr=dict(type='path'),
|
||||||
|
csr_content=dict(type='str'),
|
||||||
)
|
)
|
||||||
result.update(
|
result.update(
|
||||||
mutually_exclusive=[
|
required_one_of=[['csr', 'csr_content']],
|
||||||
['account_key_src', 'account_key_content'],
|
mutually_exclusive=[['csr', 'csr_content']],
|
||||||
],
|
|
||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
|
@ -103,7 +103,7 @@ class Challenge(object):
|
||||||
# https://tools.ietf.org/html/rfc8555#section-8.4
|
# https://tools.ietf.org/html/rfc8555#section-8.4
|
||||||
resource = '_acme-challenge'
|
resource = '_acme-challenge'
|
||||||
value = nopad_b64(hashlib.sha256(to_bytes(key_authorization)).digest())
|
value = nopad_b64(hashlib.sha256(to_bytes(key_authorization)).digest())
|
||||||
record = (resource + identifier[1:]) if identifier.startswith('*.') else '{0}.{1}'.format(resource, identifier)
|
record = '{0}.{1}'.format(resource, identifier[2:] if identifier.startswith('*.') else identifier)
|
||||||
return {
|
return {
|
||||||
'resource': resource,
|
'resource': resource,
|
||||||
'resource_value': value,
|
'resource_value': value,
|
||||||
|
|
|
@ -47,7 +47,7 @@ class ArgumentSpec:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def merge(self, other):
|
def merge(self, other):
|
||||||
self.update_argspec(other.argument_spec)
|
self.update_argspec(**other.argument_spec)
|
||||||
self.update(
|
self.update(
|
||||||
mutually_exclusive=other.mutually_exclusive,
|
mutually_exclusive=other.mutually_exclusive,
|
||||||
required_together=other.required_together,
|
required_together=other.required_together,
|
||||||
|
|
|
@ -82,6 +82,7 @@ seealso:
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- community.crypto.acme.basic
|
- community.crypto.acme.basic
|
||||||
- community.crypto.acme.account
|
- community.crypto.acme.account
|
||||||
|
- community.crypto.acme.certificate
|
||||||
- community.crypto.attributes
|
- community.crypto.attributes
|
||||||
- community.crypto.attributes.files
|
- community.crypto.attributes.files
|
||||||
- community.crypto.attributes.actiongroup_acme
|
- community.crypto.attributes.actiongroup_acme
|
||||||
|
@ -141,32 +142,8 @@ options:
|
||||||
- 'tls-alpn-01'
|
- 'tls-alpn-01'
|
||||||
- 'no challenge'
|
- 'no challenge'
|
||||||
csr:
|
csr:
|
||||||
description:
|
|
||||||
- "File containing the CSR for the new certificate."
|
|
||||||
- "Can be created with M(community.crypto.openssl_csr) or C(openssl req ...)."
|
|
||||||
- "The CSR may contain multiple Subject Alternate Names, but each one
|
|
||||||
will lead to an individual challenge that must be fulfilled for the
|
|
||||||
CSR to be signed."
|
|
||||||
- "I(Note): the private key used to create the CSR I(must not) be the
|
|
||||||
account key. This is a bad idea from a security point of view, and
|
|
||||||
the CA should not accept the CSR. The ACME server should return an
|
|
||||||
error in this case."
|
|
||||||
- Precisely one of O(csr) or O(csr_content) must be specified.
|
|
||||||
type: path
|
|
||||||
aliases: ['src']
|
aliases: ['src']
|
||||||
csr_content:
|
csr_content:
|
||||||
description:
|
|
||||||
- "Content of the CSR for the new certificate."
|
|
||||||
- "Can be created with M(community.crypto.openssl_csr_pipe) or C(openssl req ...)."
|
|
||||||
- "The CSR may contain multiple Subject Alternate Names, but each one
|
|
||||||
will lead to an individual challenge that must be fulfilled for the
|
|
||||||
CSR to be signed."
|
|
||||||
- "I(Note): the private key used to create the CSR I(must not) be the
|
|
||||||
account key. This is a bad idea from a security point of view, and
|
|
||||||
the CA should not accept the CSR. The ACME server should return an
|
|
||||||
error in this case."
|
|
||||||
- Precisely one of O(csr) or O(csr_content) must be specified.
|
|
||||||
type: str
|
|
||||||
version_added: 1.2.0
|
version_added: 1.2.0
|
||||||
data:
|
data:
|
||||||
description:
|
description:
|
||||||
|
@ -920,15 +897,14 @@ class ACMECertificateClient(object):
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
argument_spec = create_default_argspec()
|
argument_spec = create_default_argspec(with_certificate=True)
|
||||||
|
argument_spec.argument_spec['csr']['aliases'] = ['src']
|
||||||
argument_spec.update_argspec(
|
argument_spec.update_argspec(
|
||||||
modify_account=dict(type='bool', default=True),
|
modify_account=dict(type='bool', default=True),
|
||||||
account_email=dict(type='str'),
|
account_email=dict(type='str'),
|
||||||
agreement=dict(type='str'),
|
agreement=dict(type='str'),
|
||||||
terms_agreed=dict(type='bool', default=False),
|
terms_agreed=dict(type='bool', default=False),
|
||||||
challenge=dict(type='str', default='http-01', choices=['http-01', 'dns-01', 'tls-alpn-01', NO_CHALLENGE]),
|
challenge=dict(type='str', default='http-01', choices=['http-01', 'dns-01', 'tls-alpn-01', NO_CHALLENGE]),
|
||||||
csr=dict(type='path', aliases=['src']),
|
|
||||||
csr_content=dict(type='str'),
|
|
||||||
data=dict(type='dict'),
|
data=dict(type='dict'),
|
||||||
dest=dict(type='path', aliases=['cert']),
|
dest=dict(type='path', aliases=['cert']),
|
||||||
fullchain_dest=dict(type='path', aliases=['fullchain']),
|
fullchain_dest=dict(type='path', aliases=['fullchain']),
|
||||||
|
@ -947,13 +923,9 @@ def main():
|
||||||
include_renewal_cert_id=dict(type='str', choices=['never', 'when_ari_supported', 'always'], default='never'),
|
include_renewal_cert_id=dict(type='str', choices=['never', 'when_ari_supported', 'always'], default='never'),
|
||||||
)
|
)
|
||||||
argument_spec.update(
|
argument_spec.update(
|
||||||
required_one_of=(
|
required_one_of=[
|
||||||
['dest', 'fullchain_dest'],
|
['dest', 'fullchain_dest'],
|
||||||
['csr', 'csr_content'],
|
],
|
||||||
),
|
|
||||||
mutually_exclusive=(
|
|
||||||
['csr', 'csr_content'],
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
module = argument_spec.create_ansible_module(supports_check_mode=True)
|
module = argument_spec.create_ansible_module(supports_check_mode=True)
|
||||||
backend = create_backend(module, False)
|
backend = create_backend(module, False)
|
||||||
|
|
Loading…
Reference in New Issue