acme_account: add support for External Account Binding (#100)

* acme_account: add support for External Account Binding.

* Add changelog fragment.

* Error if externalAccountRequired is set in ACME directory meta, but external account data is not provided.

* Validate that EAB key is Base64URL encoded.

* Improve documentation.

* Add padding to Base64 encoded key if necessary.

* Make account creation idempotent with ZeroSSL.
This commit is contained in:
Felix Fontein
2020-08-16 18:00:26 +02:00
committed by GitHub
parent 2f59d44f9e
commit d03e723fe0
5 changed files with 233 additions and 19 deletions

View File

@@ -86,6 +86,33 @@ options:
- "Mutually exclusive with C(new_account_key_src)."
- "Required if C(new_account_key_src) is not used and state is C(changed_key)."
type: str
external_account_binding:
description:
- Allows to provide external account binding data during account creation.
- This is used by CAs like Sectigo to bind a new ACME account to an existing CA-specific
account, to be able to properly identify a customer.
- Only used when creating a new account. Can not be specified for ACME v1.
type: dict
suboptions:
kid:
description:
- The key identifier provided by the CA.
type: str
required: true
alg:
description:
- The MAC algorithm provided by the CA.
- If not specified by the CA, this is probably C(HS256).
type: str
required: true
choices: [ HS256, HS384, HS512 ]
key:
description:
- Base64 URL encoded value of the MAC key provided by the CA.
- Padding (C(=) symbols at the end) can be omitted.
type: str
required: true
version_added: 1.1.0
'''
EXAMPLES = '''
@@ -125,6 +152,8 @@ account_uri:
type: str
'''
import base64
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.crypto.plugins.module_utils.acme import (
@@ -144,6 +173,11 @@ def main():
contact=dict(type='list', elements='str', default=[]),
new_account_key_src=dict(type='path'),
new_account_key_content=dict(type='str', no_log=True),
external_account_binding=dict(type='dict', options=dict(
kid=dict(type='str', required=True),
alg=dict(type='str', required=True, choices=['HS256', 'HS384', 'HS512']),
key=dict(type='str', required=True, no_log=True),
))
))
module = AnsibleModule(
argument_spec=argument_spec,
@@ -163,6 +197,18 @@ def main():
)
handle_standard_module_arguments(module, needs_acme_v2=True)
if module.params['external_account_binding']:
# Make sure padding is there
key = module.params['external_account_binding']['key']
if len(key) % 4 != 0:
key = key + ('=' * (4 - (len(key) % 4)))
# Make sure key is Base64 encoded
try:
base64.urlsafe_b64decode(key)
except Exception as e:
module.fail_json(msg='Key for external_account_binding must be Base64 URL encoded (%s)' % e)
module.params['external_account_binding']['key'] = key
try:
account = ACMEAccount(module)
changed = False
@@ -189,13 +235,14 @@ def main():
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')
external_account_binding = module.params.get('external_account_binding')
created, account_data = account.setup_account(
contact,
terms_agreed=terms_agreed,
allow_creation=allow_creation,
external_account_binding=external_account_binding,
)
if account_data is None:
raise ModuleFailException(msg='Account does not exist or is deactivated.')