如何创建一个小型 CA

community.crypto 集合提供了多个模块,用于创建私钥、证书签名请求和证书。本指南展示了如何创建自己的小型 CA 以及如何使用它来签署证书。

在所有示例中,我们假设 CA 的私钥受密码保护,密码在 secret_ca_passphrase 变量中提供。

设置 CA

任何证书都可以用作 CA 证书。您可以创建一个自签名证书(请参阅如何创建自签名证书),使用另一个 CA 证书来签署新证书(使用下面的签署证书的说明),要求(并支付)商业 CA 签署您的 CA 证书等等。

以下说明展示了如何设置一个简单的自签名 CA 证书。

- name: Create private key with password protection
  community.crypto.openssl_privatekey:
    path: /path/to/ca-certificate.key
    passphrase: "{{ secret_ca_passphrase }}"

- name: Create certificate signing request (CSR) for CA certificate
  community.crypto.openssl_csr_pipe:
    privatekey_path: /path/to/ca-certificate.key
    privatekey_passphrase: "{{ secret_ca_passphrase }}"
    common_name: Ansible CA
    use_common_name_for_san: false  # since we do not specify SANs, don't use CN as a SAN
    basic_constraints:
      - 'CA:TRUE'
    basic_constraints_critical: true
    key_usage:
      - keyCertSign
    key_usage_critical: true
  register: ca_csr

- name: Create self-signed CA certificate from CSR
  community.crypto.x509_certificate:
    path: /path/to/ca-certificate.pem
    csr_content: "{{ ca_csr.csr }}"
    privatekey_path: /path/to/ca-certificate.key
    privatekey_passphrase: "{{ secret_ca_passphrase }}"
    provider: selfsigned

使用 CA 签署证书

要签署证书,您必须将 CSR 传递给 community.crypto.x509_certificate 模块community.crypto.x509_certificate_pipe 模块

在以下示例中,我们假设要签署的证书(包括其私钥)在 server_1 上,而我们的 CA 证书在 server_2 上。我们不希望任何密钥材料离开各自的服务器。

- name: Create private key for new certificate on server_1
  community.crypto.openssl_privatekey:
    path: /path/to/certificate.key
  delegate_to: server_1
  run_once: true

- name: Create certificate signing request (CSR) for new certificate
  community.crypto.openssl_csr_pipe:
    privatekey_path: /path/to/certificate.key
    subject_alt_name:
      - "DNS:ansible.com"
      - "DNS:www.ansible.com"
      - "DNS:docs.ansible.com"
  delegate_to: server_1
  run_once: true
  register: csr

- name: Sign certificate with our CA
  community.crypto.x509_certificate_pipe:
    csr_content: "{{ csr.csr }}"
    provider: ownca
    ownca_path: /path/to/ca-certificate.pem
    ownca_privatekey_path: /path/to/ca-certificate.key
    ownca_privatekey_passphrase: "{{ secret_ca_passphrase }}"
    ownca_not_after: +365d  # valid for one year
    ownca_not_before: "-1d"  # valid since yesterday
  delegate_to: server_2
  run_once: true
  register: certificate

- name: Write certificate file on server_1
  copy:
    dest: /path/to/certificate.pem
    content: "{{ certificate.certificate }}"
  delegate_to: server_1
  run_once: true

请注意,上述过程是**非幂等的**。以下扩展示例从 server_1 读取现有证书(如果存在),并将其提供给 community.crypto.x509_certificate_pipe 模块,并且仅在发生更改时才将结果写回

- name: Create private key for new certificate on server_1
  community.crypto.openssl_privatekey:
    path: /path/to/certificate.key
  delegate_to: server_1
  run_once: true

- name: Create certificate signing request (CSR) for new certificate
  community.crypto.openssl_csr_pipe:
    privatekey_path: /path/to/certificate.key
    subject_alt_name:
      - "DNS:ansible.com"
      - "DNS:www.ansible.com"
      - "DNS:docs.ansible.com"
  delegate_to: server_1
  run_once: true
  register: csr

- name: Check whether certificate exists
  stat:
    path: /path/to/certificate.pem
  delegate_to: server_1
  run_once: true
  register: certificate_exists

- name: Read existing certificate if exists
  slurp:
    src: /path/to/certificate.pem
  when: certificate_exists.stat.exists
  delegate_to: server_1
  run_once: true
  register: certificate

- name: Sign certificate with our CA
  community.crypto.x509_certificate_pipe:
    content: "{{ (certificate.content | b64decode) if certificate_exists.stat.exists else omit }}"
    csr_content: "{{ csr.csr }}"
    provider: ownca
    ownca_path: /path/to/ca-certificate.pem
    ownca_privatekey_path: /path/to/ca-certificate.key
    ownca_privatekey_passphrase: "{{ secret_ca_passphrase }}"
    ownca_not_after: +365d  # valid for one year
    ownca_not_before: "-1d"  # valid since yesterday
  delegate_to: server_2
  run_once: true
  register: certificate

- name: Write certificate file on server_1
  copy:
    dest: /path/to/certificate.pem
    content: "{{ certificate.certificate }}"
  delegate_to: server_1
  run_once: true
  when: certificate is changed