如何创建一个小型 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