{"id":76,"sha1":"c817045be4f5d78c643c966aa6ac13765c9ec665","playbook":{"id":2,"items":{"plays":7,"tasks":72,"results":71,"hosts":2,"files":97,"records":0},"arguments":{"version":null,"verbosity":0,"private_key_file":null,"remote_user":null,"connection":"openstack.osa.ssh","timeout":null,"ssh_common_args":null,"sftp_extra_args":null,"scp_extra_args":null,"ssh_extra_args":null,"ask_pass":false,"connection_password_file":null,"force_handlers":true,"flush_cache":false,"become":false,"become_method":"sudo","become_user":null,"become_ask_pass":false,"become_password_file":null,"tags":["all"],"skip_tags":[],"check":false,"diff":false,"inventory":["/home/zuul/src/opendev.org/openstack/openstack-ansible/inventory/dynamic_inventory.py","/home/zuul/src/opendev.org/openstack/openstack-ansible/inventory/inventory.ini","/etc/openstack_deploy/inventory.ini"],"listhosts":false,"subset":null,"extra_vars":"Not saved by ARA as configured by 'ignored_arguments'","vault_ids":[],"ask_vault_pass":false,"vault_password_files":[],"forks":4,"module_path":null,"syntax":false,"listtasks":false,"listtags":false,"step":false,"start_at_task":null,"args":["setup-hosts.yml"]},"labels":[{"id":1,"name":"check:False"},{"id":2,"name":"tags:all"}],"started":"2025-12-05T13:22:31.084267Z","ended":"2025-12-05T13:23:21.889555Z","duration":"00:00:50.805288","name":null,"ansible_version":"2.18.6","client_version":"1.7.4","python_version":"3.12.11","server_version":"1.7.4","status":"failed","path":"/home/zuul/src/opendev.org/openstack/openstack-ansible/playbooks/setup-hosts.yml","controller":"aio1.openstack.local","user":"root"},"content":"---\n# Copyright 2021, BBC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n- name: Create PKI directories\n  ansible.builtin.file:\n    state: directory\n    path: \"{{ item.path }}\"\n    owner: \"{{ item.owner | default(pki_owner) | default(omit) }}\"\n    group: \"{{ item.group | default(pki_group) | default(omit) }}\"\n    mode: \"{{ pki_cert_dir_mode }}\"\n  with_items:\n    - \"{{ pki_ca_dirs }}\"\n    - \"{{ pki_cert_dirs }}\"\n  delegate_to: \"{{ pki_setup_host }}\"\n  check_mode: false\n  run_once: true\n\n- name: Create certificate {{ ca.name }}\n  vars:\n    next_serial_no: \"{{ serial_no['content'] | b64decode | int + 1 }}\"\n    ansible_python_interpreter: \"{{ pki_setup_host_python_interpreter }}\"\n    ca_dir: \"{{ pki_dir }}/roots/{{ ca.name }}\"\n    ca_cert_prefix: \"{{ pki_dir }}/roots/{{ ca.name }}/certs/{{ ca.name }}\"\n    # NOTE(damiandabrowski): not_after support is kept only for backward compatbility and should be replaced after 2026.1 with:\n    #                        ownca_not_after: \"+{{ ca.ttl }}\"\n    ca_expires: \"{{ ca.not_after | default(ca.ttl) | trim }}\"\n    # NOTE(damiandabrowski): ensures that '+' is added at the beginning\n    ca_not_after: \"{{ ca_expires.startswith('+') | ternary(ca_expires, '+' ~ ca_expires) }}\"\n  delegate_to: \"{{ pki_setup_host }}\"\n  block:\n    - name: Create directories for certificate authority {{ ca.name }}\n      ansible.builtin.file:\n        state: directory\n        path: \"{{ item.path }}\"\n        owner: \"{{ item.owner | default(pki_owner) | default(omit) }}\"\n        group: \"{{ item.group | default(pki_group) | default(omit) }}\"\n        mode: \"{{ item.mode | default('0755') }}\"\n      with_items:\n        - path: \"{{ ca_dir }}\"\n        - path: \"{{ ca_dir ~ '/csr' }}\"\n          mode: \"{{ pki_key_dir_mode }}\"\n        - path: \"{{ ca_dir ~ '/private' }}\"\n          mode: \"{{ pki_key_dir_mode }}\"\n        - path: \"{{ ca_dir ~ '/certs' }}\"\n          mode: \"{{ pki_cert_dir_mode }}\"\n\n    # NOTE(noonedeadpunk): Incorrect permissions lead to CA certs re-generation as\n    #                      openssl_privatekey gets changed when harmonizing ownership/permissions\n    - name: Ensure private key has proper ownership\n      ansible.builtin.file:\n        state: file\n        path: \"{{ ca_dir ~ '/private/' ~ ca.name ~ '.key.pem' }}\"\n        mode: \"{{ ca.key_mode | default(pki_key_mode) }}\"\n        owner: \"{{ ca.key_owner | default(pki_owner) | default(omit) }}\"\n        group: \"{{ ca.key_group | default(pki_group) | default(omit) }}\"\n      failed_when: false\n\n    - name: Initialise the serial number for {{ ca.name }}\n      ansible.builtin.copy:\n        content: \"999\"\n        dest: \"{{ ca_dir ~ '/serial' }}\"\n        force: false\n\n    - name: Generate CA private key for {{ ca.name }}\n      community.crypto.openssl_privatekey:\n        path: \"{{ ca_dir ~ '/private/' ~ ca.name ~ '.key.pem' }}\"\n        passphrase: \"{{ ca.key_passphrase | default(omit) }}\"\n        cipher: \"{{ ('key_passphrase' in ca and ca.key_passphrase) | ternary('auto', omit) }}\"\n        backup: \"{{ ca.backup | default(True) }}\"\n        mode: \"{{ ca.key_mode | default(pki_key_mode) }}\"\n        owner: \"{{ ca.key_owner | default(pki_owner) | default(omit) }}\"\n        group: \"{{ ca.key_group | default(pki_group) | default(omit) }}\"\n      register: ca_privkey\n\n    - name: Read the serial number for {{ ca.name }}\n      ansible.builtin.slurp:\n        src: \"{{ pki_dir ~ '/roots/' ~ ca.name ~ '/serial' }}\"\n      register: serial_no\n\n    - name: Create the CA CSR for {{ ca.name }}\n      community.crypto.openssl_csr:\n        path: \"{{ ca_dir }}/csr/ca_csr-{{ next_serial_no }}.csr\"\n        privatekey_path: \"{{ ca_privkey.filename }}\"\n        privatekey_passphrase: \"{{ ca.key_passphrase | default(omit) }}\"\n        common_name: \"{{ ca.cn }}\"\n        basic_constraints_critical: true\n        basic_constraints: \"{{ ca.basic_constraints }}\"\n        key_usage: \"{{ ca.key_usage }}\"\n        country_name: \"{{ ca.country_name | default(omit) }}\"\n        state_or_province_name: \"{{ ca.state_or_province_name | default(omit) }}\"\n        locality_name: \"{{ ca.locality_name | default(omit) }}\"\n        organization_name: \"{{ ca.orgnization_name | default(omit) }}\"\n        organizational_unit_name: \"{{ ca.organization_unit_name | default(omit) }}\"\n        subject: \"{{ ca.subject | default(omit) }}\"\n        backup: \"{{ ca.backup | default(True) }}\"\n      register: ca_csr\n      when:\n        - ca_privkey is changed or pki_regen_ca == ca.name or (pki_regen_ca | lower) == 'true'\n\n    - name: Write out the new serial number for {{ ca.name }}\n      ansible.builtin.copy:\n        content: \"{{ next_serial_no }}\"\n        dest: \"{{ ca_dir }}/serial\"\n      when: ca_csr is changed\n\n    - name: Sign the selfsigned Root CA CSR for {{ ca.name }}\n      community.crypto.x509_certificate:\n        path: \"{{ ca_cert_prefix ~ '-' ~ next_serial_no ~ '.crt' }}\"\n        csr_path: \"{{ ca_csr.filename }}\"\n        provider: \"selfsigned\"\n        privatekey_path: \"{{ ca_privkey.filename }}\"\n        privatekey_passphrase: \"{{ ca.key_passphrase | default(omit) }}\"\n        selfsigned_not_after: \"{{ ca_not_after }}\"\n        backup: \"{{ ca.backup | default(True) }}\"\n      register: ca_selfsigned_crt\n      when:\n        - ca.provider == 'selfsigned'\n        - ca_csr is changed\n      notify:\n        - \"{{ pki_handler_ca_changed }}\"\n\n    - name: Sign the intermediate CA CSR for {{ ca.name }}\n      community.crypto.x509_certificate:\n        path: \"{{ ca_cert_prefix ~ '-' ~ next_serial_no ~ '.crt' }}\"\n        csr_path: \"{{ ca_csr.filename }}\"\n        provider: \"ownca\"\n        ownca_privatekey_path: \"{{ pki_dir ~ '/roots/' ~ ca.signed_by ~ '/private/' ~ ca.signed_by ~ '.key.pem' }}\"\n        ownca_privatekey_passphrase: \"{{ ca.ownca_key_passphrase | default(omit) }}\"\n        ownca_path: \"{{ pki_dir ~ '/roots/' ~ ca.signed_by ~ '/certs/' ~ ca.signed_by ~ '.crt' }}\"\n        ownca_not_after: \"{{ ca_not_after }}\"\n        backup: \"{{ ca.backup | default(True) }}\"\n      register: ca_ownca_crt\n      when:\n        - ca.provider == 'ownca'\n        - ca_csr is changed\n      notify:\n        - \"{{ pki_handler_ca_changed }}\"\n\n    - name: Symlink the certificate name to the most recently generated\n      ansible.builtin.file:\n        src: \"{{ (ca_selfsigned_crt.filename | default(ca_ownca_crt.filename)) | basename }}\"\n        dest: \"{{ ca_cert_prefix ~ '.crt' }}\"\n        state: link\n      when: ca_ownca_crt is changed or ca_selfsigned_crt is changed\n\n    - name: Symlink cert path to the chain file for selfsigned CA\n      ansible.builtin.file:\n        src: \"{{ (ca_cert_prefix ~ '.crt') | basename }}\"\n        dest: \"{{ ca_cert_prefix ~ '-chain.crt' }}\"\n        state: link\n      when: ca.provider == 'selfsigned'\n\n    - name: Get certificate info for {{ ca.name }}\n      community.crypto.x509_certificate_info:\n        path: \"{{ ca_selfsigned_crt.filename | default(ca_ownca_crt.filename) }}\"\n      register: ca_cert_info\n      when: ca_ownca_crt is changed or ca_selfsigned_crt is changed\n\n    - name: Save certificate info for {{ ca.name }}\n      ansible.builtin.copy:\n        content: \"{{ ca_cert_info | to_nice_yaml }}\"\n        dest: \"{{ (ca_selfsigned_crt.filename | default(ca_ownca_crt.filename)) ~ '.info' }}\"\n      when: ca_ownca_crt is changed or ca_selfsigned_crt is changed\n\n    - name: Check if intermediate certificate chain exists\n      ansible.builtin.stat:\n        path: \"{{ ca_cert_prefix ~ '-chain.crt' }}\"\n      register: chain_result\n      when:\n        - ca.provider == 'ownca'\n\n    - name: Create intermediate certificate chain\n      vars:\n        ownca_path: \"{{ pki_dir ~ '/roots/' ~ ca.signed_by ~ '/certs/' ~ ca.signed_by ~ '.crt' }}\"\n        cert_path: \"{{ ca_cert_prefix ~ '.crt' }}\"\n        cert_chain_path: \"{{ ca_cert_prefix ~ '-chain.crt' }}\"\n      ansible.builtin.shell:\n        cmd: \"cat {{ cert_path }} {{ ownca_path }} > {{ cert_chain_path }}\"\n      when:\n        - ca_ownca_crt is changed or not (chain_result.stat.exists | default(true))\n        - ca.provider == 'ownca'\n","created":"2025-12-05T13:22:34.233777Z","updated":"2025-12-05T13:22:34.233789Z","path":"/home/zuul/src/opendev.org/openstack/ansible-role-pki/tasks/standalone/create_ca.yml"}