Execution
Date 14 Dec 2025 10:04:43 +0000
Duration 00:10:10.66
Controller aio1.openstack.local
User root
Versions
Ansible 2.18.6
ara 1.7.4 / 1.7.4
Python 3.13.5
Summary
15 Hosts
603 Tasks
2357 Results
18 Plays
157 Files
0 Records

File: /home/zuul/src/opendev.org/openstack/ansible-role-pki/tasks/standalone/create_ca.yml

---
# Copyright 2021, BBC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

- name: Create PKI directories
  ansible.builtin.file:
    state: directory
    path: "{{ item.path }}"
    owner: "{{ item.owner | default(pki_owner) | default(omit) }}"
    group: "{{ item.group | default(pki_group) | default(omit) }}"
    mode: "{{ pki_cert_dir_mode }}"
  with_items:
    - "{{ pki_ca_dirs }}"
    - "{{ pki_cert_dirs }}"
  delegate_to: "{{ pki_setup_host }}"
  check_mode: false
  run_once: true

- name: Create certificate {{ ca.name }}
  vars:
    next_serial_no: "{{ serial_no['content'] | b64decode | int + 1 }}"
    ansible_python_interpreter: "{{ pki_setup_host_python_interpreter }}"
    ca_dir: "{{ pki_dir }}/roots/{{ ca.name }}"
    ca_cert_prefix: "{{ pki_dir }}/roots/{{ ca.name }}/certs/{{ ca.name }}"
    # NOTE(damiandabrowski): not_after support is kept only for backward compatbility and should be replaced after 2026.1 with:
    #                        ownca_not_after: "+{{ ca.ttl }}"
    ca_expires: "{{ ca.not_after | default(ca.ttl) | trim }}"
    # NOTE(damiandabrowski): ensures that '+' is added at the beginning
    ca_not_after: "{{ ca_expires.startswith('+') | ternary(ca_expires, '+' ~ ca_expires) }}"
  delegate_to: "{{ pki_setup_host }}"
  block:
    - name: Create directories for certificate authority {{ ca.name }}
      ansible.builtin.file:
        state: directory
        path: "{{ item.path }}"
        owner: "{{ item.owner | default(pki_owner) | default(omit) }}"
        group: "{{ item.group | default(pki_group) | default(omit) }}"
        mode: "{{ item.mode | default('0755') }}"
      with_items:
        - path: "{{ ca_dir }}"
        - path: "{{ ca_dir ~ '/csr' }}"
          mode: "{{ pki_key_dir_mode }}"
        - path: "{{ ca_dir ~ '/private' }}"
          mode: "{{ pki_key_dir_mode }}"
        - path: "{{ ca_dir ~ '/certs' }}"
          mode: "{{ pki_cert_dir_mode }}"

    # NOTE(noonedeadpunk): Incorrect permissions lead to CA certs re-generation as
    #                      openssl_privatekey gets changed when harmonizing ownership/permissions
    - name: Ensure private key has proper ownership
      ansible.builtin.file:
        state: file
        path: "{{ ca_dir ~ '/private/' ~ ca.name ~ '.key.pem' }}"
        mode: "{{ ca.key_mode | default(pki_key_mode) }}"
        owner: "{{ ca.key_owner | default(pki_owner) | default(omit) }}"
        group: "{{ ca.key_group | default(pki_group) | default(omit) }}"
      failed_when: false

    - name: Initialise the serial number for {{ ca.name }}
      ansible.builtin.copy:
        content: "999"
        dest: "{{ ca_dir ~ '/serial' }}"
        force: false

    - name: Generate CA private key for {{ ca.name }}
      community.crypto.openssl_privatekey:
        path: "{{ ca_dir ~ '/private/' ~ ca.name ~ '.key.pem' }}"
        passphrase: "{{ ca.key_passphrase | default(omit) }}"
        cipher: "{{ ('key_passphrase' in ca and ca.key_passphrase) | ternary('auto', omit) }}"
        backup: "{{ ca.backup | default(True) }}"
        mode: "{{ ca.key_mode | default(pki_key_mode) }}"
        owner: "{{ ca.key_owner | default(pki_owner) | default(omit) }}"
        group: "{{ ca.key_group | default(pki_group) | default(omit) }}"
      register: ca_privkey

    - name: Read the serial number for {{ ca.name }}
      ansible.builtin.slurp:
        src: "{{ pki_dir ~ '/roots/' ~ ca.name ~ '/serial' }}"
      register: serial_no

    - name: Create the CA CSR for {{ ca.name }}
      community.crypto.openssl_csr:
        path: "{{ ca_dir }}/csr/ca_csr-{{ next_serial_no }}.csr"
        privatekey_path: "{{ ca_privkey.filename }}"
        privatekey_passphrase: "{{ ca.key_passphrase | default(omit) }}"
        common_name: "{{ ca.cn }}"
        basic_constraints_critical: true
        basic_constraints: "{{ ca.basic_constraints }}"
        key_usage: "{{ ca.key_usage }}"
        country_name: "{{ ca.country_name | default(omit) }}"
        state_or_province_name: "{{ ca.state_or_province_name | default(omit) }}"
        locality_name: "{{ ca.locality_name | default(omit) }}"
        organization_name: "{{ ca.orgnization_name | default(omit) }}"
        organizational_unit_name: "{{ ca.organization_unit_name | default(omit) }}"
        subject: "{{ ca.subject | default(omit) }}"
        backup: "{{ ca.backup | default(True) }}"
      register: ca_csr
      when:
        - ca_privkey is changed or pki_regen_ca == ca.name or (pki_regen_ca | lower) == 'true'

    - name: Write out the new serial number for {{ ca.name }}
      ansible.builtin.copy:
        content: "{{ next_serial_no }}"
        dest: "{{ ca_dir }}/serial"
      when: ca_csr is changed

    - name: Sign the selfsigned Root CA CSR for {{ ca.name }}
      community.crypto.x509_certificate:
        path: "{{ ca_cert_prefix ~ '-' ~ next_serial_no ~ '.crt' }}"
        csr_path: "{{ ca_csr.filename }}"
        provider: "selfsigned"
        privatekey_path: "{{ ca_privkey.filename }}"
        privatekey_passphrase: "{{ ca.key_passphrase | default(omit) }}"
        selfsigned_not_after: "{{ ca_not_after }}"
        backup: "{{ ca.backup | default(True) }}"
      register: ca_selfsigned_crt
      when:
        - ca.provider == 'selfsigned'
        - ca_csr is changed
      notify:
        - "{{ pki_handler_ca_changed }}"

    - name: Sign the intermediate CA CSR for {{ ca.name }}
      community.crypto.x509_certificate:
        path: "{{ ca_cert_prefix ~ '-' ~ next_serial_no ~ '.crt' }}"
        csr_path: "{{ ca_csr.filename }}"
        provider: "ownca"
        ownca_privatekey_path: "{{ pki_dir ~ '/roots/' ~ ca.signed_by ~ '/private/' ~ ca.signed_by ~ '.key.pem' }}"
        ownca_privatekey_passphrase: "{{ ca.ownca_key_passphrase | default(omit) }}"
        ownca_path: "{{ pki_dir ~ '/roots/' ~ ca.signed_by ~ '/certs/' ~ ca.signed_by ~ '.crt' }}"
        ownca_not_after: "{{ ca_not_after }}"
        backup: "{{ ca.backup | default(True) }}"
      register: ca_ownca_crt
      when:
        - ca.provider == 'ownca'
        - ca_csr is changed
      notify:
        - "{{ pki_handler_ca_changed }}"

    - name: Symlink the certificate name to the most recently generated
      ansible.builtin.file:
        src: "{{ (ca_selfsigned_crt.filename | default(ca_ownca_crt.filename)) | basename }}"
        dest: "{{ ca_cert_prefix ~ '.crt' }}"
        state: link
      when: ca_ownca_crt is changed or ca_selfsigned_crt is changed

    - name: Symlink cert path to the chain file for selfsigned CA
      ansible.builtin.file:
        src: "{{ (ca_cert_prefix ~ '.crt') | basename }}"
        dest: "{{ ca_cert_prefix ~ '-chain.crt' }}"
        state: link
      when: ca.provider == 'selfsigned'

    - name: Get certificate info for {{ ca.name }}
      community.crypto.x509_certificate_info:
        path: "{{ ca_selfsigned_crt.filename | default(ca_ownca_crt.filename) }}"
      register: ca_cert_info
      when: ca_ownca_crt is changed or ca_selfsigned_crt is changed

    - name: Save certificate info for {{ ca.name }}
      ansible.builtin.copy:
        content: "{{ ca_cert_info | to_nice_yaml }}"
        dest: "{{ (ca_selfsigned_crt.filename | default(ca_ownca_crt.filename)) ~ '.info' }}"
      when: ca_ownca_crt is changed or ca_selfsigned_crt is changed

    - name: Check if intermediate certificate chain exists
      ansible.builtin.stat:
        path: "{{ ca_cert_prefix ~ '-chain.crt' }}"
      register: chain_result
      when:
        - ca.provider == 'ownca'

    - name: Create intermediate certificate chain
      vars:
        ownca_path: "{{ pki_dir ~ '/roots/' ~ ca.signed_by ~ '/certs/' ~ ca.signed_by ~ '.crt' }}"
        cert_path: "{{ ca_cert_prefix ~ '.crt' }}"
        cert_chain_path: "{{ ca_cert_prefix ~ '-chain.crt' }}"
      ansible.builtin.shell:
        cmd: "cat {{ cert_path }} {{ ownca_path }} > {{ cert_chain_path }}"
      when:
        - ca_ownca_crt is changed or not (chain_result.stat.exists | default(true))
        - ca.provider == 'ownca'