diff --git a/docs/docsite/extra-docs.yml b/docs/docsite/extra-docs.yml new file mode 100644 index 000000000..d53eb0ea9 --- /dev/null +++ b/docs/docsite/extra-docs.yml @@ -0,0 +1,6 @@ +--- +sections: + - title: Scenario Guides + toctree: + - guide_selfsigned + - guide_ownca diff --git a/docs/docsite/rst/guide_ownca.rst b/docs/docsite/rst/guide_ownca.rst new file mode 100644 index 000000000..0c2ed4661 --- /dev/null +++ b/docs/docsite/rst/guide_ownca.rst @@ -0,0 +1,148 @@ +.. _ansible_collections.community.crypto.docsite.guide_ownca: + +How to create a small CA +======================== + +The `community.crypto collection `_ offers multiple modules that create private keys, certificate signing requests, and certificates. This guide shows how to create your own small CA and how to use it to sign certificates. + +In all examples, we assume that the CA's private key is password protected, where the password is provided in the ``secret_ca_passphrase`` variable. + +Set up the CA +------------- + +Any certificate can be used as a CA certificate. You can create a self-signed certificate (see :ref:`ansible_collections.community.crypto.docsite.guide_selfsigned`), use another CA certificate to sign a new certificate (using the instructions below for signing a certificate), ask (and pay) a commercial CA to sign your CA certificate, etc. + +The following instructions show how to set up a simple self-signed CA certificate. + +.. code-block:: yaml+jinja + + - 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: yes + 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 + +Use the CA to sign a certificate +-------------------------------- + +To sign a certificate, you must pass a CSR to the :ref:`community.crypto.x509_certificate module ` or :ref:`community.crypto.x509_certificate_pipe module `. + +In the following example, we assume that the certificate to sign (including its private key) are on ``server_1``, while our CA certificate is on ``server_2``. We do not want any key material to leave each respective server. + +.. code-block:: yaml+jinja + + - 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: "{{ ca_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 + +Please note that the above procedure is **not idempotent**. The following extended example reads the existing certificate from ``server_1`` (if exists) and provides it to the :ref:`community.crypto.x509_certificate_pipe module `, and only writes the result back if it was changed: + +.. code-block:: yaml+jinja + + - 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: "{{ ca_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 diff --git a/docs/docsite/rst/guide_selfsigned.rst b/docs/docsite/rst/guide_selfsigned.rst new file mode 100644 index 000000000..a9c300693 --- /dev/null +++ b/docs/docsite/rst/guide_selfsigned.rst @@ -0,0 +1,60 @@ +.. _ansible_collections.community.crypto.docsite.guide_selfsigned: + +How to create self-signed certificates +====================================== + +The `community.crypto collection `_ offers multiple modules that create private keys, certificate signing requests, and certificates. This guide shows how to create self-signed certificates. + +For creating any kind of certificate, you always have to start with a private key. You can use the :ref:`community.crypto.openssl_privatekey module ` to create a private key. If you only specify ``path``, the default parameters will be used. This will result in a 4096 bit RSA private key: + +.. code-block:: yaml+jinja + + - name: Create private key (RSA, 4096 bits) + community.crypto.openssl_privatekey: + path: /path/to/certificate.key + +You can specify ``type`` to select another key type, ``size`` to select a different key size (only available for RSA and DSA keys), or ``passphrase`` if you want to store the key password-protected: + +.. code-block:: yaml+jinja + + - name: Create private key (X25519) with password protection + community.crypto.openssl_privatekey: + path: /path/to/certificate.key + type: X25519 + passphrase: changeme + +To create a very simple self-signed certificate with no specific information, you can proceed directly with the :ref:`community.crypto.x509_certificate module `: + +.. code-block:: yaml+jinja + + - name: Create simple self-signed certificate + community.crypto.x509_certificate: + path: /path/to/certificate.pem + privatekey_path: /path/to/certificate.key + provider: selfsigned + +(If you used ``passphrase`` for the private key, you have to provide ``privatekey_passphrase``.) + +You can use ``selfsigned_not_after`` to define when the certificate expires (default: in roughly 10 years), and ``selfsigned_not_before`` to define from when the certificate is valid (default: now). + +To define further properties of the certificate, like the subject, Subject Alternative Names (SANs), key usages, name constraints, etc., you need to first create a Certificate Signing Request (CSR) and provide it to the :ref:`community.crypto.x509_certificate module `. If you do not need the CSR file, you can use the :ref:`community.crypto.openssl_csr_pipe module ` as in the example below. (To store it to disk, use the :ref:`community.crypto.openssl_csr module ` instead.) + +.. code-block:: yaml+jinja + + - name: Create certificate signing request (CSR) for self-signed certificate + community.crypto.openssl_csr_pipe: + privatekey_path: /path/to/certificate.key + common_name: ansible.com + organization_name: Ansible, Inc. + subject_alt_name: + - "DNS:ansible.com" + - "DNS:www.ansible.com" + - "DNS:docs.ansible.com" + register: csr + + - name: Create self-signed certificate from CSR + community.crypto.x509_certificate: + path: /path/to/certificate.pem + csr_content: "{{ csr.csr }}" + privatekey_path: /path/to/certificate.key + provider: selfsigned diff --git a/tests/sanity/extra/extra-docs.json b/tests/sanity/extra/extra-docs.json new file mode 100644 index 000000000..a62ef37e6 --- /dev/null +++ b/tests/sanity/extra/extra-docs.json @@ -0,0 +1,10 @@ +{ + "include_symlinks": false, + "prefixes": [ + "docs/docsite/" + ], + "output": "path-line-column-message", + "requirements": [ + "antsibull" + ] +} diff --git a/tests/sanity/extra/extra-docs.py b/tests/sanity/extra/extra-docs.py new file mode 100755 index 000000000..f4b7f59d3 --- /dev/null +++ b/tests/sanity/extra/extra-docs.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +"""Check extra collection docs with antsibull-lint.""" +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import sys +import subprocess + + +def main(): + """Main entry point.""" + if not os.path.isdir(os.path.join('docs', 'docsite')): + return + p = subprocess.run(['antsibull-lint', 'collection-docs', '.'], check=False) + if p.returncode not in (0, 3): + print('{0}:0:0: unexpected return code {1}'.format(sys.argv[0], p.returncode)) + + +if __name__ == '__main__': + main()