community.crypto/plugins/modules/acme_inspect.py

326 lines
12 KiB
Python
Raw Normal View History

2020-03-09 13:11:34 +00:00
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2018 Felix Fontein (@felixfontein)
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
2020-03-09 13:11:34 +00:00
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: acme_inspect
author: "Felix Fontein (@felixfontein)"
short_description: Send direct requests to an ACME server
description:
- "Allows to send direct requests to an ACME server with the
L(ACME protocol,https://tools.ietf.org/html/rfc8555),
which is supported by CAs such as L(Let's Encrypt,https://letsencrypt.org/)."
- "This module can be used to debug failed certificate request attempts,
for example when M(community.crypto.acme_certificate) fails or encounters a problem which
you wish to investigate."
- "The module can also be used to directly access features of an ACME servers
which are not yet supported by the Ansible ACME modules."
2020-03-09 13:11:34 +00:00
notes:
- "The O(account_uri) option must be specified for properly authenticated
ACME v2 requests (except a C(new-account) request)."
- "Using the C(ansible) tool, M(community.crypto.acme_inspect) can be used to directly execute
ACME requests without the need of writing a playbook. For example, the
following command retrieves the ACME account with ID 1 from Let's Encrypt
(assuming C(/path/to/key) is the correct private account key):
C(ansible localhost -m acme_inspect -a \"account_key_src=/path/to/key
acme_directory=https://acme-v02.api.letsencrypt.org/directory acme_version=2
account_uri=https://acme-v02.api.letsencrypt.org/acme/acct/1 method=get
url=https://acme-v02.api.letsencrypt.org/acme/acct/1\")"
2020-03-09 13:11:34 +00:00
seealso:
- name: Automatic Certificate Management Environment (ACME)
description: The specification of the ACME protocol (RFC 8555).
link: https://tools.ietf.org/html/rfc8555
- name: ACME TLS ALPN Challenge Extension
description: The specification of the C(tls-alpn-01) challenge (RFC 8737).
link: https://www.rfc-editor.org/rfc/rfc8737.html
extends_documentation_fragment:
Revert all non-bugfixes merged since the last release. Revert "Fix documentation. (#751)" Revert "ACME modules: simplify code, refactor argspec handling code, move csr/csr_content to own docs fragment (#750)" Revert "Refactor and extend argument spec helper, use for ACME modules (#749)" Revert "Avoid exception if certificate has no AKI in acme_certificate. (#748)" Revert "ACME: improve acme_certificate docs, include cert_id in acme_certificate_renewal_info return value (#747)" Revert "Add acme_certificate_renewal_info module (#746)" Revert "Refactor time code, add tests, fix bug when parsing absolute timestamps that omit seconds (#745)" Revert "Add tests for acme_certificate_deactivate_authz module. (#744)" Revert "Create acme_certificate_deactivate_authz module (#741)" Revert "acme_certificate: allow to request renewal of a certificate according to ARI (#739)" Revert "Implement basic acme_ari_info module. (#732)" Revert "Add function for retrieval of ARI information. (#738)" Revert "acme module utils: add functions for parsing Retry-After header values and computation of ARI certificate IDs (#737)" Revert "Implement certificate information retrieval code in the ACME backends. (#736)" Revert "Split up the default acme docs fragment to allow modules ot not need account data. (#735)" This reverts commits 5e59c5261e7664c9f823ff2633c67d4d5de931b5, aa82575a786ec0ab79fe549db6bdd77b325767c2, f3c9cb7a8ac37a7b2565934ff5c4fc63f087407a, f82b33591614fa8013259762865b69b6e7e537ac, 553ab45f46cf5af6bed5867e447e4c9878cf8e68, 59606d48ad26e0a72c64c10eccaca04bf67db548, 0a15be101758333bafce41f11189852d210f4194, 9501a28a934653d61329c8c271879b09cf6b7c27, d906914737c40b60b23928f3929ff34d25d25c35, 33d278ad8fafedd01591351bbd584f458bb6f1c0, 6d4fc589aee55b81bb0717eed6dd12401785f20c, 9614b09f7a5b744ebb59e9a1b82cb301dc3859cd, af5f4b57f8afa61082e9a24b6218cbc970939e80, c6fbe58382b4a67fb8d86aaec11f89f7445f15ea, and afe7f7522c34a0b08199dac39501aa041ce5fc57.
2024-05-11 14:06:52 +00:00
- community.crypto.acme
- community.crypto.attributes
- community.crypto.attributes.actiongroup_acme
attributes:
check_mode:
support: none
diff_mode:
support: none
2020-03-09 13:11:34 +00:00
options:
url:
description:
- "The URL to send the request to."
- "Must be specified if O(method) is not V(directory-only)."
2020-03-09 13:11:34 +00:00
type: str
method:
description:
- "The method to use to access the given URL on the ACME server."
- "The value V(post) executes an authenticated POST request. The content
must be specified in the O(content) option."
- "The value V(get) executes an authenticated POST-as-GET request for ACME v2,
2020-03-09 13:11:34 +00:00
and a regular GET request for ACME v1."
- "The value V(directory-only) only retrieves the directory, without doing
2020-03-09 13:11:34 +00:00
a request."
type: str
default: get
choices:
- get
- post
- directory-only
content:
description:
- "An encoded JSON object which will be sent as the content if O(method)
is V(post)."
- "Required when O(method) is V(post), and not allowed otherwise."
2020-03-09 13:11:34 +00:00
type: str
fail_on_acme_error:
description:
- "If O(method) is V(post) or V(get), make the module fail in case an ACME
2020-03-09 13:11:34 +00:00
error is returned."
type: bool
2022-08-23 19:33:29 +00:00
default: true
2020-03-09 13:11:34 +00:00
'''
EXAMPLES = r'''
- name: Get directory
2020-03-31 14:23:45 +00:00
community.crypto.acme_inspect:
2020-03-09 13:11:34 +00:00
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
acme_version: 2
method: directory-only
register: directory
- name: Create an account
2020-03-31 14:23:45 +00:00
community.crypto.acme_inspect:
2020-03-09 13:11:34 +00:00
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
url: "{{ directory.newAccount}}"
method: post
content: '{"termsOfServiceAgreed":true}'
register: account_creation
# account_creation.headers.location contains the account URI
# if creation was successful
- name: Get account information
2020-03-31 14:23:45 +00:00
community.crypto.acme_inspect:
2020-03-09 13:11:34 +00:00
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
account_uri: "{{ account_creation.headers.location }}"
url: "{{ account_creation.headers.location }}"
method: get
- name: Update account contacts
2020-03-31 14:23:45 +00:00
community.crypto.acme_inspect:
2020-03-09 13:11:34 +00:00
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
account_uri: "{{ account_creation.headers.location }}"
url: "{{ account_creation.headers.location }}"
method: post
content: '{{ account_info | to_json }}'
vars:
account_info:
# For valid values, see
# https://tools.ietf.org/html/rfc8555#section-7.3
contact:
- mailto:me@example.com
- name: Create certificate order
2020-03-31 14:23:45 +00:00
community.crypto.acme_certificate:
2020-03-09 13:11:34 +00:00
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
account_uri: "{{ account_creation.headers.location }}"
csr: /etc/pki/cert/csr/sample.com.csr
fullchain_dest: /etc/httpd/ssl/sample.com-fullchain.crt
challenge: http-01
register: certificate_request
# Assume something went wrong. certificate_request.order_uri contains
# the order URI.
- name: Get order information
2020-03-31 14:23:45 +00:00
community.crypto.acme_inspect:
2020-03-09 13:11:34 +00:00
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
account_uri: "{{ account_creation.headers.location }}"
url: "{{ certificate_request.order_uri }}"
method: get
register: order
- name: Get first authz for order
2020-03-31 14:23:45 +00:00
community.crypto.acme_inspect:
2020-03-09 13:11:34 +00:00
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
account_uri: "{{ account_creation.headers.location }}"
url: "{{ order.output_json.authorizations[0] }}"
method: get
register: authz
- name: Get HTTP-01 challenge for authz
2020-03-31 14:23:45 +00:00
community.crypto.acme_inspect:
2020-03-09 13:11:34 +00:00
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
account_uri: "{{ account_creation.headers.location }}"
url: "{{ authz.output_json.challenges | selectattr('type', 'equalto', 'http-01') }}"
method: get
register: http01challenge
- name: Activate HTTP-01 challenge manually
2020-03-31 14:23:45 +00:00
community.crypto.acme_inspect:
2020-03-09 13:11:34 +00:00
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
account_uri: "{{ account_creation.headers.location }}"
url: "{{ http01challenge.url }}"
method: post
content: '{}'
'''
RETURN = '''
directory:
description: The ACME directory's content
returned: always
type: dict
sample:
2020-03-09 13:11:34 +00:00
{
"a85k3x9f91A4": "https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417",
"keyChange": "https://acme-v02.api.letsencrypt.org/acme/key-change",
"meta": {
"caaIdentities": [
"letsencrypt.org"
],
"termsOfService": "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf",
"website": "https://letsencrypt.org"
},
"newAccount": "https://acme-v02.api.letsencrypt.org/acme/new-acct",
"newNonce": "https://acme-v02.api.letsencrypt.org/acme/new-nonce",
"newOrder": "https://acme-v02.api.letsencrypt.org/acme/new-order",
"revokeCert": "https://acme-v02.api.letsencrypt.org/acme/revoke-cert"
}
headers:
description: The request's HTTP headers (with lowercase keys)
returned: always
type: dict
sample:
2020-03-09 13:11:34 +00:00
{
"boulder-requester": "12345",
"cache-control": "max-age=0, no-cache, no-store",
"connection": "close",
"content-length": "904",
"content-type": "application/json",
"cookies": {},
"cookies_string": "",
"date": "Wed, 07 Nov 2018 12:34:56 GMT",
"expires": "Wed, 07 Nov 2018 12:44:56 GMT",
"link": '<https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf>;rel="terms-of-service"',
2020-03-09 13:11:34 +00:00
"msg": "OK (904 bytes)",
"pragma": "no-cache",
"replay-nonce": "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGH",
"server": "nginx",
"status": 200,
"strict-transport-security": "max-age=604800",
"url": "https://acme-v02.api.letsencrypt.org/acme/acct/46161",
"x-frame-options": "DENY"
}
output_text:
description: The raw text output
returned: always
type: str
sample: "{\\n \\\"id\\\": 12345,\\n \\\"key\\\": {\\n \\\"kty\\\": \\\"RSA\\\",\\n ..."
output_json:
description: The output parsed as JSON
returned: if output can be parsed as JSON
type: dict
sample:
- id: 12345
- key:
- kty: RSA
- ...
'''
Revert all non-bugfixes merged since the last release. Revert "Fix documentation. (#751)" Revert "ACME modules: simplify code, refactor argspec handling code, move csr/csr_content to own docs fragment (#750)" Revert "Refactor and extend argument spec helper, use for ACME modules (#749)" Revert "Avoid exception if certificate has no AKI in acme_certificate. (#748)" Revert "ACME: improve acme_certificate docs, include cert_id in acme_certificate_renewal_info return value (#747)" Revert "Add acme_certificate_renewal_info module (#746)" Revert "Refactor time code, add tests, fix bug when parsing absolute timestamps that omit seconds (#745)" Revert "Add tests for acme_certificate_deactivate_authz module. (#744)" Revert "Create acme_certificate_deactivate_authz module (#741)" Revert "acme_certificate: allow to request renewal of a certificate according to ARI (#739)" Revert "Implement basic acme_ari_info module. (#732)" Revert "Add function for retrieval of ARI information. (#738)" Revert "acme module utils: add functions for parsing Retry-After header values and computation of ARI certificate IDs (#737)" Revert "Implement certificate information retrieval code in the ACME backends. (#736)" Revert "Split up the default acme docs fragment to allow modules ot not need account data. (#735)" This reverts commits 5e59c5261e7664c9f823ff2633c67d4d5de931b5, aa82575a786ec0ab79fe549db6bdd77b325767c2, f3c9cb7a8ac37a7b2565934ff5c4fc63f087407a, f82b33591614fa8013259762865b69b6e7e537ac, 553ab45f46cf5af6bed5867e447e4c9878cf8e68, 59606d48ad26e0a72c64c10eccaca04bf67db548, 0a15be101758333bafce41f11189852d210f4194, 9501a28a934653d61329c8c271879b09cf6b7c27, d906914737c40b60b23928f3929ff34d25d25c35, 33d278ad8fafedd01591351bbd584f458bb6f1c0, 6d4fc589aee55b81bb0717eed6dd12401785f20c, 9614b09f7a5b744ebb59e9a1b82cb301dc3859cd, af5f4b57f8afa61082e9a24b6218cbc970939e80, c6fbe58382b4a67fb8d86aaec11f89f7445f15ea, and afe7f7522c34a0b08199dac39501aa041ce5fc57.
2024-05-11 14:06:52 +00:00
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.common.text.converters import to_native, to_bytes, to_text
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
create_backend,
Revert all non-bugfixes merged since the last release. Revert "Fix documentation. (#751)" Revert "ACME modules: simplify code, refactor argspec handling code, move csr/csr_content to own docs fragment (#750)" Revert "Refactor and extend argument spec helper, use for ACME modules (#749)" Revert "Avoid exception if certificate has no AKI in acme_certificate. (#748)" Revert "ACME: improve acme_certificate docs, include cert_id in acme_certificate_renewal_info return value (#747)" Revert "Add acme_certificate_renewal_info module (#746)" Revert "Refactor time code, add tests, fix bug when parsing absolute timestamps that omit seconds (#745)" Revert "Add tests for acme_certificate_deactivate_authz module. (#744)" Revert "Create acme_certificate_deactivate_authz module (#741)" Revert "acme_certificate: allow to request renewal of a certificate according to ARI (#739)" Revert "Implement basic acme_ari_info module. (#732)" Revert "Add function for retrieval of ARI information. (#738)" Revert "acme module utils: add functions for parsing Retry-After header values and computation of ARI certificate IDs (#737)" Revert "Implement certificate information retrieval code in the ACME backends. (#736)" Revert "Split up the default acme docs fragment to allow modules ot not need account data. (#735)" This reverts commits 5e59c5261e7664c9f823ff2633c67d4d5de931b5, aa82575a786ec0ab79fe549db6bdd77b325767c2, f3c9cb7a8ac37a7b2565934ff5c4fc63f087407a, f82b33591614fa8013259762865b69b6e7e537ac, 553ab45f46cf5af6bed5867e447e4c9878cf8e68, 59606d48ad26e0a72c64c10eccaca04bf67db548, 0a15be101758333bafce41f11189852d210f4194, 9501a28a934653d61329c8c271879b09cf6b7c27, d906914737c40b60b23928f3929ff34d25d25c35, 33d278ad8fafedd01591351bbd584f458bb6f1c0, 6d4fc589aee55b81bb0717eed6dd12401785f20c, 9614b09f7a5b744ebb59e9a1b82cb301dc3859cd, af5f4b57f8afa61082e9a24b6218cbc970939e80, c6fbe58382b4a67fb8d86aaec11f89f7445f15ea, and afe7f7522c34a0b08199dac39501aa041ce5fc57.
2024-05-11 14:06:52 +00:00
get_default_argspec,
ACMEClient,
)
from ansible_collections.community.crypto.plugins.module_utils.acme.errors import (
ACMEProtocolException,
ModuleFailException,
2020-03-09 13:11:34 +00:00
)
def main():
Revert all non-bugfixes merged since the last release. Revert "Fix documentation. (#751)" Revert "ACME modules: simplify code, refactor argspec handling code, move csr/csr_content to own docs fragment (#750)" Revert "Refactor and extend argument spec helper, use for ACME modules (#749)" Revert "Avoid exception if certificate has no AKI in acme_certificate. (#748)" Revert "ACME: improve acme_certificate docs, include cert_id in acme_certificate_renewal_info return value (#747)" Revert "Add acme_certificate_renewal_info module (#746)" Revert "Refactor time code, add tests, fix bug when parsing absolute timestamps that omit seconds (#745)" Revert "Add tests for acme_certificate_deactivate_authz module. (#744)" Revert "Create acme_certificate_deactivate_authz module (#741)" Revert "acme_certificate: allow to request renewal of a certificate according to ARI (#739)" Revert "Implement basic acme_ari_info module. (#732)" Revert "Add function for retrieval of ARI information. (#738)" Revert "acme module utils: add functions for parsing Retry-After header values and computation of ARI certificate IDs (#737)" Revert "Implement certificate information retrieval code in the ACME backends. (#736)" Revert "Split up the default acme docs fragment to allow modules ot not need account data. (#735)" This reverts commits 5e59c5261e7664c9f823ff2633c67d4d5de931b5, aa82575a786ec0ab79fe549db6bdd77b325767c2, f3c9cb7a8ac37a7b2565934ff5c4fc63f087407a, f82b33591614fa8013259762865b69b6e7e537ac, 553ab45f46cf5af6bed5867e447e4c9878cf8e68, 59606d48ad26e0a72c64c10eccaca04bf67db548, 0a15be101758333bafce41f11189852d210f4194, 9501a28a934653d61329c8c271879b09cf6b7c27, d906914737c40b60b23928f3929ff34d25d25c35, 33d278ad8fafedd01591351bbd584f458bb6f1c0, 6d4fc589aee55b81bb0717eed6dd12401785f20c, 9614b09f7a5b744ebb59e9a1b82cb301dc3859cd, af5f4b57f8afa61082e9a24b6218cbc970939e80, c6fbe58382b4a67fb8d86aaec11f89f7445f15ea, and afe7f7522c34a0b08199dac39501aa041ce5fc57.
2024-05-11 14:06:52 +00:00
argument_spec = get_default_argspec()
argument_spec.update(dict(
2020-03-09 13:11:34 +00:00
url=dict(type='str'),
method=dict(type='str', choices=['get', 'post', 'directory-only'], default='get'),
content=dict(type='str'),
fail_on_acme_error=dict(type='bool', default=True),
Revert all non-bugfixes merged since the last release. Revert "Fix documentation. (#751)" Revert "ACME modules: simplify code, refactor argspec handling code, move csr/csr_content to own docs fragment (#750)" Revert "Refactor and extend argument spec helper, use for ACME modules (#749)" Revert "Avoid exception if certificate has no AKI in acme_certificate. (#748)" Revert "ACME: improve acme_certificate docs, include cert_id in acme_certificate_renewal_info return value (#747)" Revert "Add acme_certificate_renewal_info module (#746)" Revert "Refactor time code, add tests, fix bug when parsing absolute timestamps that omit seconds (#745)" Revert "Add tests for acme_certificate_deactivate_authz module. (#744)" Revert "Create acme_certificate_deactivate_authz module (#741)" Revert "acme_certificate: allow to request renewal of a certificate according to ARI (#739)" Revert "Implement basic acme_ari_info module. (#732)" Revert "Add function for retrieval of ARI information. (#738)" Revert "acme module utils: add functions for parsing Retry-After header values and computation of ARI certificate IDs (#737)" Revert "Implement certificate information retrieval code in the ACME backends. (#736)" Revert "Split up the default acme docs fragment to allow modules ot not need account data. (#735)" This reverts commits 5e59c5261e7664c9f823ff2633c67d4d5de931b5, aa82575a786ec0ab79fe549db6bdd77b325767c2, f3c9cb7a8ac37a7b2565934ff5c4fc63f087407a, f82b33591614fa8013259762865b69b6e7e537ac, 553ab45f46cf5af6bed5867e447e4c9878cf8e68, 59606d48ad26e0a72c64c10eccaca04bf67db548, 0a15be101758333bafce41f11189852d210f4194, 9501a28a934653d61329c8c271879b09cf6b7c27, d906914737c40b60b23928f3929ff34d25d25c35, 33d278ad8fafedd01591351bbd584f458bb6f1c0, 6d4fc589aee55b81bb0717eed6dd12401785f20c, 9614b09f7a5b744ebb59e9a1b82cb301dc3859cd, af5f4b57f8afa61082e9a24b6218cbc970939e80, c6fbe58382b4a67fb8d86aaec11f89f7445f15ea, and afe7f7522c34a0b08199dac39501aa041ce5fc57.
2024-05-11 14:06:52 +00:00
))
module = AnsibleModule(
argument_spec=argument_spec,
mutually_exclusive=(
['account_key_src', 'account_key_content'],
),
2020-03-09 13:11:34 +00:00
required_if=(
['method', 'get', ['url']],
['method', 'post', ['url', 'content']],
['method', 'get', ['account_key_src', 'account_key_content'], True],
['method', 'post', ['account_key_src', 'account_key_content'], True],
),
)
backend = create_backend(module, False)
2020-03-09 13:11:34 +00:00
result = dict()
changed = False
try:
# Get hold of ACMEClient and ACMEAccount objects (includes directory)
client = ACMEClient(module, backend)
2020-03-09 13:11:34 +00:00
method = module.params['method']
result['directory'] = client.directory.directory
2020-03-09 13:11:34 +00:00
# Do we have to do more requests?
if method != 'directory-only':
url = module.params['url']
fail_on_acme_error = module.params['fail_on_acme_error']
# Do request
if method == 'get':
data, info = client.get_request(url, parse_json_result=False, fail_on_error=False)
2020-03-09 13:11:34 +00:00
elif method == 'post':
changed = True # only POSTs can change
data, info = client.send_signed_request(
url, to_bytes(module.params['content']), parse_json_result=False, encode_payload=False, fail_on_error=False)
2020-03-09 13:11:34 +00:00
# Update results
result.update(dict(
headers=info,
output_text=to_native(data),
))
# See if we can parse the result as JSON
try:
result['output_json'] = module.from_json(to_text(data))
2020-03-09 13:11:34 +00:00
except Exception as dummy:
pass
# Fail if error was returned
if fail_on_acme_error and info['status'] >= 400:
raise ACMEProtocolException(module, info=info, content=data)
2020-03-09 13:11:34 +00:00
# Done!
module.exit_json(changed=changed, **result)
except ModuleFailException as e:
e.do_fail(module, **result)
if __name__ == '__main__':
main()