--- # Copyright (c) Ansible Project # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later ## SET UP ACCOUNT KEYS ######################################################################## - block: - name: Generate account keys community.crypto.openssl_privatekey: path: "{{ remote_tmp_dir }}/{{ item.name }}.pem" type: "{{ item.type }}" size: "{{ item.size | default(omit) }}" curve: "{{ item.curve | default(omit) }}" force: true loop: "{{ account_keys }}" vars: account_keys: - name: account-ec256 type: ECC curve: secp256r1 - name: account-ec384 type: ECC curve: secp384r1 - name: account-rsa type: RSA size: "{{ default_rsa_key_size }}" ## SET UP ACCOUNTS ############################################################################ - name: Make sure ECC256 account hasn't been created yet community.crypto.acme_account: select_crypto_backend: "{{ select_crypto_backend }}" acme_version: 2 acme_directory: "{{ acme_directory_url }}" validate_certs: false account_key_src: "{{ remote_tmp_dir }}/account-ec256.pem" state: absent - name: Read account key (EC384) ansible.builtin.slurp: src: '{{ remote_tmp_dir }}/account-ec384.pem' register: slurp - name: Create ECC384 account community.crypto.acme_account: select_crypto_backend: "{{ select_crypto_backend }}" acme_version: 2 acme_directory: "{{ acme_directory_url }}" validate_certs: false account_key_content: "{{ slurp.content | b64decode }}" state: present allow_creation: true terms_agreed: true contact: - mailto:example@example.org - mailto:example@example.com - name: Create RSA account community.crypto.acme_account: select_crypto_backend: "{{ select_crypto_backend }}" acme_version: 2 acme_directory: "{{ acme_directory_url }}" validate_certs: false account_key_src: "{{ remote_tmp_dir }}/account-rsa.pem" state: present allow_creation: true terms_agreed: true contact: [] ## OBTAIN CERTIFICATES ######################################################################## - name: Obtain cert 1 ansible.builtin.include_tasks: obtain-cert.yml vars: certgen_title: Certificate 1 certificate_name: cert-1 key_type: rsa rsa_bits: "{{ default_rsa_key_size }}" subject_alt_name: "DNS:example.com" subject_alt_name_critical: false account_key: account-ec256 challenge: http-01 modify_account: true deactivate_authzs: false force: false remaining_days: 1 terms_agreed: true account_email: "example@example.org" retrieve_all_alternates: true acme_expected_root_number: 1 select_chain: - test_certificates: last issuer: "{{ acme_roots[1].subject }}" use_csr_content: true - name: Store obtain results for cert 1 ansible.builtin.set_fact: cert_1_obtain_results: "{{ certificate_obtain_result }}" cert_1_alternate: "{{ 1 if select_crypto_backend == 'cryptography' else 0 }}" - name: Obtain cert 2 ansible.builtin.include_tasks: obtain-cert.yml vars: certgen_title: Certificate 2 certificate_name: cert-2 certificate_passphrase: "{{ 'hunter2' if select_crypto_backend != 'openssl' else '' }}" key_type: ec256 subject_alt_name: "DNS:*.example.com,DNS:example.com" subject_alt_name_critical: true account_key: account-ec384 challenge: dns-01 modify_account: false deactivate_authzs: true force: false remaining_days: 1 terms_agreed: false account_email: "" acme_expected_root_number: 0 retrieve_all_alternates: true select_chain: # All intermediates have the same subject, so always the first # chain will be found, and we need a second condition to make sure # that the first condition actually works. (The second condition # has been tested above.) - test_certificates: all subject: "{{ acme_intermediates[0].subject }}" - test_certificates: all issuer: "{{ acme_roots[2].subject }}" use_csr_content: false - name: Store obtain results for cert 2 ansible.builtin.set_fact: cert_2_obtain_results: "{{ certificate_obtain_result }}" cert_2_alternate: "{{ 0 if select_crypto_backend == 'cryptography' else 0 }}" - name: Read account key (RSA) ansible.builtin.slurp: src: '{{ remote_tmp_dir }}/account-rsa.pem' register: slurp_account_key - name: Obtain cert 3 ansible.builtin.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: false account_key_content: "{{ slurp_account_key.content | b64decode }}" challenge: dns-01 modify_account: false deactivate_authzs: true force: false remaining_days: 1 terms_agreed: false account_email: "" acme_expected_root_number: 0 retrieve_all_alternates: true select_chain: - test_certificates: last subject: "{{ acme_roots[1].subject }}" use_csr_content: true - name: Store obtain results for cert 3 ansible.builtin.set_fact: cert_3_obtain_results: "{{ certificate_obtain_result }}" cert_3_alternate: "{{ 0 if select_crypto_backend == 'cryptography' else 0 }}" - name: Obtain cert 4 ansible.builtin.include_tasks: obtain-cert.yml vars: certgen_title: Certificate 4 certificate_name: cert-4 key_type: rsa rsa_bits: "{{ default_rsa_key_size }}" 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: false account_key: account-rsa challenge: http-01 modify_account: false deactivate_authzs: false force: true remaining_days: 1 terms_agreed: false account_email: "" acme_certificate_profile: "{{ 'default' if acme_supports_profiles else omit }}" acme_expected_root_number: 2 select_chain: - test_certificates: last issuer: "{{ acme_roots[2].subject }}" - test_certificates: last issuer: "{{ acme_roots[1].subject }}" use_csr_content: false - name: Store obtain results for cert 4 ansible.builtin.set_fact: cert_4_obtain_results: "{{ certificate_obtain_result }}" cert_4_alternate: "{{ 2 if select_crypto_backend == 'cryptography' else 0 }}" - name: Obtain cert 5 ansible.builtin.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: false account_key: account-ec384 challenge: http-01 modify_account: false deactivate_authzs: true force: true remaining_days: 1 terms_agreed: false account_email: "" use_csr_content: true - name: Store obtain results for cert 5a ansible.builtin.set_fact: cert_5a_obtain_results: "{{ certificate_obtain_result }}" cert_5_alternate: "{{ 0 if select_crypto_backend == 'cryptography' else 0 }}" - name: Obtain cert 5 (should not, since already there and valid for more than 1 days) ansible.builtin.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: false account_key: account-ec384 challenge: http-01 modify_account: false deactivate_authzs: true force: false remaining_days: 1 terms_agreed: false account_email: "" use_csr_content: false - name: Store obtain results for cert 5b ansible.builtin.set_fact: cert_5_recreate_1: "{{ challenge_data is changed }}" - name: Obtain cert 5 (should again by less days) ansible.builtin.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: false account_key: account-ec384 challenge: http-01 modify_account: false deactivate_authzs: true force: true remaining_days: 1000 terms_agreed: false account_email: "" use_csr_content: true acme_certificate_profile: "{{ '6days' if acme_supports_profiles else omit }}" acme_certificate_include_renewal_cert_id: when_ari_supported - name: Store obtain results for cert 5c ansible.builtin.set_fact: cert_5_recreate_2: "{{ challenge_data is changed }}" cert_5c_obtain_results: "{{ certificate_obtain_result }}" - name: Read account key (EC384) ansible.builtin.slurp: src: '{{ remote_tmp_dir }}/account-ec384.pem' register: slurp_account_key - name: Obtain cert 5 (should again by force) ansible.builtin.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: false account_key_content: "{{ slurp_account_key.content | b64decode }}" challenge: http-01 modify_account: false deactivate_authzs: true force: true remaining_days: 1 terms_agreed: false account_email: "" use_csr_content: false - name: Store obtain results for cert 5d ansible.builtin.set_fact: cert_5_recreate_3: "{{ challenge_data is changed }}" cert_5d_obtain_results: "{{ certificate_obtain_result }}" - block: - name: Obtain cert 6 ansible.builtin.include_tasks: obtain-cert.yml vars: certgen_title: Certificate 6 certificate_name: cert-6 key_type: rsa rsa_bits: "{{ default_rsa_key_size }}" subject_alt_name: "DNS:example.org" subject_alt_name_critical: false account_key: account-ec256 challenge: tls-alpn-01 modify_account: true deactivate_authzs: false force: false remaining_days: 1 terms_agreed: true account_email: "example@example.org" acme_expected_root_number: 0 select_chain: # All intermediates have the same subject key identifier, so always # the first chain will be found, and we need a second condition to # make sure that the first condition actually works. (The second # condition has been tested above.) - test_certificates: first subject_key_identifier: "{{ acme_intermediates[0].subject_key_identifier }}" - test_certificates: last issuer: "{{ acme_roots[1].subject }}" use_csr_content: true - name: Store obtain results for cert 6 ansible.builtin.set_fact: cert_6_obtain_results: "{{ certificate_obtain_result }}" cert_6_alternate: "{{ 0 if select_crypto_backend == 'cryptography' else 0 }}" when: acme_intermediates[0].subject_key_identifier is defined - block: - name: Obtain cert 7 ansible.builtin.include_tasks: obtain-cert.yml vars: certgen_title: Certificate 7 certificate_name: cert-7 key_type: rsa rsa_bits: "{{ default_rsa_key_size }}" subject_alt_name: - "IP:127.0.0.1" # - "IP:::1" subject_alt_name_critical: false account_key: account-ec256 challenge: http-01 modify_account: true deactivate_authzs: false force: false remaining_days: 1 terms_agreed: true account_email: "example@example.org" acme_expected_root_number: 2 select_chain: - test_certificates: last authority_key_identifier: "{{ acme_roots[2].subject_key_identifier }}" use_csr_content: false - name: Store obtain results for cert 7 ansible.builtin.set_fact: cert_7_obtain_results: "{{ certificate_obtain_result }}" cert_7_alternate: "{{ 2 if select_crypto_backend == 'cryptography' else 0 }}" when: acme_roots[2].subject_key_identifier is defined - block: - name: Obtain cert 8 ansible.builtin.include_tasks: obtain-cert.yml vars: certgen_title: Certificate 8 certificate_name: cert-8 key_type: rsa rsa_bits: "{{ default_rsa_key_size_certificates }}" subject_alt_name: - "IP:127.0.0.1" # IPv4 only since our test validation server doesn't work # with IPv6 (thanks to Python's socketserver). subject_alt_name_critical: false account_key: account-ec256 challenge: tls-alpn-01 challenge_alpn_tls: acme_challenge_cert_helper modify_account: true deactivate_authzs: false force: false remaining_days: 1 terms_agreed: true account_email: "example@example.org" use_csr_content: true - name: Store obtain results for cert 8 ansible.builtin.set_fact: cert_8_obtain_results: "{{ certificate_obtain_result }}" cert_8_alternate: "{{ 0 if select_crypto_backend == 'cryptography' else 0 }}" - when: ansible_version.full is version('2.21', '>=') block: - name: Obtain cert 9 ansible.builtin.include_tasks: obtain-cert.yml vars: certgen_title: Certificate 9 certificate_name: cert-9 key_type: ec256 subject_alt_name: "DNS:*.example.com,DNS:example.org,DNS:t1.example.com" subject_alt_name_critical: false account_key: account-ec256 challenge: dns-account-01 modify_account: true deactivate_authzs: false force: false remaining_days: 1 terms_agreed: true account_email: "example@example.org" use_csr_content: true - name: Store obtain results for cert 9 ansible.builtin.set_fact: cert_9_obtain_results: "{{ certificate_obtain_result }}" cert_9_alternate: "{{ 0 if select_crypto_backend == 'cryptography' else 0 }}" - when: ansible_version.full is version('2.21', '>=') vars: validation_record_domains: - name: example.com wildcard: true - name: example.org persist_until: "+5m" - name: t1.example.com issuer_domain_name: pebble.letsencrypt.org block: - name: Obtain ACME account community.crypto.acme_account_info: acme_directory: "{{ acme_directory_url }}" acme_version: 2 validate_certs: false account_key_src: "{{ remote_tmp_dir }}/account-ec256.pem" select_crypto_backend: "{{ select_crypto_backend }}" register: account_info - name: Create dns-persist-01 DNS entries ansible.builtin.uri: url: "http://{{ acme_host }}:5000/dns/_validation-persist.{{ item.name }}" method: PUT body_format: json body: - >- {{ issuer_domain_name | community.crypto.acme_dns_persist_record( account_uri=account_info.account_uri, policy='wildcard' if item.wildcard | default(false) else none, persist_until=item.persist_until | default(none), ) }} loop: "{{ validation_record_domains }}" - name: Obtain cert 10 ansible.builtin.include_tasks: obtain-cert.yml vars: certgen_title: Certificate 10 certificate_name: cert-10 key_type: ec256 subject_alt_name: "DNS:*.example.com,DNS:example.org,DNS:t1.example.com" subject_alt_name_critical: false account_key: account-ec256 challenge: dns-persist-01 modify_account: true deactivate_authzs: false force: false remaining_days: 1 terms_agreed: true account_email: "example@example.org" use_csr_content: true - name: Store obtain results for cert 10 ansible.builtin.set_fact: cert_10_obtain_results: "{{ certificate_obtain_result }}" cert_10_alternate: "{{ 0 if select_crypto_backend == 'cryptography' else 0 }}" - name: Remove dns-persist-01 DNS entries ansible.builtin.uri: url: "http://{{ acme_host }}:5000/dns/_validation-persist.{{ item.name }}" method: DELETE loop: "{{ validation_record_domains }}" ## DISSECT CERTIFICATES ####################################################################### # Make sure certificates are valid. Root certificate for Pebble equals the chain certificate. - name: Verifying cert 1 ansible.builtin.command: '{{ openssl_binary }} verify -CAfile "{{ remote_tmp_dir }}/cert-1-root.pem" -untrusted "{{ remote_tmp_dir }}/cert-1-chain.pem" "{{ remote_tmp_dir }}/cert-1.pem"' ignore_errors: true register: cert_1_valid - name: Verifying cert 2 ansible.builtin.command: '{{ openssl_binary }} verify -CAfile "{{ remote_tmp_dir }}/cert-2-root.pem" -untrusted "{{ remote_tmp_dir }}/cert-2-chain.pem" "{{ remote_tmp_dir }}/cert-2.pem"' ignore_errors: true register: cert_2_valid - name: Verifying cert 3 ansible.builtin.command: '{{ openssl_binary }} verify -CAfile "{{ remote_tmp_dir }}/cert-3-root.pem" -untrusted "{{ remote_tmp_dir }}/cert-3-chain.pem" "{{ remote_tmp_dir }}/cert-3.pem"' ignore_errors: true register: cert_3_valid - name: Verifying cert 4 ansible.builtin.command: '{{ openssl_binary }} verify -CAfile "{{ remote_tmp_dir }}/cert-4-root.pem" -untrusted "{{ remote_tmp_dir }}/cert-4-chain.pem" "{{ remote_tmp_dir }}/cert-4.pem"' ignore_errors: true register: cert_4_valid - name: Verifying cert 5 ansible.builtin.command: '{{ openssl_binary }} verify -CAfile "{{ remote_tmp_dir }}/cert-5-root.pem" -untrusted "{{ remote_tmp_dir }}/cert-5-chain.pem" "{{ remote_tmp_dir }}/cert-5.pem"' ignore_errors: true register: cert_5_valid - name: Verifying cert 6 ansible.builtin.command: '{{ openssl_binary }} verify -CAfile "{{ remote_tmp_dir }}/cert-6-root.pem" -untrusted "{{ remote_tmp_dir }}/cert-6-chain.pem" "{{ remote_tmp_dir }}/cert-6.pem"' ignore_errors: true register: cert_6_valid when: acme_intermediates[0].subject_key_identifier is defined - name: Verifying cert 7 ansible.builtin.command: '{{ openssl_binary }} verify -CAfile "{{ remote_tmp_dir }}/cert-7-root.pem" -untrusted "{{ remote_tmp_dir }}/cert-7-chain.pem" "{{ remote_tmp_dir }}/cert-7.pem"' ignore_errors: true register: cert_7_valid when: acme_roots[2].subject_key_identifier is defined - name: Verifying cert 8 ansible.builtin.command: '{{ openssl_binary }} verify -CAfile "{{ remote_tmp_dir }}/cert-8-root.pem" -untrusted "{{ remote_tmp_dir }}/cert-8-chain.pem" "{{ remote_tmp_dir }}/cert-8.pem"' ignore_errors: true register: cert_8_valid - name: Verifying cert 9 ansible.builtin.command: '{{ openssl_binary }} verify -CAfile "{{ remote_tmp_dir }}/cert-9-root.pem" -untrusted "{{ remote_tmp_dir }}/cert-9-chain.pem" "{{ remote_tmp_dir }}/cert-9.pem"' ignore_errors: true register: cert_9_valid when: ansible_version.full is version('2.21', '>=') - name: Verifying cert 10 ansible.builtin.command: '{{ openssl_binary }} verify -CAfile "{{ remote_tmp_dir }}/cert-10-root.pem" -untrusted "{{ remote_tmp_dir }}/cert-10-chain.pem" "{{ remote_tmp_dir }}/cert-10.pem"' ignore_errors: true register: cert_10_valid when: ansible_version.full is version('2.21', '>=') # Dump certificate info - name: Dumping cert 1 ansible.builtin.command: '{{ openssl_binary }} x509 -in "{{ remote_tmp_dir }}/cert-1.pem" -noout -text' register: cert_1_text - name: Dumping cert 2 ansible.builtin.command: '{{ openssl_binary }} x509 -in "{{ remote_tmp_dir }}/cert-2.pem" -noout -text' register: cert_2_text - name: Dumping cert 3 ansible.builtin.command: '{{ openssl_binary }} x509 -in "{{ remote_tmp_dir }}/cert-3.pem" -noout -text' register: cert_3_text - name: Dumping cert 4 ansible.builtin.command: '{{ openssl_binary }} x509 -in "{{ remote_tmp_dir }}/cert-4.pem" -noout -text' register: cert_4_text - name: Dumping cert 5 ansible.builtin.command: '{{ openssl_binary }} x509 -in "{{ remote_tmp_dir }}/cert-5.pem" -noout -text' register: cert_5_text - name: Dumping cert 6 ansible.builtin.command: '{{ openssl_binary }} x509 -in "{{ remote_tmp_dir }}/cert-6.pem" -noout -text' register: cert_6_text when: acme_intermediates[0].subject_key_identifier is defined - name: Dumping cert 7 ansible.builtin.command: '{{ openssl_binary }} x509 -in "{{ remote_tmp_dir }}/cert-7.pem" -noout -text' register: cert_7_text when: acme_roots[2].subject_key_identifier is defined - name: Dumping cert 8 ansible.builtin.command: '{{ openssl_binary }} x509 -in "{{ remote_tmp_dir }}/cert-8.pem" -noout -text' register: cert_8_text - name: Dumping cert 9 ansible.builtin.command: '{{ openssl_binary }} x509 -in "{{ remote_tmp_dir }}/cert-9.pem" -noout -text' register: cert_9_text when: ansible_version.full is version('2.21', '>=') - name: Dumping cert 10 ansible.builtin.command: '{{ openssl_binary }} x509 -in "{{ remote_tmp_dir }}/cert-10.pem" -noout -text' register: cert_10_text when: ansible_version.full is version('2.21', '>=') # Dump certificate info - name: Dumping cert 1 community.crypto.x509_certificate_info: path: "{{ remote_tmp_dir }}/cert-1.pem" register: cert_1_info - name: Dumping cert 2 community.crypto.x509_certificate_info: path: "{{ remote_tmp_dir }}/cert-2.pem" register: cert_2_info - name: Dumping cert 3 community.crypto.x509_certificate_info: path: "{{ remote_tmp_dir }}/cert-3.pem" register: cert_3_info - name: Dumping cert 4 community.crypto.x509_certificate_info: path: "{{ remote_tmp_dir }}/cert-4.pem" register: cert_4_info - name: Dumping cert 5 community.crypto.x509_certificate_info: path: "{{ remote_tmp_dir }}/cert-5.pem" register: cert_5_info - name: Dumping cert 6 community.crypto.x509_certificate_info: path: "{{ remote_tmp_dir }}/cert-6.pem" register: cert_6_info when: acme_intermediates[0].subject_key_identifier is defined - name: Dumping cert 7 community.crypto.x509_certificate_info: path: "{{ remote_tmp_dir }}/cert-7.pem" register: cert_7_info when: acme_roots[2].subject_key_identifier is defined - name: Dumping cert 8 community.crypto.x509_certificate_info: path: "{{ remote_tmp_dir }}/cert-8.pem" register: cert_8_info - name: Dumping cert 9 community.crypto.x509_certificate_info: path: "{{ remote_tmp_dir }}/cert-9.pem" register: cert_9_info when: ansible_version.full is version('2.21', '>=') - name: Dumping cert 10 community.crypto.x509_certificate_info: path: "{{ remote_tmp_dir }}/cert-10.pem" register: cert_10_info when: ansible_version.full is version('2.21', '>=') ## GET ACCOUNT ORDERS ######################################################################### - name: Don't retrieve orders community.crypto.acme_account_info: select_crypto_backend: "{{ select_crypto_backend }}" account_key_src: "{{ remote_tmp_dir }}/account-ec256.pem" acme_version: 2 acme_directory: "{{ acme_directory_url }}" validate_certs: false retrieve_orders: ignore register: account_orders_not - name: Retrieve orders as URL list (1/2) community.crypto.acme_account_info: select_crypto_backend: "{{ select_crypto_backend }}" account_key_src: "{{ remote_tmp_dir }}/account-ec256.pem" acme_version: 2 acme_directory: "{{ acme_directory_url }}" validate_certs: false retrieve_orders: url_list register: account_orders_urls - name: Retrieve orders as URL list (2/2) community.crypto.acme_account_info: select_crypto_backend: "{{ select_crypto_backend }}" account_key_src: "{{ remote_tmp_dir }}/account-rsa.pem" acme_version: 2 acme_directory: "{{ acme_directory_url }}" validate_certs: false retrieve_orders: url_list register: account_orders_urls2 - name: Retrieve orders as object list (1/2) community.crypto.acme_account_info: select_crypto_backend: "{{ select_crypto_backend }}" account_key_src: "{{ remote_tmp_dir }}/account-ec256.pem" acme_version: 2 acme_directory: "{{ acme_directory_url }}" validate_certs: false retrieve_orders: object_list register: account_orders_full - name: Retrieve orders as object list (2/2) community.crypto.acme_account_info: select_crypto_backend: "{{ select_crypto_backend }}" account_key_src: "{{ remote_tmp_dir }}/account-rsa.pem" acme_version: 2 acme_directory: "{{ acme_directory_url }}" validate_certs: false retrieve_orders: object_list register: account_orders_full2