mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
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.
This commit is contained in:
parent
c809500c79
commit
d4c16f51be
17 changed files with 863 additions and 48 deletions
|
@ -1,3 +1,2 @@
|
|||
shippable/posix/group1
|
||||
destructive
|
||||
disabled
|
||||
shippable/cloud/group1
|
||||
cloud/acme
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
dependencies:
|
||||
- setup_openssl
|
||||
- setup_acme
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
---
|
||||
- block:
|
||||
- debug: var=openssl_version.stdout
|
||||
|
||||
- name: Generate account key
|
||||
command: openssl ecparam -name prime256v1 -genkey -out {{ output_dir }}/accountkey.pem
|
||||
|
||||
|
@ -12,7 +10,8 @@
|
|||
acme_account:
|
||||
account_key_src: "{{ output_dir }}/accountkey.pem"
|
||||
acme_version: 2
|
||||
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
validate_certs: no
|
||||
state: present
|
||||
allow_creation: no
|
||||
ignore_errors: yes
|
||||
|
@ -22,7 +21,8 @@
|
|||
acme_account:
|
||||
account_key_src: "{{ output_dir }}/accountkey.pem"
|
||||
acme_version: 2
|
||||
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
validate_certs: no
|
||||
state: present
|
||||
allow_creation: yes
|
||||
terms_agreed: yes
|
||||
|
@ -32,9 +32,10 @@
|
|||
|
||||
- name: Change email address
|
||||
acme_account:
|
||||
account_key_src: "{{ output_dir }}/accountkey.pem"
|
||||
account_key_content: "{{ lookup('file', output_dir ~ '/accountkey.pem') }}"
|
||||
acme_version: 2
|
||||
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
validate_certs: no
|
||||
state: present
|
||||
# allow_creation: no
|
||||
contact:
|
||||
|
@ -45,7 +46,8 @@
|
|||
acme_account:
|
||||
account_key_src: "{{ output_dir }}/accountkey.pem"
|
||||
acme_version: 2
|
||||
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
validate_certs: no
|
||||
state: present
|
||||
# allow_creation: no
|
||||
contact:
|
||||
|
@ -58,52 +60,91 @@
|
|||
- name: Parse account key (to ease debugging some test failures)
|
||||
command: openssl ec -in {{ output_dir }}/accountkey2.pem -noout -text
|
||||
|
||||
- name: Change account key
|
||||
acme_account:
|
||||
account_key_src: "{{ output_dir }}/accountkey.pem"
|
||||
acme_version: 2
|
||||
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
new_account_key_src: "{{ output_dir }}/accountkey2.pem"
|
||||
state: changed_key
|
||||
contact:
|
||||
- mailto:example@example.com
|
||||
register: account_change_key
|
||||
# Note that pebble has no change key endpoint implemented yet!
|
||||
# When it has (and the container was updated), uncomment the
|
||||
# uncomment the following tests, and delete the ones below the
|
||||
# out-commented ones.
|
||||
|
||||
# - name: Change account key
|
||||
# acme_account:
|
||||
# account_key_src: "{{ output_dir }}/accountkey.pem"
|
||||
# acme_version: 2
|
||||
# acme_directory: https://{{ acme_host }}:14000/dir
|
||||
# validate_certs: no
|
||||
# new_account_key_src: "{{ output_dir }}/accountkey2.pem"
|
||||
# state: changed_key
|
||||
# contact:
|
||||
# - mailto:example@example.com
|
||||
# register: account_change_key
|
||||
|
||||
# - name: Deactivate account
|
||||
# acme_account:
|
||||
# account_key_src: "{{ output_dir }}/accountkey2.pem"
|
||||
# acme_version: 2
|
||||
# acme_directory: https://{{ acme_host }}:14000/dir
|
||||
# validate_certs: no
|
||||
# state: absent
|
||||
# register: account_deactivate
|
||||
|
||||
# - name: Deactivate account (idempotent)
|
||||
# acme_account:
|
||||
# account_key_src: "{{ output_dir }}/accountkey2.pem"
|
||||
# acme_version: 2
|
||||
# acme_directory: https://{{ acme_host }}:14000/dir
|
||||
# validate_certs: no
|
||||
# state: absent
|
||||
# register: account_deactivate_idempotent
|
||||
|
||||
# - name: Do not try to create account II
|
||||
# acme_account:
|
||||
# account_key_src: "{{ output_dir }}/accountkey2.pem"
|
||||
# acme_version: 2
|
||||
# acme_directory: https://{{ acme_host }}:14000/dir
|
||||
# validate_certs: no
|
||||
# state: present
|
||||
# allow_creation: no
|
||||
# ignore_errors: yes
|
||||
# register: account_not_created_2
|
||||
|
||||
# - name: Do not try to create account III
|
||||
# acme_account:
|
||||
# account_key_src: "{{ output_dir }}/accountkey.pem"
|
||||
# acme_version: 2
|
||||
# acme_directory: https://{{ acme_host }}:14000/dir
|
||||
# validate_certs: no
|
||||
# state: present
|
||||
# allow_creation: no
|
||||
# ignore_errors: yes
|
||||
# register: account_not_created_3
|
||||
|
||||
- name: Deactivate account
|
||||
acme_account:
|
||||
account_key_src: "{{ output_dir }}/accountkey2.pem"
|
||||
account_key_src: "{{ output_dir }}/accountkey.pem"
|
||||
acme_version: 2
|
||||
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
validate_certs: no
|
||||
state: absent
|
||||
register: account_deactivate
|
||||
|
||||
- name: Deactivate account (idempotent)
|
||||
acme_account:
|
||||
account_key_src: "{{ output_dir }}/accountkey2.pem"
|
||||
account_key_src: "{{ output_dir }}/accountkey.pem"
|
||||
acme_version: 2
|
||||
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
validate_certs: no
|
||||
state: absent
|
||||
register: account_deactivate_idempotent
|
||||
|
||||
- name: Do not try to create account II
|
||||
acme_account:
|
||||
account_key_src: "{{ output_dir }}/accountkey2.pem"
|
||||
account_key_src: "{{ output_dir }}/accountkey.pem"
|
||||
acme_version: 2
|
||||
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
validate_certs: no
|
||||
state: present
|
||||
allow_creation: no
|
||||
ignore_errors: yes
|
||||
register: account_not_created_2
|
||||
|
||||
- name: Do not try to create account III
|
||||
acme_account:
|
||||
account_key_src: "{{ output_dir }}/accountkey.pem"
|
||||
acme_version: 2
|
||||
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
state: present
|
||||
allow_creation: no
|
||||
ignore_errors: yes
|
||||
register: account_not_created_3
|
||||
|
||||
# Old 0.9.8 versions have insufficient CLI support for signing with EC keys
|
||||
when: openssl_version.stdout is version('1.0.0', '>=')
|
||||
|
|
|
@ -22,11 +22,38 @@
|
|||
- account_modified_idempotent is not changed
|
||||
- account_modified_idempotent.account_uri is not none
|
||||
|
||||
- name: Validate that the account key was changed
|
||||
assert:
|
||||
that:
|
||||
- account_change_key is changed
|
||||
- account_change_key.account_uri is not none
|
||||
# Note that pebble has no change key endpoint implemented yet!
|
||||
# When it has (and the container was updated), uncomment the
|
||||
# following validations, and delete the ones below the
|
||||
# out-commented ones:
|
||||
|
||||
#- name: Validate that the account key was changed
|
||||
# assert:
|
||||
# that:
|
||||
# - account_change_key is changed
|
||||
# - account_change_key.account_uri is not none
|
||||
#
|
||||
#- name: Validate that the account was deactivated
|
||||
# assert:
|
||||
# that:
|
||||
# - account_deactivate is changed
|
||||
# - account_deactivate.account_uri is not none
|
||||
#
|
||||
#- name: Validate that the account was really deactivated (idempotency)
|
||||
# assert:
|
||||
# that:
|
||||
# - account_deactivate_idempotent is not changed
|
||||
# - account_deactivate_idempotent.account_uri is not none
|
||||
#
|
||||
#- name: Validate that the account is gone (new account key)
|
||||
# assert:
|
||||
# that:
|
||||
# - account_not_created_2 is failed
|
||||
#
|
||||
#- name: Validate that the account is gone (old account key)
|
||||
# assert:
|
||||
# that:
|
||||
# - account_not_created_3 is failed
|
||||
|
||||
- name: Validate that the account was deactivated
|
||||
assert:
|
||||
|
@ -40,12 +67,7 @@
|
|||
- account_deactivate_idempotent is not changed
|
||||
- account_deactivate_idempotent.account_uri is not none
|
||||
|
||||
- name: Validate that the account is gone (new account key)
|
||||
- name: Validate that the account is gone
|
||||
assert:
|
||||
that:
|
||||
- account_not_created_2 is failed
|
||||
|
||||
- name: Validate that the account is gone (old account key)
|
||||
assert:
|
||||
that:
|
||||
- account_not_created_3 is failed
|
||||
|
|
2
test/integration/targets/acme_certificate/aliases
Normal file
2
test/integration/targets/acme_certificate/aliases
Normal file
|
@ -0,0 +1,2 @@
|
|||
shippable/cloud/group1
|
||||
cloud/acme
|
2
test/integration/targets/acme_certificate/meta/main.yml
Normal file
2
test/integration/targets/acme_certificate/meta/main.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
dependencies:
|
||||
- setup_acme
|
217
test/integration/targets/acme_certificate/tasks/main.yml
Normal file
217
test/integration/targets/acme_certificate/tasks/main.yml
Normal file
|
@ -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', '>=')
|
1
test/integration/targets/acme_certificate/tasks/obtain-cert.yml
Symbolic link
1
test/integration/targets/acme_certificate/tasks/obtain-cert.yml
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../setup_acme/tasks/obtain-cert.yml
|
64
test/integration/targets/acme_certificate/tests/validate.yml
Normal file
64
test/integration/targets/acme_certificate/tests/validate.yml
Normal file
|
@ -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
|
2
test/integration/targets/acme_certificate_revoke/aliases
Normal file
2
test/integration/targets/acme_certificate_revoke/aliases
Normal file
|
@ -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
|
10
test/integration/targets/setup_acme/tasks/main.yml
Normal file
10
test/integration/targets/setup_acme/tasks/main.yml
Normal file
|
@ -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 }}"
|
153
test/integration/targets/setup_acme/tasks/obtain-cert.yml
Normal file
153
test/integration/targets/setup_acme/tasks/obtain-cert.yml
Normal file
|
@ -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'"
|
||||
###############################################################################################
|
193
test/runner/lib/cloud/acme.py
Normal file
193
test/runner/lib/cloud/acme.py
Normal file
|
@ -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 a new issue