The role uses openssl_privatekey, openssl_csr, x509_certificate and x509_certificate_info, but neither declared
that collection anywhere nor mentioned it in the readme. A standalone role cannot express collection dependencies in
its metadata, so a requirements file next to the readme section is the usual way.
The section also documents two requirements that arose from the recent changes and were undocumented as well. Facts
have to be gathered, because the package names, the trust store anchor and the update command are resolved by
distribution, os_family and architecture. And the role needs become, since it writes into /etc and updates the trust
store.
No version is pinned. All used modules exist since community.crypto 1.0.0, so a lower bound would be arbitrary.
Co-authored-by: Copilot <copilot@github.com>
The scenario was only runnable by hand so far, which defeats its purpose. Podman is already installed and
configured on the runners, so the job only has to add molecule itself.
The repository is named ansible-role-certificate-authority while the role is named certificate_authority. Since the
scenario includes the role by its name and uses the parent of the project directory as roles path, the checkout has
to happen into a directory matching the role.
The collections are declared in molecule/default/collections.yml instead of being installed by an explicit step.
That is the path the collections invoker of molecule looks at by default, so the dependency action of the test
sequence installs them for the workflow and for local runs alike.
Co-authored-by: Copilot <copilot@github.com>
The scenario was added without any hint in the readme, so a contributor had to read molecule.yml to find out
which tools are needed and how to invoke them. Molecule ships only its default driver, therefore podman and the
collection containers.podman are prerequisites that are not obvious.
The section is placed above the parameters, because the readme generator treats the parameter section as the last
one in the file and replaces everything below it.
Co-authored-by: Copilot <copilot@github.com>
The role was only linted statically so far, which is why every bug of the recent analysis passed the ci
unnoticed. The scenario converges the role in podman containers of Archlinux, Debian and Fedora, checks
idempotence and then verifies the result.
Verification covers the chain via openssl verify, the file modes of keys, certificates and directories,
the number of certificates in the fullchain of the client, its subject alternative names and the anchor in
the systems trust store. Root and intermediate use a passphrase so the protected code paths are exercised
as well.
Molecule ships only the default driver, so create and destroy are provided as playbooks. Three details
were needed to make it work. The connection has to be declared in the instance config, since molecule
ignores ansible_connection_options of the driver. Raw commands are passed through sh explicitly, because
the podman connection plugin splits them instead of using a shell. And the roles path has to point at the
parent of the project directory, which is the role itself.
Co-authored-by: Copilot <copilot@github.com>
The update command ran on every play with changed_when tied to its return code, so the role never
reported a converged state. It is a handler now, notified by the anchor symlink and by the three tasks
which write cert.pem of the root certificate authority. Notifying from the certificate tasks as well keeps
the trust store correct on renewal, where the symlink itself stays untouched.
The anchor path and the update command moved into the per os_family vars, because a handler cannot read
the block vars they lived in before. Debian and RedHat need dedicated files again, they agree on the
package name but differ in anchor path, file extension and update command.
Co-authored-by: Copilot <copilot@github.com>
Building chain.pem, fullchain.pem and all.pem was implemented seven times across three task files with
identical stat, awk and copy tasks. The blocks now include tasks/concatenate.yaml and pass the sources, the
destination and the mode, which removes about a hundred lines.
Two side effects come with it. Every source file is checked instead of only the foreign one, so a missing
file skips the block instead of letting awk fail. And the trailing newline of the result is kept, because
stdout_lines joined by a newline dropped it.
Co-authored-by: Copilot <copilot@github.com>
The pki directories were created with mode 0700, so cert.pem, chain.pem and fullchain.pem stayed
unreachable for every non root process although they are declared as 0644. That defeats the main purpose of
the role, which is handing a certificate to a service such as nginx or postgres.
The directories are opened up to 0755. To keep that safe, the mode of the private keys is now pinned
explicitly to 0600 instead of relying on the default of community.crypto.openssl_privatekey.
Co-authored-by: Copilot <copilot@github.com>
Installing the python cryptography bindings with ansible.builtin.pip is rejected by PEP 668 on distributions
which mark their python installation as externally managed. Fedora 38+, Ubuntu 23.04+ and RHEL 10 are
affected, so the role aborted on its very first task there.
The package names are resolved from vars/ via first_found, which keeps distribution specifics out of the
task file. vars/main.yaml provides python3-cryptography as a fallback for every family without a dedicated
file, Archlinux overrides it with python-cryptography.
Co-authored-by: Copilot <copilot@github.com>
Several parameter descriptions were carried over from unrelated variables and
described the wrong attribute. Both country name parameters claimed to document
a Common Name, the client organizational unit name repeated the Common Name text
and the two client content parameters described a passphrase and a key
algorithm.
The client content parameters now follow the wording already used for their root
and intermediate counterparts. The README tables were regenerated with
readme-generator, which also normalises the column padding of the previously
hand-edited rows.
Additionally fix the misspelled repository owner in the homepage URL and a typo
in the galaxy description.
Co-authored-by: Copilot <copilot@github.com>
The task named "Upgrade python package manager pip" installed pip with
`state: present`, which never upgrades anything, and it did so through
`ansible.builtin.pip`. That module already requires a working pip on the target,
so the task could only ever run when its own result was already satisfied.
Co-authored-by: Copilot <copilot@github.com>
The `awk 1` commands assembling the chain, fullchain and all files declared
`changed_when: chain_content.rc == 0`, which holds on every successful run. As a
result the role never reported a converged state, even when no certificate was
touched.
These commands only read files and write to stdout, so mark them as unchanged.
The subsequent copy task remains responsible for reporting an actual change.
Co-authored-by: Copilot <copilot@github.com>
All tasks writing the concatenated chain, fullchain and all files pass the file
body via `content`. The `remote_src` option only governs how `src` is resolved
and is ignored in that case, so it merely suggested a behaviour the tasks never
had.
Co-authored-by: Copilot <copilot@github.com>
Issuing an intermediate certificate authority or a client certificate always
signs against the private key of the parent authority, regardless of whether
that authority is managed in the same run. With `certificate_authority_root_ca_skip`
or `certificate_authority_intermediate_ca_skip` enabled and no previously
provisioned key at the configured path, this surfaced as a generic module error
deep inside the signing task.
Stat the parent private key upfront and assert its presence, so the failure
names the missing path and the variables that control it. Skipping the parent
remains valid when its key already exists, because the check inspects the file
instead of the skip variable.
Co-authored-by: Copilot <copilot@github.com>
The trust store import hardcoded the RHEL anchor directory and
`update-ca-trust`, although `meta/main.yaml` also declares ArchLinux and Ubuntu
as supported platforms. On those distributions the task created a dangling
symlink outside any trust source and then failed on the missing binary.
Resolve both the anchor path and the update command from a map keyed by
`ansible_facts['os_family']`. Debian based systems additionally require the file
extension `crt`, because `update-ca-certificates` ignores anchors named
otherwise.
Note that the role now depends on gathered facts.
Co-authored-by: Copilot <copilot@github.com>
The previous wording exceeded the description column width generated by
readme-generator, so markdownlint reported MD060/table-column-style for all
three parameter tables. Shorten the hint to an `Example:` clause that fits the
existing column width and pad the cells accordingly, keeping the README stable
across regeneration.
Co-authored-by: Copilot <copilot@github.com>
The variables `certificate_authority_root_ca_subject_alternative_names` and
`certificate_authority_intermediate_ca_subject_alternative_names` were
documented but never referenced by any task, so both CA certificates were always
issued without SANs. Wire them into the corresponding CSR tasks and fall back to
`omit` when the list is empty.
SAN entries are now passed to `openssl_csr` unchanged instead of being prefixed
with `DNS:` by the role. This allows other types such as `IP:` or `email:`,
which the previous rewrite would have corrupted into values like `DNS:IP:...`.
The client tasks additionally dropped `join(',') | quote`, because `quote`
performs shell escaping and `openssl_csr` expects a list.
Since both client CSR tasks only differed in `subject_alt_name`, they collapse
into a single task per file.
BREAKING CHANGE: Entries of all `*_subject_alternative_names` variables must now
carry their type prefix, for example `DNS:example.local` instead of
`example.local`.
Co-authored-by: Copilot <copilot@github.com>
This task file is only included when
`certificate_authority_client_tls_key_passphrase` is empty, so the private key
is created without encryption. Passing the empty passphrase to `openssl_csr`
made the module attempt to decrypt an unencrypted key instead of treating it as
absent. The sibling CSR task for certificates with SANs already omitted the
attribute, so this also aligns both code paths.
Co-authored-by: Copilot <copilot@github.com>
The task importing the client certificate passed
`certificate_authority_client_tls_crt_content` to `dest`, so the PEM payload
itself was interpreted as a directory path. The certificate was written to a
bogus location derived from its own content instead of the configured client
directory. Use `certificate_authority_client_path` as destination, consistent
with the private key import above.
Co-authored-by: Copilot <copilot@github.com>
The task importing the client private key referenced
`certificate_authority_client_ca_path`, a variable that is declared neither in
`defaults/main.yaml` nor in `meta/argument_specs.yaml`. Every run with
`certificate_authority_client_create: false` therefore aborted with an undefined
variable error before the key was written. Use the actual role variable
`certificate_authority_client_path` instead.
Co-authored-by: Copilot <copilot@github.com>