summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFelix Fontein <felix@fontein.de>2018-07-30 20:10:17 +0200
committerMatt Clay <matt@mystile.com>2018-07-30 11:10:17 -0700
commitd4c16f51be642d1e4d5773a7d48841efc96530fa (patch)
tree3538b5361a8787fa5c1fcf770ab91ca5cfd38e76
parentc809500c7940d54c7cd320d86508d87e4100d779 (diff)
downloadansible-d4c16f51be642d1e4d5773a7d48841efc96530fa.tar.gz
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.
-rw-r--r--test/integration/targets/acme_account/aliases5
-rw-r--r--test/integration/targets/acme_account/meta/main.yml2
-rw-r--r--test/integration/targets/acme_account/tasks/main.yml107
-rw-r--r--test/integration/targets/acme_account/tests/validate.yml44
-rw-r--r--test/integration/targets/acme_certificate/aliases2
-rw-r--r--test/integration/targets/acme_certificate/meta/main.yml2
-rw-r--r--test/integration/targets/acme_certificate/tasks/main.yml217
l---------test/integration/targets/acme_certificate/tasks/obtain-cert.yml1
-rw-r--r--test/integration/targets/acme_certificate/tests/validate.yml64
-rw-r--r--test/integration/targets/acme_certificate_revoke/aliases2
-rw-r--r--test/integration/targets/acme_certificate_revoke/meta/main.yml2
-rw-r--r--test/integration/targets/acme_certificate_revoke/tasks/main.yml90
l---------test/integration/targets/acme_certificate_revoke/tasks/obtain-cert.yml1
-rw-r--r--test/integration/targets/acme_certificate_revoke/tests/validate.yml16
-rw-r--r--test/integration/targets/setup_acme/tasks/main.yml10
-rw-r--r--test/integration/targets/setup_acme/tasks/obtain-cert.yml153
-rw-r--r--test/runner/lib/cloud/acme.py193
17 files changed, 863 insertions, 48 deletions
diff --git a/test/integration/targets/acme_account/aliases b/test/integration/targets/acme_account/aliases
index b159f9e760..d793633030 100644
--- a/test/integration/targets/acme_account/aliases
+++ b/test/integration/targets/acme_account/aliases
@@ -1,3 +1,2 @@
-shippable/posix/group1
-destructive
-disabled
+shippable/cloud/group1
+cloud/acme
diff --git a/test/integration/targets/acme_account/meta/main.yml b/test/integration/targets/acme_account/meta/main.yml
index 800aff6428..81d1e7e77a 100644
--- a/test/integration/targets/acme_account/meta/main.yml
+++ b/test/integration/targets/acme_account/meta/main.yml
@@ -1,2 +1,2 @@
dependencies:
- - setup_openssl
+ - setup_acme
diff --git a/test/integration/targets/acme_account/tasks/main.yml b/test/integration/targets/acme_account/tasks/main.yml
index 83b74d9c86..8879ddce55 100644
--- a/test/integration/targets/acme_account/tasks/main.yml
+++ b/test/integration/targets/acme_account/tasks/main.yml
@@ -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"
- 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_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
+ acme_directory: https://{{ acme_host }}:14000/dir
+ validate_certs: no
state: present
allow_creation: no
ignore_errors: yes
- register: account_not_created_3
+ register: account_not_created_2
# Old 0.9.8 versions have insufficient CLI support for signing with EC keys
when: openssl_version.stdout is version('1.0.0', '>=')
diff --git a/test/integration/targets/acme_account/tests/validate.yml b/test/integration/targets/acme_account/tests/validate.yml
index cb0edd8f60..70bba0d695 100644
--- a/test/integration/targets/acme_account/tests/validate.yml
+++ b/test/integration/targets/acme_account/tests/validate.yml
@@ -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
diff --git a/test/integration/targets/acme_certificate/aliases b/test/integration/targets/acme_certificate/aliases
new file mode 100644
index 0000000000..d793633030
--- /dev/null
+++ b/test/integration/targets/acme_certificate/aliases
@@ -0,0 +1,2 @@
+shippable/cloud/group1
+cloud/acme
diff --git a/test/integration/targets/acme_certificate/meta/main.yml b/test/integration/targets/acme_certificate/meta/main.yml
new file mode 100644
index 0000000000..81d1e7e77a
--- /dev/null
+++ b/test/integration/targets/acme_certificate/meta/main.yml
@@ -0,0 +1,2 @@
+dependencies:
+ - setup_acme
diff --git a/test/integration/targets/acme_certificate/tasks/main.yml b/test/integration/targets/acme_certificate/tasks/main.yml
new file mode 100644
index 0000000000..88a3068954
--- /dev/null
+++ b/test/integration/targets/acme_certificate/tasks/main.yml
@@ -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', '>=')
diff --git a/test/integration/targets/acme_certificate/tasks/obtain-cert.yml b/test/integration/targets/acme_certificate/tasks/obtain-cert.yml
new file mode 120000
index 0000000000..532df9452e
--- /dev/null
+++ b/test/integration/targets/acme_certificate/tasks/obtain-cert.yml
@@ -0,0 +1 @@
+../../setup_acme/tasks/obtain-cert.yml \ No newline at end of file
diff --git a/test/integration/targets/acme_certificate/tests/validate.yml b/test/integration/targets/acme_certificate/tests/validate.yml
new file mode 100644
index 0000000000..6d1954067c
--- /dev/null
+++ b/test/integration/targets/acme_certificate/tests/validate.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
diff --git a/test/integration/targets/acme_certificate_revoke/aliases b/test/integration/targets/acme_certificate_revoke/aliases
new file mode 100644
index 0000000000..d793633030
--- /dev/null
+++ b/test/integration/targets/acme_certificate_revoke/aliases
@@ -0,0 +1,2 @@
+shippable/cloud/group1
+cloud/acme
diff --git a/test/integration/targets/acme_certificate_revoke/meta/main.yml b/test/integration/targets/acme_certificate_revoke/meta/main.yml
new file mode 100644
index 0000000000..81d1e7e77a
--- /dev/null
+++ b/test/integration/targets/acme_certificate_revoke/meta/main.yml
@@ -0,0 +1,2 @@
+dependencies:
+ - setup_acme
diff --git a/test/integration/targets/acme_certificate_revoke/tasks/main.yml b/test/integration/targets/acme_certificate_revoke/tasks/main.yml
new file mode 100644
index 0000000000..affd839af5
--- /dev/null
+++ b/test/integration/targets/acme_certificate_revoke/tasks/main.yml
@@ -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', '>=')
diff --git a/test/integration/targets/acme_certificate_revoke/tasks/obtain-cert.yml b/test/integration/targets/acme_certificate_revoke/tasks/obtain-cert.yml
new file mode 120000
index 0000000000..532df9452e
--- /dev/null
+++ b/test/integration/targets/acme_certificate_revoke/tasks/obtain-cert.yml
@@ -0,0 +1 @@
+../../setup_acme/tasks/obtain-cert.yml \ No newline at end of file
diff --git a/test/integration/targets/acme_certificate_revoke/tests/validate.yml b/test/integration/targets/acme_certificate_revoke/tests/validate.yml
new file mode 100644
index 0000000000..7f8f9c5438
--- /dev/null
+++ b/test/integration/targets/acme_certificate_revoke/tests/validate.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
diff --git a/test/integration/targets/setup_acme/tasks/main.yml b/test/integration/targets/setup_acme/tasks/main.yml
new file mode 100644
index 0000000000..670a4fb8e2
--- /dev/null
+++ b/test/integration/targets/setup_acme/tasks/main.yml
@@ -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 }}"
diff --git a/test/integration/targets/setup_acme/tasks/obtain-cert.yml b/test/integration/targets/setup_acme/tasks/obtain-cert.yml
new file mode 100644
index 0000000000..cc3a1067fd
--- /dev/null
+++ b/test/integration/targets/setup_acme/tasks/obtain-cert.yml
@@ -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'"
+###############################################################################################
diff --git a/test/runner/lib/cloud/acme.py b/test/runner/lib/cloud/acme.py
new file mode 100644
index 0000000000..f7db4b7856
--- /dev/null
+++ b/test/runner/lib/cloud/acme.py
@@ -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'))