Skip to content

Commit

Permalink
acme_certificate: allow 'no challenge' (#615)
Browse files Browse the repository at this point in the history
* Allow 'no challenge'.

* Fix undefined variable.
  • Loading branch information
felixfontein authored Jun 5, 2023
1 parent 9305bfe commit 17702d1
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 5 deletions.
4 changes: 4 additions & 0 deletions changelogs/fragments/615-no-challenge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
minor_changes:
- "acme_certificate - allow to use no challenge by providing ``no challenge`` for the ``challenge`` option.
This is needed for ACME servers where validation is done without challenges
(https://github.com/ansible-collections/community.crypto/issues/613, https://github.com/ansible-collections/community.crypto/pull/615)."
32 changes: 27 additions & 5 deletions plugins/modules/acme_certificate.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,19 @@
type: bool
default: true
challenge:
description: The challenge to be performed.
description:
- The challenge to be performed.
- If set to C(no challenge), no challenge will be used. This is necessary for some private
CAs which use External Account Binding and other means of validating certificate assurance.
For example, an account could be allowed to issue certificates for C(foo.example.com)
without any further validation for a certain period of time.
type: str
default: 'http-01'
choices: [ 'http-01', 'dns-01', 'tls-alpn-01' ]
choices:
- 'http-01'
- 'dns-01'
- 'tls-alpn-01'
- 'no challenge'
csr:
description:
- "File containing the CSR for the new certificate."
Expand Down Expand Up @@ -578,6 +587,9 @@
)


NO_CHALLENGE = 'no challenge'


class ACMECertificateClient(object):
'''
ACME client class. Uses an ACME account object and a CSR to
Expand All @@ -589,6 +601,9 @@ def __init__(self, module, backend):
self.module = module
self.version = module.params['acme_version']
self.challenge = module.params['challenge']
# We use None instead of a magic string for 'no challenge'
if self.challenge == NO_CHALLENGE:
self.challenge = None
self.csr = module.params['csr']
self.csr_content = module.params['csr_content']
self.dest = module.params.get('dest')
Expand Down Expand Up @@ -696,7 +711,7 @@ def get_challenges_data(self, first_step):
continue
# We drop the type from the key to preserve backwards compatibility
data[identifier] = authz.get_challenge_data(self.client)
if first_step and self.challenge not in data[identifier]:
if first_step and self.challenge is not None and self.challenge not in data[identifier]:
raise ModuleFailException("Found no challenge of type '{0}' for identifier {1}!".format(
self.challenge, type_identifier))
# Get DNS challenge data
Expand Down Expand Up @@ -735,7 +750,14 @@ def finish_challenges(self):
for type_identifier, authz in self.authorizations.items():
if authz.status == 'pending':
identifier_type, identifier = split_identifier(type_identifier)
authz.call_validate(self.client, self.challenge)
if self.challenge is not None:
authz.call_validate(self.client, self.challenge)
# If there is no challenge, we must check whether the authz is valid
elif authz.status != 'valid':
authz.raise_error(
'Status is not "valid", even though no challenge should be necessary',
module=self.client.module,
)
self.changed = True

def download_alternate_chains(self, cert):
Expand Down Expand Up @@ -832,7 +854,7 @@ def main():
account_email=dict(type='str'),
agreement=dict(type='str'),
terms_agreed=dict(type='bool', default=False),
challenge=dict(type='str', default='http-01', choices=['http-01', 'dns-01', 'tls-alpn-01']),
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'),
Expand Down

0 comments on commit 17702d1

Please sign in to comment.