add acme_inspect module (#48444)
This commit is contained in:
parent
fe147289b7
commit
2bd8e47247
8 changed files with 649 additions and 8 deletions
|
@ -59,8 +59,8 @@ class ModuleFailException(Exception):
|
|||
self.msg = msg
|
||||
self.module_fail_args = args
|
||||
|
||||
def do_fail(self, module):
|
||||
module.fail_json(msg=self.msg, other=self.module_fail_args)
|
||||
def do_fail(self, module, **arguments):
|
||||
module.fail_json(msg=self.msg, other=self.module_fail_args, **arguments)
|
||||
|
||||
|
||||
def nopad_b64(data):
|
||||
|
@ -518,12 +518,16 @@ class ACMEAccount(object):
|
|||
else:
|
||||
return _parse_key_openssl(self._openssl_bin, self.module, key_file, key_content)
|
||||
|
||||
def sign_request(self, protected, payload, key_data):
|
||||
def sign_request(self, protected, payload, key_data, encode_payload=True):
|
||||
try:
|
||||
if payload is None:
|
||||
# POST-as-GET
|
||||
payload64 = ''
|
||||
else:
|
||||
payload64 = nopad_b64(self.module.jsonify(payload).encode('utf8'))
|
||||
# POST
|
||||
if encode_payload:
|
||||
payload = self.module.jsonify(payload).encode('utf8')
|
||||
payload64 = nopad_b64(to_bytes(payload))
|
||||
protected64 = nopad_b64(self.module.jsonify(protected).encode('utf8'))
|
||||
except Exception as e:
|
||||
raise ModuleFailException("Failed to encode payload / headers as JSON: {0}".format(e))
|
||||
|
@ -533,7 +537,7 @@ class ACMEAccount(object):
|
|||
else:
|
||||
return _sign_request_openssl(self._openssl_bin, self.module, payload64, protected64, key_data)
|
||||
|
||||
def send_signed_request(self, url, payload, key_data=None, jws_header=None, parse_json_result=True):
|
||||
def send_signed_request(self, url, payload, key_data=None, jws_header=None, parse_json_result=True, encode_payload=True):
|
||||
'''
|
||||
Sends a JWS signed HTTP POST request to the ACME server and returns
|
||||
the response as dictionary
|
||||
|
@ -551,7 +555,7 @@ class ACMEAccount(object):
|
|||
if self.version != 1:
|
||||
protected["url"] = url
|
||||
|
||||
data = self.sign_request(protected, payload, key_data)
|
||||
data = self.sign_request(protected, payload, key_data, encode_payload=encode_payload)
|
||||
if self.version == 1:
|
||||
data["header"] = jws_header
|
||||
data = self.module.jsonify(data)
|
||||
|
@ -588,7 +592,7 @@ class ACMEAccount(object):
|
|||
|
||||
return result, info
|
||||
|
||||
def get_request(self, uri, parse_json_result=True, headers=None, get_only=False):
|
||||
def get_request(self, uri, parse_json_result=True, headers=None, get_only=False, fail_on_error=True):
|
||||
'''
|
||||
Perform a GET-like request. Will try POST-as-GET for ACMEv2, with fallback
|
||||
to GET if server replies with a status code of 405.
|
||||
|
@ -626,7 +630,7 @@ class ACMEAccount(object):
|
|||
else:
|
||||
result = content
|
||||
|
||||
if info['status'] >= 400:
|
||||
if fail_on_error and info['status'] >= 400:
|
||||
raise ModuleFailException("ACME request failed: CODE: {0} RESULT: {1}".format(info['status'], result))
|
||||
return result, info
|
||||
|
||||
|
|
|
@ -51,6 +51,8 @@ notes:
|
|||
M(acme_challenge_cert_helper) module to prepare the challenge certificate."
|
||||
- "You can use the M(certificate_complete_chain) module to find the root certificate
|
||||
for the returned fullchain."
|
||||
- "In case you want to debug problems, you might be interested in the M(acme_inspect)
|
||||
module."
|
||||
extends_documentation_fragment:
|
||||
- acme
|
||||
options:
|
||||
|
|
319
lib/ansible/modules/crypto/acme/acme_inspect.py
Normal file
319
lib/ansible/modules/crypto/acme/acme_inspect.py
Normal file
|
@ -0,0 +1,319 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2018 Felix Fontein (@felixfontein)
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: acme_inspect
|
||||
author: "Felix Fontein (@felixfontein)"
|
||||
version_added: "2.8"
|
||||
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/draft-ietf-acme-acme-14),
|
||||
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(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."
|
||||
notes:
|
||||
- "The I(account_uri) option must be specified for properly authenticated
|
||||
ACME v2 requests (except a C(new-account) request)."
|
||||
- "Using the C(ansible) tool, M(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\")"
|
||||
extends_documentation_fragment:
|
||||
- acme
|
||||
options:
|
||||
url:
|
||||
description:
|
||||
- "The URL to send the request to."
|
||||
- "Must be specified if I(method) is not C(directory-only)."
|
||||
type: str
|
||||
method:
|
||||
description:
|
||||
- "The method to use to access the given URL on the ACME server."
|
||||
- "The value C(post) executes an authenticated POST request. The content
|
||||
must be specified in the I(content) option."
|
||||
- "The value C(get) executes an authenticated POST-as-GET request for ACME v2,
|
||||
and a regular GET request for ACME v1."
|
||||
- "The value C(directory-only) only retrieves the directory, without doing
|
||||
a request."
|
||||
choices:
|
||||
- get
|
||||
- post
|
||||
- directory-only
|
||||
default: get
|
||||
content:
|
||||
description:
|
||||
- "An encoded JSON object which will be sent as the content if I(method)
|
||||
is C(post)."
|
||||
- "Required when I(method) is C(post), and not allowed otherwise."
|
||||
type: str
|
||||
fail_on_acme_error:
|
||||
description:
|
||||
- "If I(method) is C(post) or C(get), make the module fail in case an ACME
|
||||
error is returned."
|
||||
type: bool
|
||||
default: yes
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Get directory
|
||||
acme_inspect:
|
||||
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
acme_version: 2
|
||||
method: directory-only
|
||||
register: directory
|
||||
|
||||
- name: Create an account
|
||||
acme_inspect:
|
||||
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
|
||||
acme_inspect:
|
||||
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
|
||||
acme_inspect:
|
||||
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/draft-ietf-acme-acme-16#section-7.3
|
||||
contact:
|
||||
- mailto:me@example.com
|
||||
|
||||
- name: Create certificate order
|
||||
acme_certificate:
|
||||
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
|
||||
acme_inspect:
|
||||
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
|
||||
acme_inspect:
|
||||
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
|
||||
acme_inspect:
|
||||
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
|
||||
acme_inspect:
|
||||
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: |
|
||||
{
|
||||
"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: |
|
||||
{
|
||||
"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\"",
|
||||
"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: string
|
||||
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
|
||||
- ...
|
||||
'''
|
||||
|
||||
from ansible.module_utils.acme import (
|
||||
ModuleFailException, ACMEAccount, set_crypto_backend,
|
||||
)
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_native, to_bytes
|
||||
|
||||
import json
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
account_key_src=dict(type='path', aliases=['account_key']),
|
||||
account_key_content=dict(type='str', no_log=True),
|
||||
account_uri=dict(required=False, type='str'),
|
||||
acme_directory=dict(required=False, default='https://acme-staging.api.letsencrypt.org/directory', type='str'),
|
||||
acme_version=dict(required=False, default=1, choices=[1, 2], type='int'),
|
||||
validate_certs=dict(required=False, default=True, type='bool'),
|
||||
url=dict(required=False, type='str'),
|
||||
method=dict(required=False, type='str', choices=['get', 'post', 'directory-only'], default='get'),
|
||||
content=dict(required=False, type='str'),
|
||||
fail_on_acme_error=dict(required=False, type='bool', default=True),
|
||||
select_crypto_backend=dict(required=False, choices=['auto', 'openssl', 'cryptography'], default='auto', type='str'),
|
||||
),
|
||||
mutually_exclusive=(
|
||||
['account_key_src', 'account_key_content'],
|
||||
),
|
||||
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],
|
||||
),
|
||||
)
|
||||
set_crypto_backend(module)
|
||||
|
||||
if not module.params.get('validate_certs'):
|
||||
module.warn(warning='Disabling certificate validation for communications with ACME endpoint. ' +
|
||||
'This should only be done for testing against a local ACME server for ' +
|
||||
'development purposes, but *never* for production purposes.')
|
||||
|
||||
result = dict()
|
||||
changed = False
|
||||
try:
|
||||
# Get hold of ACMEAccount object (includes directory)
|
||||
account = ACMEAccount(module)
|
||||
method = module.params['method']
|
||||
result['directory'] = account.directory.directory
|
||||
# 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 = account.get_request(url, parse_json_result=False, fail_on_error=False)
|
||||
elif method == 'post':
|
||||
changed = True # only POSTs can change
|
||||
data, info = account.send_signed_request(url, to_bytes(module.params['content']), parse_json_result=False, encode_payload=False)
|
||||
# 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'] = json.loads(data)
|
||||
except Exception as dummy:
|
||||
pass
|
||||
# Fail if error was returned
|
||||
if fail_on_acme_error and info['status'] >= 400:
|
||||
raise ModuleFailException("ACME request failed: CODE: {0} RESULT: {1}".format(info['status'], data))
|
||||
# Done!
|
||||
module.exit_json(changed=changed, **result)
|
||||
except ModuleFailException as e:
|
||||
e.do_fail(module, **result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
2
test/integration/targets/acme_inspect/aliases
Normal file
2
test/integration/targets/acme_inspect/aliases
Normal file
|
@ -0,0 +1,2 @@
|
|||
shippable/cloud/group1
|
||||
cloud/acme
|
2
test/integration/targets/acme_inspect/meta/main.yml
Normal file
2
test/integration/targets/acme_inspect/meta/main.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
dependencies:
|
||||
- setup_acme
|
150
test/integration/targets/acme_inspect/tasks/impl.yml
Normal file
150
test/integration/targets/acme_inspect/tasks/impl.yml
Normal file
|
@ -0,0 +1,150 @@
|
|||
---
|
||||
- name: Generate account key
|
||||
command: openssl ecparam -name prime256v1 -genkey -out {{ output_dir }}/accountkey.pem
|
||||
|
||||
- name: Parse account key (to ease debugging some test failures)
|
||||
command: openssl ec -in {{ output_dir }}/accountkey.pem -noout -text
|
||||
|
||||
- name: Get directory
|
||||
acme_inspect:
|
||||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
acme_version: 2
|
||||
validate_certs: no
|
||||
method: directory-only
|
||||
register: directory
|
||||
- debug: var=directory
|
||||
|
||||
- name: Create an account
|
||||
acme_inspect:
|
||||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
acme_version: 2
|
||||
validate_certs: no
|
||||
account_key_src: "{{ output_dir }}/accountkey.pem"
|
||||
url: "{{ directory.directory.newAccount}}"
|
||||
method: post
|
||||
content: '{"termsOfServiceAgreed":true}'
|
||||
register: account_creation
|
||||
# account_creation.headers.location contains the account URI
|
||||
# if creation was successful
|
||||
- debug: var=account_creation
|
||||
|
||||
- name: Get account information
|
||||
acme_inspect:
|
||||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
acme_version: 2
|
||||
validate_certs: no
|
||||
account_key_src: "{{ output_dir }}/accountkey.pem"
|
||||
account_uri: "{{ account_creation.headers.location }}"
|
||||
url: "{{ account_creation.headers.location }}"
|
||||
method: get
|
||||
register: account_get
|
||||
- debug: var=account_get
|
||||
|
||||
- name: Update account contacts
|
||||
acme_inspect:
|
||||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
acme_version: 2
|
||||
validate_certs: no
|
||||
account_key_src: "{{ output_dir }}/accountkey.pem"
|
||||
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/draft-ietf-acme-acme-16#section-7.3
|
||||
contact:
|
||||
- mailto:me@example.com
|
||||
register: account_update
|
||||
- debug: var=account_update
|
||||
|
||||
- name: Create certificate order
|
||||
acme_inspect:
|
||||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
acme_version: 2
|
||||
validate_certs: no
|
||||
account_key_src: "{{ output_dir }}/accountkey.pem"
|
||||
account_uri: "{{ account_creation.headers.location }}"
|
||||
url: "{{ directory.directory.newOrder }}"
|
||||
method: post
|
||||
content: '{{ create_order | to_json }}'
|
||||
vars:
|
||||
create_order:
|
||||
# For valid values, see
|
||||
# https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.4
|
||||
identifiers:
|
||||
- type: dns
|
||||
value: example.com
|
||||
- type: dns
|
||||
value: example.org
|
||||
register: new_order
|
||||
- debug: var=new_order
|
||||
|
||||
- name: Get order information
|
||||
acme_inspect:
|
||||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
acme_version: 2
|
||||
validate_certs: no
|
||||
account_key_src: "{{ output_dir }}/accountkey.pem"
|
||||
account_uri: "{{ account_creation.headers.location }}"
|
||||
url: "{{ new_order.headers.location }}"
|
||||
method: get
|
||||
register: order
|
||||
- debug: var=order
|
||||
|
||||
- name: Get authzs for order
|
||||
acme_inspect:
|
||||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
acme_version: 2
|
||||
validate_certs: no
|
||||
account_key_src: "{{ output_dir }}/accountkey.pem"
|
||||
account_uri: "{{ account_creation.headers.location }}"
|
||||
url: "{{ item }}"
|
||||
method: get
|
||||
loop: "{{ order.output_json.authorizations }}"
|
||||
register: authz
|
||||
- debug: var=authz
|
||||
|
||||
- name: Get HTTP-01 challenge for authz
|
||||
acme_inspect:
|
||||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
acme_version: 2
|
||||
validate_certs: no
|
||||
account_key_src: "{{ output_dir }}/accountkey.pem"
|
||||
account_uri: "{{ account_creation.headers.location }}"
|
||||
url: "{{ (item.challenges | selectattr('type', 'equalto', 'http-01') | list)[0].url }}"
|
||||
method: get
|
||||
register: http01challenge
|
||||
loop: "{{ authz.results | map(attribute='output_json') | list }}"
|
||||
- debug: var=http01challenge
|
||||
|
||||
- name: Activate HTTP-01 challenge manually
|
||||
acme_inspect:
|
||||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
acme_version: 2
|
||||
validate_certs: no
|
||||
account_key_src: "{{ output_dir }}/accountkey.pem"
|
||||
account_uri: "{{ account_creation.headers.location }}"
|
||||
url: "{{ item.url }}"
|
||||
method: post
|
||||
content: '{}'
|
||||
register: activation
|
||||
loop: "{{ http01challenge.results | map(attribute='output_json') | list }}"
|
||||
- debug: var=activation
|
||||
|
||||
- name: Get HTTP-01 challenge results
|
||||
acme_inspect:
|
||||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
acme_version: 2
|
||||
validate_certs: no
|
||||
account_key_src: "{{ output_dir }}/accountkey.pem"
|
||||
account_uri: "{{ account_creation.headers.location }}"
|
||||
url: "{{ item.url }}"
|
||||
method: get
|
||||
register: validation_result
|
||||
loop: "{{ http01challenge.results | map(attribute='output_json') | list }}"
|
||||
until: "validation_result.output_json.status != 'pending'"
|
||||
retries: 20
|
||||
delay: 1
|
||||
- debug: var=validation_result
|
31
test/integration/targets/acme_inspect/tasks/main.yml
Normal file
31
test/integration/targets/acme_inspect/tasks/main.yml
Normal file
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
- block:
|
||||
- name: Running tests with OpenSSL backend
|
||||
include_tasks: impl.yml
|
||||
vars:
|
||||
select_crypto_backend: openssl
|
||||
|
||||
- import_tasks: ../tests/validate.yml
|
||||
|
||||
# Old 0.9.8 versions have insufficient CLI support for signing with EC keys
|
||||
when: openssl_version.stdout is version('1.0.0', '>=')
|
||||
|
||||
- name: Remove output directory
|
||||
file:
|
||||
path: "{{ output_dir }}"
|
||||
state: absent
|
||||
|
||||
- name: Re-create output directory
|
||||
file:
|
||||
path: "{{ output_dir }}"
|
||||
state: directory
|
||||
|
||||
- block:
|
||||
- name: Running tests with cryptography backend
|
||||
include_tasks: impl.yml
|
||||
vars:
|
||||
select_crypto_backend: cryptography
|
||||
|
||||
- import_tasks: ../tests/validate.yml
|
||||
|
||||
when: cryptography_version.stdout is version('1.5', '>=')
|
131
test/integration/targets/acme_inspect/tests/validate.yml
Normal file
131
test/integration/targets/acme_inspect/tests/validate.yml
Normal file
|
@ -0,0 +1,131 @@
|
|||
---
|
||||
- name: Check directory output
|
||||
assert:
|
||||
that:
|
||||
- directory is not changed
|
||||
- "'directory' in directory"
|
||||
- "'newAccount' in directory.directory"
|
||||
- "'newOrder' in directory.directory"
|
||||
- "'newNonce' in directory.directory"
|
||||
- "'headers' not in directory"
|
||||
- "'output_text' not in directory"
|
||||
- "'output_json' not in directory"
|
||||
|
||||
- name: Check account creation output
|
||||
assert:
|
||||
that:
|
||||
- account_creation is changed
|
||||
- "'directory' in account_creation"
|
||||
- "'headers' in account_creation"
|
||||
- "'output_text' in account_creation"
|
||||
- "'output_json' in account_creation"
|
||||
- account_creation.headers.status == 201
|
||||
- "'location' in account_creation.headers"
|
||||
- account_creation.output_json.status == 'valid'
|
||||
- not account_creation.output_json.contact
|
||||
- account_creation.output_text | from_json == account_creation.output_json
|
||||
|
||||
- name: Check account get output
|
||||
assert:
|
||||
that:
|
||||
- account_get is not changed
|
||||
- "'directory' in account_get"
|
||||
- "'headers' in account_get"
|
||||
- "'output_text' in account_get"
|
||||
- "'output_json' in account_get"
|
||||
- account_get.headers.status == 200
|
||||
- account_get.output_json == account_creation.output_json
|
||||
|
||||
- name: Check account update output
|
||||
assert:
|
||||
that:
|
||||
- account_update is changed
|
||||
- "'directory' in account_update"
|
||||
- "'headers' in account_update"
|
||||
- "'output_text' in account_update"
|
||||
- "'output_json' in account_update"
|
||||
- account_update.output_json.status == 'valid'
|
||||
- account_update.output_json.contact | length == 1
|
||||
- account_update.output_json.contact[0] == 'mailto:me@example.com'
|
||||
|
||||
- name: Check certificate request output
|
||||
assert:
|
||||
that:
|
||||
- new_order is changed
|
||||
- "'directory' in new_order"
|
||||
- "'headers' in new_order"
|
||||
- "'output_text' in new_order"
|
||||
- "'output_json' in new_order"
|
||||
- new_order.output_json.authorizations | length == 2
|
||||
- new_order.output_json.identifiers | length == 2
|
||||
- new_order.output_json.status == 'pending'
|
||||
- "'finalize' in new_order.output_json"
|
||||
|
||||
- name: Check get order output
|
||||
assert:
|
||||
that:
|
||||
- order is not changed
|
||||
- "'directory' in order"
|
||||
- "'headers' in order"
|
||||
- "'output_text' in order"
|
||||
- "'output_json' in order"
|
||||
# The order of identifiers and authorizations is randomized!
|
||||
# - new_order.output_json == order.output_json
|
||||
|
||||
- name: Check get authz output
|
||||
assert:
|
||||
that:
|
||||
- item is not changed
|
||||
- "'directory' in item"
|
||||
- "'headers' in item"
|
||||
- "'output_text' in item"
|
||||
- "'output_json' in item"
|
||||
- item.output_json.challenges | length >= 3
|
||||
- item.output_json.identifier.type == 'dns'
|
||||
- item.output_json.status == 'pending'
|
||||
loop: "{{ authz.results }}"
|
||||
|
||||
- name: Check get challenge output
|
||||
assert:
|
||||
that:
|
||||
- item is not changed
|
||||
- "'directory' in item"
|
||||
- "'headers' in item"
|
||||
- "'output_text' in item"
|
||||
- "'output_json' in item"
|
||||
- item.output_json.status == 'pending'
|
||||
- item.output_json.type == 'http-01'
|
||||
- item.output_json.url == item.invocation.module_args.url
|
||||
- "'token' in item.output_json"
|
||||
loop: "{{ http01challenge.results }}"
|
||||
|
||||
- name: Check challenge activation output
|
||||
assert:
|
||||
that:
|
||||
- item is changed
|
||||
- "'directory' in item"
|
||||
- "'headers' in item"
|
||||
- "'output_text' in item"
|
||||
- "'output_json' in item"
|
||||
- item.output_json.status == 'pending'
|
||||
- item.output_json.type == 'http-01'
|
||||
- item.output_json.url == item.invocation.module_args.url
|
||||
- "'token' in item.output_json"
|
||||
loop: "{{ activation.results }}"
|
||||
|
||||
- name: Check validation result
|
||||
assert:
|
||||
that:
|
||||
- item is not changed
|
||||
- "'directory' in item"
|
||||
- "'headers' in item"
|
||||
- "'output_text' in item"
|
||||
- "'output_json' in item"
|
||||
- item.output_json.status == 'invalid'
|
||||
- item.output_json.type == 'http-01'
|
||||
- item.output_json.url == item.invocation.module_args.url
|
||||
- "'token' in item.output_json"
|
||||
- "'validated' in item.output_json"
|
||||
- "'error' in item.output_json"
|
||||
- item.output_json.error.type == 'urn:ietf:params:acme:error:unauthorized'
|
||||
loop: "{{ validation_result.results }}"
|
Loading…
Reference in a new issue