ACME: add diff to acme_account, account_public_key to acme_account_facts, and general refactoring (#49410)

* Only one exit point.

* Refactoring account handling.

* Add diff support for acme_account.

* Insert public_account_key into acme_account_facts result and into acme_account diff.

* Add changelog.
This commit is contained in:
Felix Fontein 2018-12-02 18:40:14 +01:00 committed by René Moser
commit b0c7efcc6b
9 changed files with 305 additions and 104 deletions

View file

@ -166,43 +166,51 @@ def main():
try:
account = ACMEAccount(module)
changed = False
state = module.params.get('state')
diff_before = {}
diff_after = {}
if state == 'absent':
changed = account.init_account(
[],
allow_creation=False,
update_contact=False,
)
if changed:
raise AssertionError('Unwanted account change')
if account.uri is not None:
# Account does exist
account_data = account.get_account_data()
if account_data is not None:
# Account is not yet deactivated
if not module.check_mode:
# Deactivate it
payload = {
'status': 'deactivated'
}
result, info = account.send_signed_request(account.uri, payload)
if info['status'] != 200:
raise ModuleFailException('Error deactivating account: {0} {1}'.format(info['status'], result))
module.exit_json(changed=True, account_uri=account.uri)
module.exit_json(changed=False, account_uri=account.uri)
created, account_data = account.setup_account(allow_creation=False)
if account_data:
diff_before = dict(account_data)
diff_before['public_account_key'] = account.key_data['jwk']
if created:
raise AssertionError('Unwanted account creation')
if account_data is not None:
# Account is not yet deactivated
if not module.check_mode:
# Deactivate it
payload = {
'status': 'deactivated'
}
result, info = account.send_signed_request(account.uri, payload)
if info['status'] != 200:
raise ModuleFailException('Error deactivating account: {0} {1}'.format(info['status'], result))
changed = True
elif state == 'present':
allow_creation = module.params.get('allow_creation')
# Make sure contact is a list of strings (unfortunately, Ansible doesn't do that for us)
contact = [str(v) for v in module.params.get('contact')]
terms_agreed = module.params.get('terms_agreed')
changed = account.init_account(
created, account_data = account.setup_account(
contact,
terms_agreed=terms_agreed,
allow_creation=allow_creation,
)
if account.uri is None:
if account_data is None:
raise ModuleFailException(msg='Account does not exist or is deactivated.')
module.exit_json(changed=changed, account_uri=account.uri)
if created:
diff_before = {}
else:
diff_before = dict(account_data)
diff_before['public_account_key'] = account.key_data['jwk']
updated = False
if not created:
updated, account_data = account.update_account(account_data, contact)
changed = created or updated
diff_after = dict(account_data)
diff_after['public_account_key'] = account.key_data['jwk']
elif state == 'changed_key':
# Parse new account key
error, new_key_data = account.parse_key(
@ -212,15 +220,13 @@ def main():
if error:
raise ModuleFailException("error while parsing account key: %s" % error)
# Verify that the account exists and has not been deactivated
changed = account.init_account(
[],
allow_creation=False,
update_contact=False,
)
if changed:
raise AssertionError('Unwanted account change')
if account.uri is None or account.get_account_data() is None:
created, account_data = account.setup_account(allow_creation=False)
if created:
raise AssertionError('Unwanted account creation')
if account_data is None:
raise ModuleFailException(msg='Account does not exist or is deactivated.')
diff_before = dict(account_data)
diff_before['public_account_key'] = account.key_data['jwk']
# Now we can start the account key rollover
if not module.check_mode:
# Compose inner signed message
@ -241,7 +247,25 @@ def main():
result, info = account.send_signed_request(url, data)
if info['status'] != 200:
raise ModuleFailException('Error account key rollover: {0} {1}'.format(info['status'], result))
module.exit_json(changed=True, account_uri=account.uri)
if module._diff:
account.key_data = new_key_data
account.jws_header['alg'] = new_key_data['alg']
diff_after = account.get_account_data()
elif module._diff:
# Kind of fake diff_after
diff_after = dict(diff_before)
diff_after['public_account_key'] = new_key_data['jwk']
changed = True
result = {
'changed': changed,
'account_uri': account.uri,
}
if module._diff:
result['diff'] = {
'before': diff_before,
'after': diff_after,
}
module.exit_json(**result)
except ModuleFailException as e:
e.do_fail(module)

View file

@ -89,6 +89,11 @@ account:
returned: always
type: str
sample: https://example.ca/account/1/orders
public_account_key:
description: the public account key as a L(JSON Web Key,https://tools.ietf.org/html/rfc7517).
returned: always
type: str
sample: https://example.ca/account/1/orders
'''
from ansible.module_utils.acme import (
@ -129,24 +134,25 @@ def main():
try:
account = ACMEAccount(module)
# Check whether account exists
changed = account.init_account(
created, account_data = account.setup_account(
[],
allow_creation=False,
update_contact=False,
remove_account_uri_if_not_exists=True,
)
if changed:
raise AssertionError('Unwanted account change')
if account.uri is None:
# Account does exist
module.exit_json(changed=False, exists=False, account_uri=None)
else:
# Account exists: retrieve account information
data = account.get_account_data()
if created:
raise AssertionError('Unwanted account creation')
result = {
'changed': False,
'exists': account.uri is not None,
'account_uri': account.uri,
}
if account.uri is not None:
# Make sure promised data is there
if 'contact' not in data:
data['contact'] = []
module.exit_json(changed=False, exists=True, account_uri=account.uri, account=data)
if 'contact' not in account_data:
account_data['contact'] = []
account_data['public_account_key'] = account.key_data['jwk']
result['account'] = account_data
module.exit_json(**result)
except ModuleFailException as e:
e.do_fail(module)

View file

@ -406,16 +406,21 @@ class ACMEClient(object):
contact = []
if module.params['account_email']:
contact.append('mailto:' + module.params['account_email'])
self.changed = self.account.init_account(
created, account_data = self.account.setup_account(
contact,
agreement=module.params.get('agreement'),
terms_agreed=module.params.get('terms_agreed'),
allow_creation=modify_account,
update_contact=modify_account
)
if account_data is None:
raise ModuleFailException(msg='Account does not exist or is deactivated.')
updated = False
if not created and account_data and modify_account:
updated, account_data = self.account.update_account(account_data, contact)
self.changed = created or updated
else:
# This happens if modify_account is False and the ACME v1
# protocol is used. In this case, we do not call init_account()
# protocol is used. In this case, we do not call setup_account()
# to avoid accidental creation of an account. This is OK
# since for ACME v1, the account URI is not needed to send a
# signed ACME request.

View file

@ -177,13 +177,11 @@ def main():
result, info = account.send_signed_request(endpoint, payload, key_data=private_key_data, jws_header=jws_header)
else:
# Step 1: get hold of account URI
changed = account.init_account(
[],
allow_creation=False,
update_contact=False,
)
if changed:
raise AssertionError('Unwanted account change')
created, account_data = account.setup_account(allow_creation=False)
if created:
raise AssertionError('Unwanted account creation')
if account_data is None:
raise ModuleFailException(msg='Account does not exist or is deactivated.')
# Step 2: sign revokation request with account key
result, info = account.send_signed_request(endpoint, payload)
if info['status'] != 200: