mirror of https://github.com/ansible/ansible.git
New acme_* integration test using ACME test docker container (#41626)
* Using ACME test container for acme_account integration test. * Removing dependency on setup_openssl. Waiting for controller and Pebble. * More tinkering. * Reducing number of tries. * One more try. * Another try. * Added acme_certificate tests. * Removed double key. * Added tests for acme_certificate_revoke. * Making task names more meaningful (during certificate generation). * Using newer test container which integrates letsencrypt/pebble#137. Adding test for revoking certificate by its private key. * Using new version of Pebble which limits the random auth delay. * Simplifying certificates for revocation tests. * Reworking acme_certificate tests (there are now more, but they are faster). * Test whether account_key_content works. * Preparing TLS-ALPN-01 support. * Using official Ansible image of testing container on quay.io. * Bumping version. * Bumping version of test container to 1.1.0. * Adjusting to new CI group names. * Pass ACME simulator IP as playbook variable. * Let test plugin wait for controller and CA endpoints to become active. * Refactor common setup parts of tests to setup_acme. * _ -> dummy * Moving common obtain-cert.yml to setup_acme.pull/43120/merge
parent
c809500c79
commit
d4c16f51be
@ -1,3 +1,2 @@
|
|||||||
shippable/posix/group1
|
shippable/cloud/group1
|
||||||
destructive
|
cloud/acme
|
||||||
disabled
|
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
- setup_openssl
|
- setup_acme
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
shippable/cloud/group1
|
||||||
|
cloud/acme
|
@ -0,0 +1,2 @@
|
|||||||
|
dependencies:
|
||||||
|
- setup_acme
|
@ -0,0 +1,217 @@
|
|||||||
|
---
|
||||||
|
- block:
|
||||||
|
## SET UP ACCOUNT KEYS ########################################################################
|
||||||
|
- name: Create ECC256 account key
|
||||||
|
command: openssl ecparam -name prime256v1 -genkey -out {{ output_dir }}/account-ec256.pem
|
||||||
|
- name: Create ECC384 account key
|
||||||
|
command: openssl ecparam -name secp384r1 -genkey -out {{ output_dir }}/account-ec384.pem
|
||||||
|
- name: Create RSA-2048 account key
|
||||||
|
command: openssl genrsa -out {{ output_dir }}/account-rsa2048.pem 2048
|
||||||
|
## SET UP ACCOUNTS ############################################################################
|
||||||
|
- name: Make sure ECC256 account hasn't been created yet
|
||||||
|
acme_account:
|
||||||
|
acme_version: 2
|
||||||
|
acme_directory: https://{{ acme_host }}:14000/dir
|
||||||
|
validate_certs: no
|
||||||
|
account_key_src: "{{ output_dir }}/account-ec256.pem"
|
||||||
|
state: absent
|
||||||
|
- name: Create ECC384 account
|
||||||
|
acme_account:
|
||||||
|
acme_version: 2
|
||||||
|
acme_directory: https://{{ acme_host }}:14000/dir
|
||||||
|
validate_certs: no
|
||||||
|
account_key_content: "{{ lookup('file', output_dir ~ '/account-ec384.pem') }}"
|
||||||
|
state: present
|
||||||
|
allow_creation: yes
|
||||||
|
terms_agreed: yes
|
||||||
|
contact:
|
||||||
|
- mailto:example@example.org
|
||||||
|
- mailto:example@example.com
|
||||||
|
- name: Create RSA-2048 account
|
||||||
|
acme_account:
|
||||||
|
acme_version: 2
|
||||||
|
acme_directory: https://{{ acme_host }}:14000/dir
|
||||||
|
validate_certs: no
|
||||||
|
account_key_src: "{{ output_dir }}/account-rsa2048.pem"
|
||||||
|
state: present
|
||||||
|
allow_creation: yes
|
||||||
|
terms_agreed: yes
|
||||||
|
contact: []
|
||||||
|
## OBTAIN CERTIFICATES ########################################################################
|
||||||
|
- name: Obtain cert 1
|
||||||
|
include_tasks: obtain-cert.yml
|
||||||
|
vars:
|
||||||
|
certgen_title: Certificate 1
|
||||||
|
certificate_name: cert-1
|
||||||
|
key_type: rsa
|
||||||
|
rsa_bits: 2048
|
||||||
|
subject_alt_name: "DNS:example.com"
|
||||||
|
subject_alt_name_critical: no
|
||||||
|
account_key: account-ec256
|
||||||
|
challenge: http-01
|
||||||
|
modify_account: yes
|
||||||
|
deactivate_authzs: no
|
||||||
|
force: no
|
||||||
|
remaining_days: 10
|
||||||
|
terms_agreed: yes
|
||||||
|
account_email: "example@example.org"
|
||||||
|
- name: Obtain cert 2
|
||||||
|
include_tasks: obtain-cert.yml
|
||||||
|
vars:
|
||||||
|
certgen_title: Certificate 2
|
||||||
|
certificate_name: cert-2
|
||||||
|
key_type: ec256
|
||||||
|
subject_alt_name: "DNS:*.example.com,DNS:example.com"
|
||||||
|
subject_alt_name_critical: yes
|
||||||
|
account_key: account-ec384
|
||||||
|
challenge: dns-01
|
||||||
|
modify_account: no
|
||||||
|
deactivate_authzs: yes
|
||||||
|
force: no
|
||||||
|
remaining_days: 10
|
||||||
|
terms_agreed: no
|
||||||
|
account_email: ""
|
||||||
|
- name: Obtain cert 3
|
||||||
|
include_tasks: obtain-cert.yml
|
||||||
|
vars:
|
||||||
|
certgen_title: Certificate 3
|
||||||
|
certificate_name: cert-3
|
||||||
|
key_type: ec384
|
||||||
|
subject_alt_name: "DNS:*.example.com,DNS:example.org,DNS:t1.example.com"
|
||||||
|
subject_alt_name_critical: no
|
||||||
|
account_key_content: "{{ lookup('file', output_dir ~ '/account-rsa2048.pem') }}"
|
||||||
|
challenge: dns-01
|
||||||
|
modify_account: no
|
||||||
|
deactivate_authzs: no
|
||||||
|
force: no
|
||||||
|
remaining_days: 10
|
||||||
|
terms_agreed: no
|
||||||
|
account_email: ""
|
||||||
|
- name: Obtain cert 4
|
||||||
|
include_tasks: obtain-cert.yml
|
||||||
|
vars:
|
||||||
|
certgen_title: Certificate 4
|
||||||
|
certificate_name: cert-4
|
||||||
|
key_type: rsa
|
||||||
|
rsa_bits: 2048
|
||||||
|
subject_alt_name: "DNS:example.com,DNS:t1.example.com,DNS:test.t2.example.com,DNS:example.org,DNS:test.example.org"
|
||||||
|
subject_alt_name_critical: no
|
||||||
|
account_key: account-rsa2048
|
||||||
|
challenge: http-01
|
||||||
|
modify_account: no
|
||||||
|
deactivate_authzs: yes
|
||||||
|
force: yes
|
||||||
|
remaining_days: 10
|
||||||
|
terms_agreed: no
|
||||||
|
account_email: ""
|
||||||
|
- name: Obtain cert 5
|
||||||
|
include_tasks: obtain-cert.yml
|
||||||
|
vars:
|
||||||
|
certgen_title: Certificate 5, Iteration 1/4
|
||||||
|
certificate_name: cert-5
|
||||||
|
key_type: ec521
|
||||||
|
subject_alt_name: "DNS:t2.example.com"
|
||||||
|
subject_alt_name_critical: no
|
||||||
|
account_key: account-ec384
|
||||||
|
challenge: http-01
|
||||||
|
modify_account: no
|
||||||
|
deactivate_authzs: yes
|
||||||
|
force: yes
|
||||||
|
remaining_days: 10
|
||||||
|
terms_agreed: no
|
||||||
|
account_email: ""
|
||||||
|
- name: Obtain cert 5 (should not, since already there and valid for more than 10 days)
|
||||||
|
include_tasks: obtain-cert.yml
|
||||||
|
vars:
|
||||||
|
certgen_title: Certificate 5, Iteration 2/4
|
||||||
|
certificate_name: cert-5
|
||||||
|
key_type: ec521
|
||||||
|
subject_alt_name: "DNS:t2.example.com"
|
||||||
|
subject_alt_name_critical: no
|
||||||
|
account_key: account-ec384
|
||||||
|
challenge: http-01
|
||||||
|
modify_account: no
|
||||||
|
deactivate_authzs: yes
|
||||||
|
force: no
|
||||||
|
remaining_days: 10
|
||||||
|
terms_agreed: no
|
||||||
|
account_email: ""
|
||||||
|
- set_fact:
|
||||||
|
cert_5_recreate_1: "{{ challenge_data is changed }}"
|
||||||
|
- name: Obtain cert 5 (should again by less days)
|
||||||
|
include_tasks: obtain-cert.yml
|
||||||
|
vars:
|
||||||
|
certgen_title: Certificate 5, Iteration 3/4
|
||||||
|
certificate_name: cert-5
|
||||||
|
key_type: ec521
|
||||||
|
subject_alt_name: "DNS:t2.example.com"
|
||||||
|
subject_alt_name_critical: no
|
||||||
|
account_key: account-ec384
|
||||||
|
challenge: http-01
|
||||||
|
modify_account: no
|
||||||
|
deactivate_authzs: yes
|
||||||
|
force: yes
|
||||||
|
remaining_days: 1000
|
||||||
|
terms_agreed: no
|
||||||
|
account_email: ""
|
||||||
|
- set_fact:
|
||||||
|
cert_5_recreate_2: "{{ challenge_data is changed }}"
|
||||||
|
- name: Obtain cert 5 (should again by force)
|
||||||
|
include_tasks: obtain-cert.yml
|
||||||
|
vars:
|
||||||
|
certgen_title: Certificate 5, Iteration 4/4
|
||||||
|
certificate_name: cert-5
|
||||||
|
key_type: ec521
|
||||||
|
subject_alt_name: "DNS:t2.example.com"
|
||||||
|
subject_alt_name_critical: no
|
||||||
|
account_key_content: "{{ lookup('file', output_dir ~ '/account-ec384.pem') }}"
|
||||||
|
challenge: http-01
|
||||||
|
modify_account: no
|
||||||
|
deactivate_authzs: yes
|
||||||
|
force: yes
|
||||||
|
remaining_days: 10
|
||||||
|
terms_agreed: no
|
||||||
|
account_email: ""
|
||||||
|
- set_fact:
|
||||||
|
cert_5_recreate_3: "{{ challenge_data is changed }}"
|
||||||
|
## DISSECT CERTIFICATES #######################################################################
|
||||||
|
# Make sure certificates are valid. Root certificate for Pebble equals the chain certificate.
|
||||||
|
- name: Verifying cert 1
|
||||||
|
command: openssl verify -CAfile "{{ output_dir }}/cert-1-chain.pem" "{{ output_dir }}/cert-1.pem"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: cert_1_valid
|
||||||
|
- name: Verifying cert 2
|
||||||
|
command: openssl verify -CAfile "{{ output_dir }}/cert-2-chain.pem" "{{ output_dir }}/cert-2.pem"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: cert_2_valid
|
||||||
|
- name: Verifying cert 3
|
||||||
|
command: openssl verify -CAfile "{{ output_dir }}/cert-3-chain.pem" "{{ output_dir }}/cert-3.pem"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: cert_3_valid
|
||||||
|
- name: Verifying cert 4
|
||||||
|
command: openssl verify -CAfile "{{ output_dir }}/cert-4-chain.pem" "{{ output_dir }}/cert-4.pem"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: cert_4_valid
|
||||||
|
- name: Verifying cert 5
|
||||||
|
command: openssl verify -CAfile "{{ output_dir }}/cert-5-chain.pem" "{{ output_dir }}/cert-5.pem"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: cert_5_valid
|
||||||
|
# Dump certificate info
|
||||||
|
- name: Dumping cert 1
|
||||||
|
command: openssl x509 -in "{{ output_dir }}/cert-1.pem" -noout -text
|
||||||
|
register: cert_1_text
|
||||||
|
- name: Dumping cert 2
|
||||||
|
command: openssl x509 -in "{{ output_dir }}/cert-2.pem" -noout -text
|
||||||
|
register: cert_2_text
|
||||||
|
- name: Dumping cert 3
|
||||||
|
command: openssl x509 -in "{{ output_dir }}/cert-3.pem" -noout -text
|
||||||
|
register: cert_3_text
|
||||||
|
- name: Dumping cert 4
|
||||||
|
command: openssl x509 -in "{{ output_dir }}/cert-4.pem" -noout -text
|
||||||
|
register: cert_4_text
|
||||||
|
- name: Dumping cert 5
|
||||||
|
command: openssl x509 -in "{{ output_dir }}/cert-5.pem" -noout -text
|
||||||
|
register: cert_5_text
|
||||||
|
|
||||||
|
# Old 0.9.8 versions have insufficient CLI support for signing with EC keys
|
||||||
|
when: openssl_version.stdout is version('1.0.0', '>=')
|
@ -0,0 +1 @@
|
|||||||
|
../../setup_acme/tasks/obtain-cert.yml
|
@ -0,0 +1,64 @@
|
|||||||
|
---
|
||||||
|
- name: Check that certificate 1 is valid
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- cert_1_valid is not failed
|
||||||
|
- name: Check that certificate 1 contains correct SANs
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- "'DNS:example.com' in cert_1_text.stdout"
|
||||||
|
|
||||||
|
- name: Check that certificate 2 is valid
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- cert_2_valid is not failed
|
||||||
|
- name: Check that certificate 2 contains correct SANs
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- "'DNS:*.example.com' in cert_2_text.stdout"
|
||||||
|
- "'DNS:example.com' in cert_2_text.stdout"
|
||||||
|
|
||||||
|
- name: Check that certificate 3 is valid
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- cert_3_valid is not failed
|
||||||
|
- name: Check that certificate 3 contains correct SANs
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- "'DNS:*.example.com' in cert_3_text.stdout"
|
||||||
|
- "'DNS:example.org' in cert_3_text.stdout"
|
||||||
|
- "'DNS:t1.example.com' in cert_3_text.stdout"
|
||||||
|
|
||||||
|
- name: Check that certificate 4 is valid
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- cert_4_valid is not failed
|
||||||
|
- name: Check that certificate 4 contains correct SANs
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- "'DNS:example.com' in cert_4_text.stdout"
|
||||||
|
- "'DNS:t1.example.com' in cert_4_text.stdout"
|
||||||
|
- "'DNS:test.t2.example.com' in cert_4_text.stdout"
|
||||||
|
- "'DNS:example.org' in cert_4_text.stdout"
|
||||||
|
- "'DNS:test.example.org' in cert_4_text.stdout"
|
||||||
|
|
||||||
|
- name: Check that certificate 5 is valid
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- cert_5_valid is not failed
|
||||||
|
- name: Check that certificate 5 contains correct SANs
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- "'DNS:t2.example.com' in cert_5_text.stdout"
|
||||||
|
- name: Check that certificate 5 was not recreated on the first try
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- cert_5_recreate_1 == False
|
||||||
|
- name: Check that certificate 5 was recreated on the second try
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- cert_5_recreate_2 == True
|
||||||
|
- name: Check that certificate 5 was recreated on the third try
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- cert_5_recreate_3 == True
|
@ -0,0 +1,2 @@
|
|||||||
|
shippable/cloud/group1
|
||||||
|
cloud/acme
|
@ -0,0 +1,2 @@
|
|||||||
|
dependencies:
|
||||||
|
- setup_acme
|
@ -0,0 +1,90 @@
|
|||||||
|
---
|
||||||
|
- block:
|
||||||
|
## SET UP ACCOUNT KEYS ########################################################################
|
||||||
|
- name: Create ECC256 account key
|
||||||
|
command: openssl ecparam -name prime256v1 -genkey -out {{ output_dir }}/account-ec256.pem
|
||||||
|
- name: Create ECC384 account key
|
||||||
|
command: openssl ecparam -name secp384r1 -genkey -out {{ output_dir }}/account-ec384.pem
|
||||||
|
- name: Create RSA-2048 account key
|
||||||
|
command: openssl genrsa -out {{ output_dir }}/account-rsa2048.pem 2048
|
||||||
|
## CREATE ACCOUNTS AND OBTAIN CERTIFICATES ####################################################
|
||||||
|
- name: Obtain cert 1
|
||||||
|
include_tasks: obtain-cert.yml
|
||||||
|
vars:
|
||||||
|
certgen_title: Certificate 1 for revocation
|
||||||
|
certificate_name: cert-1
|
||||||
|
key_type: rsa
|
||||||
|
rsa_bits: 2048
|
||||||
|
subject_alt_name: "DNS:example.com"
|
||||||
|
subject_alt_name_critical: no
|
||||||
|
account_key_content: "{{ lookup('file', output_dir ~ '/account-ec256.pem') }}"
|
||||||
|
challenge: http-01
|
||||||
|
modify_account: yes
|
||||||
|
deactivate_authzs: no
|
||||||
|
force: no
|
||||||
|
remaining_days: 10
|
||||||
|
terms_agreed: yes
|
||||||
|
account_email: "example@example.org"
|
||||||
|
- name: Obtain cert 2
|
||||||
|
include_tasks: obtain-cert.yml
|
||||||
|
vars:
|
||||||
|
certgen_title: Certificate 2 for revocation
|
||||||
|
certificate_name: cert-2
|
||||||
|
key_type: ec256
|
||||||
|
subject_alt_name: "DNS:*.example.com"
|
||||||
|
subject_alt_name_critical: yes
|
||||||
|
account_key: account-ec384
|
||||||
|
challenge: dns-01
|
||||||
|
modify_account: yes
|
||||||
|
deactivate_authzs: yes
|
||||||
|
force: no
|
||||||
|
remaining_days: 10
|
||||||
|
terms_agreed: yes
|
||||||
|
account_email: "example@example.org"
|
||||||
|
- name: Obtain cert 3
|
||||||
|
include_tasks: obtain-cert.yml
|
||||||
|
vars:
|
||||||
|
certgen_title: Certificate 3 for revocation
|
||||||
|
certificate_name: cert-3
|
||||||
|
key_type: ec384
|
||||||
|
subject_alt_name: "DNS:t1.example.com"
|
||||||
|
subject_alt_name_critical: no
|
||||||
|
account_key: account-rsa2048
|
||||||
|
challenge: dns-01
|
||||||
|
modify_account: yes
|
||||||
|
deactivate_authzs: no
|
||||||
|
force: no
|
||||||
|
remaining_days: 10
|
||||||
|
terms_agreed: yes
|
||||||
|
account_email: "example@example.org"
|
||||||
|
## REVOKE CERTIFICATES ########################################################################
|
||||||
|
- name: Revoke certificate 1 via account key
|
||||||
|
acme_certificate_revoke:
|
||||||
|
account_key_src: "{{ output_dir }}/account-ec256.pem"
|
||||||
|
certificate: "{{ output_dir }}/cert-1.pem"
|
||||||
|
acme_version: 2
|
||||||
|
acme_directory: https://{{ acme_host }}:14000/dir
|
||||||
|
validate_certs: no
|
||||||
|
ignore_errors: yes
|
||||||
|
register: cert_1_revoke
|
||||||
|
- name: Revoke certificate 2 via certificate private key
|
||||||
|
acme_certificate_revoke:
|
||||||
|
private_key_src: "{{ output_dir }}/cert-2.key"
|
||||||
|
certificate: "{{ output_dir }}/cert-2.pem"
|
||||||
|
acme_version: 2
|
||||||
|
acme_directory: https://{{ acme_host }}:14000/dir
|
||||||
|
validate_certs: no
|
||||||
|
ignore_errors: yes
|
||||||
|
register: cert_2_revoke
|
||||||
|
- name: Revoke certificate 3 via account key (fullchain)
|
||||||
|
acme_certificate_revoke:
|
||||||
|
account_key_content: "{{ lookup('file', output_dir ~ '/account-rsa2048.pem') }}"
|
||||||
|
certificate: "{{ output_dir }}/cert-3-fullchain.pem"
|
||||||
|
acme_version: 2
|
||||||
|
acme_directory: https://{{ acme_host }}:14000/dir
|
||||||
|
validate_certs: no
|
||||||
|
ignore_errors: yes
|
||||||
|
register: cert_3_revoke
|
||||||
|
|
||||||
|
# Old 0.9.8 versions have insufficient CLI support for signing with EC keys
|
||||||
|
when: openssl_version.stdout is version('1.0.0', '>=')
|
@ -0,0 +1 @@
|
|||||||
|
../../setup_acme/tasks/obtain-cert.yml
|
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
- name: Check that certificate 1 was revoked
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- cert_1_revoke is changed
|
||||||
|
- cert_1_revoke is not failed
|
||||||
|
- name: Check that certificate 2 was revoked
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- cert_2_revoke is changed
|
||||||
|
- cert_2_revoke is not failed
|
||||||
|
- name: Check that certificate 3 was revoked
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- cert_3_revoke is changed
|
||||||
|
- cert_3_revoke is not failed
|
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
- name: register openssl version
|
||||||
|
shell: "openssl version | cut -d' ' -f2"
|
||||||
|
register: openssl_version
|
||||||
|
|
||||||
|
- name: register cryptography version
|
||||||
|
command: python -c 'import cryptography; print(cryptography.__version__)'
|
||||||
|
register: cryptography_version
|
||||||
|
|
||||||
|
- debug: msg="ACME test container IP is {{ acme_host }}; OpenSSL version is {{ openssl_version.stdout }}; cryptography version is {{ cryptography_version.stdout }}"
|
@ -0,0 +1,153 @@
|
|||||||
|
---
|
||||||
|
## PRIVATE KEY ################################################################################
|
||||||
|
- name: ({{ certgen_title }}) Create cert private key (RSA)
|
||||||
|
command: "openssl genrsa -out {{ output_dir }}/{{ certificate_name }}.key {{ rsa_bits if key_type == 'rsa' else 2048 }}"
|
||||||
|
when: "key_type == 'rsa'"
|
||||||
|
- name: ({{ certgen_title }}) Create cert private key (ECC 256)
|
||||||
|
command: openssl ecparam -name prime256v1 -genkey -out {{ output_dir }}/{{ certificate_name }}.key
|
||||||
|
when: "key_type == 'ec256'"
|
||||||
|
- name: ({{ certgen_title }}) Create cert private key (ECC 384)
|
||||||
|
command: openssl ecparam -name secp384r1 -genkey -out {{ output_dir }}/{{ certificate_name }}.key
|
||||||
|
when: "key_type == 'ec384'"
|
||||||
|
- name: ({{ certgen_title }}) Create cert private key (ECC 512)
|
||||||
|
command: openssl ecparam -name secp521r1 -genkey -out {{ output_dir }}/{{ certificate_name }}.key
|
||||||
|
when: "key_type == 'ec521'"
|
||||||
|
## CSR ########################################################################################
|
||||||
|
- name: ({{ certgen_title }}) Create cert CSR
|
||||||
|
openssl_csr:
|
||||||
|
path: "{{ output_dir }}/{{ certificate_name }}.csr"
|
||||||
|
privatekey_path: "{{ output_dir }}/{{ certificate_name }}.key"
|
||||||
|
subject_alt_name: "{{ subject_alt_name }}"
|
||||||
|
subject_alt_name_critical: "{{ subject_alt_name_critical }}"
|
||||||
|
## ACME STEP 1 ################################################################################
|
||||||
|
- name: ({{ certgen_title }}) Obtain cert, step 1
|
||||||
|
acme_certificate:
|
||||||
|
acme_version: 2
|
||||||
|
acme_directory: https://{{ acme_host }}:14000/dir
|
||||||
|
validate_certs: no
|
||||||
|
account_key: "{{ output_dir }}/{{ account_key }}.pem"
|
||||||
|
modify_account: "{{ modify_account }}"
|
||||||
|
csr: "{{ output_dir }}/{{ certificate_name }}.csr"
|
||||||
|
dest: "{{ output_dir }}/{{ certificate_name }}.pem"
|
||||||
|
fullchain_dest: "{{ output_dir }}/{{ certificate_name }}-fullchain.pem"
|
||||||
|
chain_dest: "{{ output_dir }}/{{ certificate_name }}-chain.pem"
|
||||||
|
challenge: "{{ challenge }}"
|
||||||
|
deactivate_authzs: "{{ deactivate_authzs }}"
|
||||||
|
force: "{{ force }}"
|
||||||
|
remaining_days: "{{ remaining_days }}"
|
||||||
|
terms_agreed: "{{ terms_agreed }}"
|
||||||
|
account_email: "{{ account_email }}"
|
||||||
|
register: challenge_data
|
||||||
|
when: account_key_content is not defined
|
||||||
|
- name: ({{ certgen_title }}) Obtain cert, step 1 (using account key data)
|
||||||
|
acme_certificate:
|
||||||
|
acme_version: 2
|
||||||
|
acme_directory: https://{{ acme_host }}:14000/dir
|
||||||
|
validate_certs: no
|
||||||
|
account_key_content: "{{ account_key_content }}"
|
||||||
|
modify_account: "{{ modify_account }}"
|
||||||
|
csr: "{{ output_dir }}/{{ certificate_name }}.csr"
|
||||||
|
dest: "{{ output_dir }}/{{ certificate_name }}.pem"
|
||||||
|
fullchain_dest: "{{ output_dir }}/{{ certificate_name }}-fullchain.pem"
|
||||||
|
chain_dest: "{{ output_dir }}/{{ certificate_name }}-chain.pem"
|
||||||
|
challenge: "{{ challenge }}"
|
||||||
|
deactivate_authzs: "{{ deactivate_authzs }}"
|
||||||
|
force: "{{ force }}"
|
||||||
|
remaining_days: "{{ remaining_days }}"
|
||||||
|
terms_agreed: "{{ terms_agreed }}"
|
||||||
|
account_email: "{{ account_email }}"
|
||||||
|
register: challenge_data_content
|
||||||
|
when: account_key_content is defined
|
||||||
|
- name: ({{ certgen_title }}) Copy challenge data (when using account key data)
|
||||||
|
set_fact:
|
||||||
|
challenge_data: "{{ challenge_data_content }}"
|
||||||
|
when: account_key_content is defined
|
||||||
|
- name: ({{ certgen_title }}) Print challenge data
|
||||||
|
debug:
|
||||||
|
var: challenge_data
|
||||||
|
- name: ({{ certgen_title }}) Create HTTP challenges
|
||||||
|
uri:
|
||||||
|
url: "http://{{ acme_host }}:5000/http/{{ item.key }}/{{ item.value['http-01'].resource[('.well-known/acme-challenge/'|length):] }}"
|
||||||
|
method: PUT
|
||||||
|
body_format: raw
|
||||||
|
body: "{{ item.value['http-01'].resource_value }}"
|
||||||
|
headers:
|
||||||
|
content-type: "application/octet-stream"
|
||||||
|
with_dict: "{{ challenge_data.challenge_data }}"
|
||||||
|
when: "challenge_data is changed and challenge == 'http-01'"
|
||||||
|
- name: ({{ certgen_title }}) Create DNS challenges
|
||||||
|
uri:
|
||||||
|
url: "http://{{ acme_host }}:5000/dns/{{ item.key }}"
|
||||||
|
method: PUT
|
||||||
|
body_format: json
|
||||||
|
body: "{{ item.value }}"
|
||||||
|
with_dict: "{{ challenge_data.challenge_data_dns }}"
|
||||||
|
when: "challenge_data is changed and challenge == 'dns-01'"
|
||||||
|
- name: ({{ certgen_title }}) Create TLS ALPN challenges
|
||||||
|
uri:
|
||||||
|
url: "http://{{ acme_host }}:5000/tls-alpn/{{ item.value['tls-alpn-01'].resource }}"
|
||||||
|
method: PUT
|
||||||
|
body_format: raw
|
||||||
|
body: "{{ item.value['tls-alpn-01'].resource_value | b64encode }}"
|
||||||
|
headers:
|
||||||
|
content-type: "application/octet-stream"
|
||||||
|
with_dict: "{{ challenge_data.challenge_data }}"
|
||||||
|
when: "challenge_data is changed and challenge == 'tls-alpn-01'"
|
||||||
|
## ACME STEP 2 ################################################################################
|
||||||
|
- name: ({{ certgen_title }}) Obtain cert, step 2
|
||||||
|
acme_certificate:
|
||||||
|
acme_version: 2
|
||||||
|
acme_directory: https://{{ acme_host }}:14000/dir
|
||||||
|
validate_certs: no
|
||||||
|
account_key: "{{ output_dir }}/{{ account_key }}.pem"
|
||||||
|
modify_account: "{{ modify_account }}"
|
||||||
|
csr: "{{ output_dir }}/{{ certificate_name }}.csr"
|
||||||
|
dest: "{{ output_dir }}/{{ certificate_name }}.pem"
|
||||||
|
fullchain_dest: "{{ output_dir }}/{{ certificate_name }}-fullchain.pem"
|
||||||
|
chain_dest: "{{ output_dir }}/{{ certificate_name }}-chain.pem"
|
||||||
|
challenge: "{{ challenge }}"
|
||||||
|
deactivate_authzs: "{{ deactivate_authzs }}"
|
||||||
|
force: "{{ force }}"
|
||||||
|
remaining_days: "{{ remaining_days }}"
|
||||||
|
terms_agreed: "{{ terms_agreed }}"
|
||||||
|
account_email: "{{ account_email }}"
|
||||||
|
data: "{{ challenge_data }}"
|
||||||
|
when: challenge_data is changed and account_key_content is not defined
|
||||||
|
- name: ({{ certgen_title }}) Obtain cert, step 2 (using account key data)
|
||||||
|
acme_certificate:
|
||||||
|
acme_version: 2
|
||||||
|
acme_directory: https://{{ acme_host }}:14000/dir
|
||||||
|
validate_certs: no
|
||||||
|
account_key_content: "{{ account_key_content }}"
|
||||||
|
modify_account: "{{ modify_account }}"
|
||||||
|
csr: "{{ output_dir }}/{{ certificate_name }}.csr"
|
||||||
|
dest: "{{ output_dir }}/{{ certificate_name }}.pem"
|
||||||
|
fullchain_dest: "{{ output_dir }}/{{ certificate_name }}-fullchain.pem"
|
||||||
|
chain_dest: "{{ output_dir }}/{{ certificate_name }}-chain.pem"
|
||||||
|
challenge: "{{ challenge }}"
|
||||||
|
deactivate_authzs: "{{ deactivate_authzs }}"
|
||||||
|
force: "{{ force }}"
|
||||||
|
remaining_days: "{{ remaining_days }}"
|
||||||
|
terms_agreed: "{{ terms_agreed }}"
|
||||||
|
account_email: "{{ account_email }}"
|
||||||
|
data: "{{ challenge_data }}"
|
||||||
|
when: challenge_data is changed and account_key_content is defined
|
||||||
|
- name: ({{ certgen_title }}) Deleting HTTP challenges
|
||||||
|
uri:
|
||||||
|
url: "http://{{ acme_host }}:5000/http/{{ item.key }}/{{ item.value['http-01'].resource[('.well-known/acme-challenge/'|length):] }}"
|
||||||
|
method: DELETE
|
||||||
|
with_dict: "{{ challenge_data.challenge_data }}"
|
||||||
|
when: "challenge_data is changed and challenge == 'http-01'"
|
||||||
|
- name: ({{ certgen_title }}) Deleting DNS challenges
|
||||||
|
uri:
|
||||||
|
url: "http://{{ acme_host }}:5000/dns/{{ item.key }}"
|
||||||
|
method: DELETE
|
||||||
|
with_dict: "{{ challenge_data.challenge_data_dns }}"
|
||||||
|
when: "challenge_data is changed and challenge == 'dns-01'"
|
||||||
|
- name: ({{ certgen_title }}) Deleting TLS ALPN challenges
|
||||||
|
uri:
|
||||||
|
url: "http://{{ acme_host }}:5000/tls-alpn/{{ item.value['tls-alpn-01'].resource }}"
|
||||||
|
method: DELETE
|
||||||
|
with_dict: "{{ challenge_data.challenge_data }}"
|
||||||
|
when: "challenge_data is changed and challenge == 'tls-alpn-01'"
|
||||||
|
###############################################################################################
|
@ -0,0 +1,193 @@
|
|||||||
|
"""ACME plugin for integration tests."""
|
||||||
|
from __future__ import absolute_import, print_function
|
||||||
|
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
from lib.cloud import (
|
||||||
|
CloudProvider,
|
||||||
|
CloudEnvironment,
|
||||||
|
)
|
||||||
|
|
||||||
|
from lib.util import (
|
||||||
|
find_executable,
|
||||||
|
display,
|
||||||
|
ApplicationError,
|
||||||
|
SubprocessError,
|
||||||
|
)
|
||||||
|
|
||||||
|
from lib.http import (
|
||||||
|
HttpClient,
|
||||||
|
)
|
||||||
|
|
||||||
|
from lib.docker_util import (
|
||||||
|
docker_run,
|
||||||
|
docker_rm,
|
||||||
|
docker_inspect,
|
||||||
|
docker_pull,
|
||||||
|
get_docker_container_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# noinspection PyPep8Naming
|
||||||
|
import ConfigParser as configparser
|
||||||
|
except ImportError:
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
import configparser
|
||||||
|
|
||||||
|
|
||||||
|
class ACMEProvider(CloudProvider):
|
||||||
|
"""ACME plugin. Sets up cloud resources for tests."""
|
||||||
|
DOCKER_SIMULATOR_NAME = 'acme-simulator'
|
||||||
|
|
||||||
|
def __init__(self, args):
|
||||||
|
"""
|
||||||
|
:type args: TestConfig
|
||||||
|
"""
|
||||||
|
super(ACMEProvider, self).__init__(args, config_extension='.ini')
|
||||||
|
|
||||||
|
# The simulator must be pinned to a specific version to guarantee CI passes with the version used.
|
||||||
|
if os.environ.get('ANSIBLE_ACME_CONTAINER'):
|
||||||
|
self.image = os.environ.get('ANSIBLE_ACME_CONTAINER')
|
||||||
|
else:
|
||||||
|
self.image = 'quay.io/ansible/acme-test-container:1.1.0'
|
||||||
|
self.container_name = ''
|
||||||
|
|
||||||
|
def _wait_for_service(self, protocol, acme_host, port, local_part, name):
|
||||||
|
"""Wait for an endpoint to accept connections."""
|
||||||
|
if self.args.explain:
|
||||||
|
return
|
||||||
|
|
||||||
|
client = HttpClient(self.args, always=True, insecure=True)
|
||||||
|
endpoint = '%s://%s:%d/%s' % (protocol, acme_host, port, local_part)
|
||||||
|
|
||||||
|
for dummy in range(1, 30):
|
||||||
|
display.info('Waiting for %s: %s' % (name, endpoint), verbosity=1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
client.get(endpoint)
|
||||||
|
return
|
||||||
|
except SubprocessError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
raise ApplicationError('Timeout waiting for %s.' % name)
|
||||||
|
|
||||||
|
def filter(self, targets, exclude):
|
||||||
|
"""Filter out the cloud tests when the necessary config and resources are not available.
|
||||||
|
:type targets: tuple[TestTarget]
|
||||||
|
:type exclude: list[str]
|
||||||
|
"""
|
||||||
|
docker = find_executable('docker', required=False)
|
||||||
|
|
||||||
|
if docker:
|
||||||
|
return
|
||||||
|
|
||||||
|
skip = 'cloud/%s/' % self.platform
|
||||||
|
skipped = [target.name for target in targets if skip in target.aliases]
|
||||||
|
|
||||||
|
if skipped:
|
||||||
|
exclude.append(skip)
|
||||||
|
display.warning('Excluding tests marked "%s" which require the "docker" command: %s'
|
||||||
|
% (skip.rstrip('/'), ', '.join(skipped)))
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
"""Setup the cloud resource before delegation and register a cleanup callback."""
|
||||||
|
super(ACMEProvider, self).setup()
|
||||||
|
|
||||||
|
if self._use_static_config():
|
||||||
|
self._setup_static()
|
||||||
|
else:
|
||||||
|
self._setup_dynamic()
|
||||||
|
|
||||||
|
def get_docker_run_options(self):
|
||||||
|
"""Get any additional options needed when delegating tests to a docker container.
|
||||||
|
:rtype: list[str]
|
||||||
|
"""
|
||||||
|
if self.managed:
|
||||||
|
return ['--link', self.DOCKER_SIMULATOR_NAME]
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
"""Clean up the cloud resource and any temporary configuration files after tests complete."""
|
||||||
|
if self.container_name:
|
||||||
|
docker_rm(self.args, self.container_name)
|
||||||
|
|
||||||
|
super(ACMEProvider, self).cleanup()
|
||||||
|
|
||||||
|
def _setup_dynamic(self):
|
||||||
|
"""Create a ACME test container using docker."""
|
||||||
|
container_id = get_docker_container_id()
|
||||||
|
|
||||||
|
if container_id:
|
||||||
|
display.info('Running in docker container: %s' % container_id, verbosity=1)
|
||||||
|
|
||||||
|
self.container_name = self.DOCKER_SIMULATOR_NAME
|
||||||
|
|
||||||
|
results = docker_inspect(self.args, self.container_name)
|
||||||
|
|
||||||
|
if results and not results[0].get('State', {}).get('Running'):
|
||||||
|
docker_rm(self.args, self.container_name)
|
||||||
|
results = []
|
||||||
|
|
||||||
|
if results:
|
||||||
|
display.info('Using the existing ACME docker test container.', verbosity=1)
|
||||||
|
else:
|
||||||
|
display.info('Starting a new ACME docker test container.', verbosity=1)
|
||||||
|
|
||||||
|
if not self.args.docker and not container_id:
|
||||||
|
# publish the simulator ports when not running inside docker
|
||||||
|
publish_ports = [
|
||||||
|
'-p', '5000:5000', # control port for flask app in container
|
||||||
|
'-p', '14000:14000', # Pebble ACME CA
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
publish_ports = []
|
||||||
|
|
||||||
|
if not os.environ.get('ANSIBLE_ACME_CONTAINER'):
|
||||||
|
docker_pull(self.args, self.image)
|
||||||
|
|
||||||
|
docker_run(
|
||||||
|
self.args,
|
||||||
|
self.image,
|
||||||
|
['-d', '--name', self.container_name] + publish_ports,
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.args.docker:
|
||||||
|
acme_host = self.DOCKER_SIMULATOR_NAME
|
||||||
|
acme_host_ip = self._get_simulator_address()
|
||||||
|
elif container_id:
|
||||||
|
acme_host = self._get_simulator_address()
|
||||||
|
acme_host_ip = acme_host
|
||||||
|
display.info('Found ACME test container address: %s' % acme_host, verbosity=1)
|
||||||
|
else:
|
||||||
|
acme_host = 'localhost'
|
||||||
|
acme_host_ip = acme_host
|
||||||
|
|
||||||
|
self._set_cloud_config('acme_host', acme_host)
|
||||||
|
|
||||||
|
self._wait_for_service('http', acme_host_ip, 5000, '', 'ACME controller')
|
||||||
|
self._wait_for_service('https', acme_host_ip, 14000, 'dir', 'ACME CA endpoint')
|
||||||
|
|
||||||
|
def _get_simulator_address(self):
|
||||||
|
results = docker_inspect(self.args, self.container_name)
|
||||||
|
ipaddress = results[0]['NetworkSettings']['IPAddress']
|
||||||
|
return ipaddress
|
||||||
|
|
||||||
|
def _setup_static(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
class ACMEEnvironment(CloudEnvironment):
|
||||||
|
"""ACME environment plugin. Updates integration test environment after delegation."""
|
||||||
|
def configure_environment(self, env, cmd):
|
||||||
|
"""
|
||||||
|
:type env: dict[str, str]
|
||||||
|
:type cmd: list[str]
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Send the container IP down to the integration test(s)
|
||||||
|
cmd.append('-e')
|
||||||
|
cmd.append('acme_host=%s' % self._get_cloud_config('acme_host'))
|
Loading…
Reference in New Issue