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

@ -646,12 +646,14 @@ class ACMEAccount(object):
def _new_reg(self, contact=None, agreement=None, terms_agreed=False, allow_creation=True):
'''
Registers a new ACME account. Returns True if the account was
created and False if it already existed (e.g. it was not newly
created).
Registers a new ACME account. Returns a pair ``(created, data)``.
Here, ``created`` is ``True`` if the account was created and
``False`` if it already existed (e.g. it was not newly created),
or does not exist. In case the account was created or exists,
``data`` contains the account data; otherwise, it is ``None``.
https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-7.3
'''
contact = [] if contact is None else contact
contact = contact or []
if self.version == 1:
new_reg = {
@ -668,6 +670,7 @@ class ACMEAccount(object):
'contact': contact
}
if not allow_creation:
# https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-7.3.1
new_reg['onlyReturnExisting'] = True
if terms_agreed:
new_reg['termsOfServiceAgreed'] = True
@ -679,7 +682,7 @@ class ACMEAccount(object):
# Account did not exist
if 'location' in info:
self.set_account_uri(info['location'])
return True
return True, result
elif info['status'] == (409 if self.version == 1 else 200):
# Account did exist
if result.get('status') == 'deactivated':
@ -689,22 +692,22 @@ class ACMEAccount(object):
# "Once an account is deactivated, the server MUST NOT accept further
# requests authorized by that account's key."
if not allow_creation:
return False
return False, None
else:
raise ModuleFailException("Account is deactivated")
if 'location' in info:
self.set_account_uri(info['location'])
return False
return False, result
elif info['status'] == 400 and result['type'] == 'urn:ietf:params:acme:error:accountDoesNotExist' and not allow_creation:
# Account does not exist (and we didn't try to create it)
return False
return False, None
else:
raise ModuleFailException("Error registering: {0} {1}".format(info['status'], result))
def get_account_data(self):
'''
Retrieve account information. Can only be called when the account
URI is already known (such as after calling init_account).
URI is already known (such as after calling setup_account).
Return None if the account was deactivated, or a dict otherwise.
'''
if self.uri is None:
@ -732,66 +735,82 @@ class ACMEAccount(object):
raise ModuleFailException("Error getting account data from {2}: {0} {1}".format(info['status'], result, self.uri))
return result
def init_account(self, contact, agreement=None, terms_agreed=False, allow_creation=True, update_contact=True, remove_account_uri_if_not_exists=False):
def setup_account(self, contact=None, agreement=None, terms_agreed=False, allow_creation=True, remove_account_uri_if_not_exists=False):
'''
Create or update an account on the ACME server. For ACME v1,
Detect or create an account on the ACME server. For ACME v1,
as the only way (without knowing an account URI) to test if an
account exists is to try and create one with the provided account
key, this method will always result in an account being present
(except on error situations). For ACME v2, a new account will
only be created if allow_creation is set to True.
only be created if ``allow_creation`` is set to True.
For ACME v2, check_mode is fully respected. For ACME v1, the account
might be created if it does not yet exist.
For ACME v2, ``check_mode`` is fully respected. For ACME v1, the
account might be created if it does not yet exist.
If the account already exists and if update_contact is set to
True, this method will update the contact information.
Return a pair ``(created, account_data)``. Here, ``created`` will
be ``True`` in case the account was created or would be created
(check mode). ``account_data`` will be the current account data,
or ``None`` if the account does not exist.
Return True in case something changed (account was created, contact
info updated) or would be changed (check_mode). The account URI
will be stored in self.uri; if it is None, the account does not
exist.
The account URI will be stored in ``self.uri``; if it is ``None``,
the account does not exist.
https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-7.3
'''
new_account = True
changed = False
if self.uri is not None:
new_account = False
if not update_contact:
# Verify that the account key belongs to the URI.
# (If update_contact is True, this will be done below.)
if self.get_account_data() is None:
if remove_account_uri_if_not_exists and not allow_creation:
self.uri = None
return False
created = False
# Verify that the account key belongs to the URI.
# (If update_contact is True, this will be done below.)
account_data = self.get_account_data()
if account_data is None:
if remove_account_uri_if_not_exists and not allow_creation:
self.uri = None
else:
raise ModuleFailException("Account is deactivated or does not exist!")
else:
new_account = self._new_reg(
created, account_data = self._new_reg(
contact,
agreement=agreement,
terms_agreed=terms_agreed,
allow_creation=allow_creation and not self.module.check_mode
)
if self.module.check_mode and self.uri is None and allow_creation:
return True
if not new_account and self.uri and update_contact:
result = self.get_account_data()
if result is None:
if not allow_creation:
self.uri = None
return False
raise ModuleFailException("Account is deactivated or does not exist!")
created = True
account_data = {
'contact': contact or []
}
return created, account_data
# ...and check if update is necessary
if result.get('contact', []) != contact:
if not self.module.check_mode:
upd_reg = result
upd_reg['contact'] = contact
result, dummy = self.send_signed_request(self.uri, upd_reg)
changed = True
return new_account or changed
def update_account(self, account_data, contact=None):
'''
Update an account on the ACME server. Check mode is fully respected.
The current account data must be provided as ``account_data``.
Return a pair ``(updated, account_data)``, where ``updated`` is
``True`` in case something changed (contact info updated) or
would be changed (check mode), and ``account_data`` the updated
account data.
https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-7.3.2
'''
# Create request
update_request = {}
if contact is not None and account_data.get('contact', []) != contact:
update_request['contact'] = list(contact)
# No change?
if not update_request:
return False, dict(account_data)
# Apply change
if self.module.check_mode:
account_data = dict(account_data)
account_data.update(update_request)
else:
account_data, dummy = self.send_signed_request(self.uri, update_request)
return True, account_data
def cryptography_get_csr_domains(module, csr_filename):