Skip to content

Commit

Permalink
Merge pull request #58 from maxhoesel/acme_cert_improvements
Browse files Browse the repository at this point in the history
Move step user to root and improve ACME role
  • Loading branch information
maxhoesel authored May 7, 2021
2 parents 95238c1 + 12672c9 commit 1885f43
Show file tree
Hide file tree
Showing 26 changed files with 263 additions and 298 deletions.
102 changes: 67 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,24 @@ You can also install the most recent version of this collection by referencing t

`ansible-galaxy collection install git+https://github.com/maxhoesel/ansible-collection-smallstep`

## Usage
## Overview

In addition to a set of modules that wrap around `step-cli` commands to perform typical `step-cli`/`ca` operations,
this collection also contains several roles that run more broad tasks related to `step-cli` and `step-ca`. They are:
This collection contains several roles that run common tasks related to `step-cli` and `step-ca`. They are:

- `step_ca`: Install and initialize a step certificate authority on a host
- `step_bootstrap_host`: Initialize a host to trust an existing step CA and create a service user for communicating with the CA via `step-cli`
- `step_acme_cert`: Generate and install and ACME certificate for a host from an existing step CA. Also sets up automatic renewal.
- `step_cli`: Install the `step-cli` client on a host. This role is used by `step_ca` and `step_bootstrap_host` and
it is recommended that you use these roles unless you really only want `step-cli` for some reason

The `step_cli` role is used by the other roles to install the step client tool. While you can run it on its own, this will not initialize your host to trust your CA.

### Basic setup
Additionally, this collection contains several modules that you can use to configure your CA, get certificates and perform various other tasks.
These generally required the `step-cli` tool to be present and the host to trust the remote CA. You can do this with `step_bootstrap_host`.

In this scenario you want to create an internal CA for a group of hosts to trust and get TLS certs from.
Your inventory will probably look a little like the one below:
If you'd like to know more about an individual module, you can view its documentation using `ansible-doc maxhoesel.smallstep.<step_module_name>`.

## Getting started

Let's say you have a set of hosts and a separate CA that you want these hosts to trust. To achieve this, you can follow the steps below

```
all:
Expand All @@ -64,8 +66,21 @@ all:
hostc.localdomain
```

First, you will need to create a step CA on the CA host.
Below is a simple example for how to do so (check the `step_ca` docs for more details):
### Create a CA

If you are starting from scratch, you will need to create a new step CA first.
Luckily, this collection has a role just for that - `step_ca`. This role will install and initialize a CA for you,
which you can then configure however you want. It also installs the `step-cli` tool, allowing you to manage your CA via the modules in this collection.
Below is a simple example for how to do so. If you want to know more, check out the documentation for `step_ca` and the `step_ca_provisioner(_claims)` modules.

---
**NOTE**

Please make sure that you have read the [considerations](https://smallstep.com/docs/step-ca/certificate-authority-server-production) for running a step-ca server in production.
`step_ca` follows these considerations where possible, but you should still be familiar with the basic operation of the `step-ca` server.
See the `step_ca` documentation for more details on how private keys are handled.

---

`ca.yml`:

Expand Down Expand Up @@ -93,7 +108,13 @@ Below is a simple example for how to do so (check the `step_ca` docs for more de
msg: "Fingerprint of root cert: {{ root_ca_fp.stdout }}"
```
Now that your CA is up and running, it's time to configure the clients to trust the CA:
### Bootstrap the clients
To establish trust between your clients and the CA, you will need the fingerprint of the CA root cert - see the [Create a CA](#create-a-ca) section for more details.
This fingerprint identifies your CA to your clients and allows them to verify the CA cert.
To actually initialize the clients, you can use `step_bootstrap_host`. This role will install `step-cli` and configure the host to trust your CA.

`clients.yml`:

Expand All @@ -109,51 +130,63 @@ Now that your CA is up and running, it's time to configure the clients to trust
step_bootstrap_ca_url: https://my-ca.localdomain
step_bootstrap_fingerprint: "your root CA certs fingerprint"
# `step` is the default service user created by step_bootstrap_host. This user
# is configured to access the CA and can be used to get certs, check the CA status and so on.
- name: Verify that everything is working
command: step-cli ca health
changed_when: no
become_user: step
become: yes
```

---
**NOTE**

If you want to access the CA from your clients CLI at a later point, you need to either run `step-cli` as root or specify the CA url and cert with the `--ca-url` and `--root` flags.
This is because `step_bootstrap_host` can automatically configure the root user to trust your CA, but it can't do so for other users on the system.

---

At this point, your CA is up and running and your hosts are configured to trust it. You're ready to go!
You can take a look at the available modules to further configure your CA and hosts if you whish to do so.

### About step-cli config and service users
### Using Modules

Most of the modules in this collection wrap around the `step-cli` command, which reads its configuration from
`$STEPPATH` (`~/.step` by default). Alternatively, it is possible to pass required configuration parameters via command-line args.
Most of the modules in this collection wrap around the `step-cli` tool. Note that there are two kinds of modules - those dealing with a CA, remote or local (named `step_ca_<action>`)
and those dealing with local, standalone actions (named `step_<action>`, TBD).

This collection makes things easy for you by installing a cli and ca user with the `step_bootstrap_host` and `step_ca` roles respectively
These users are named `step` and `step-ca` and can access the CA remotely/locally without any further configuration required.
To run the CA modules, you need to provide them with information about your CA. There's several options to do this:

You can also pass the required parameters via module args (e.g. `ca_url` for remote access or `ca_config` if the CA is local), but
this is not recommended.
- Use the root user on a host bootstrapped with `step_bootstrap_host`. This role configures the root user to trust your CA, so you can run `step-cli` commands/modules as root without issues
- Use the `ca_config` and `root` module parameters to specify the CA url and root certificate.
- Point them to your CAs config file with `ca_config` (and potentially also the `offline` flag). This obviously only works on your CA host and requires that you run the module as the CA user (`step-ca` if installed using this collection)

---
**NOTE**

Some modules can be run both remotely via the client and directly on the CA (e.g. `step_ca_certificate`), while others are remote/local-only
Most CA modules can be run both remotely via the client and directly on the CA (e.g. `step_ca_certificate`), but others are remote/local-only
(e.g. `step_ca_bootstrap` is remote-only, while `step_ca_provisioner` is local-only). See the module documentation for details.

---

```yaml
- hosts: all
become: yes
- hosts: ca
tasks:
- name: Run a step-cli command
command: step-cli ca health
become_user: step
- name: Run a module instead
- name: Run a module by specifying the CA URL and CA cert
maxhoesel.smallstep.step_ca_certificate:
root: /etc/ssl/myca.crt
ca_url: https://my-ca.localdomain
#params go here
# This will only work if you ran step_bootstrap_host on this host first!
- name: Run a module as root to use the CA configured during bootstrapping
maxhoesel.smallstep.step_ca_certificate:
# module args
become_user: step
#params go here
become: yes
- name: run a module against a local CA
- name: Run a module against a locally installed CA
maxhoesel.smallstep.step_ca_provisioner:
# module args
ca_config: /etc/step-ca/config/ca.json
#params go here
# You should run modules acting on a local CA as the user that the CA runs as.
# If you configured your CA with `step_ca`, the default user name is `step-ca`.
become_user: step-ca
```
Expand Down Expand Up @@ -190,16 +223,15 @@ to make use of ACME certs.
state: reloaded
- hosts: clients
become: yes
tasks:
# This will download a certificate to /etc/step/ that you can then use in other applications.
# See the step_acme_cert README for more options
- name: Configure an ACME cert + renewal
include_tasks:
include_role:
name: maxhoesel.smallstep.step_acme_cert
vars:
step_acme_cert_ca_provisioner: ACME
become_user: step
become: yes
```

Expand Down
3 changes: 2 additions & 1 deletion galaxy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ tags: ["smallstep", "ca", "certificates"]
# collection label 'namespace.name'. The value is a version range
# L(specifiers,https://python-semanticversion.readthedocs.io/en/latest/#requirement-specification). Multiple version
# range specifiers can be set and are separated by ','
dependencies: {}
dependencies:
"community.crypto": ">=1.0,<2.0"

# The URL of the originating SCM repository
repository: https://github.com/maxhoesel/ansible-collection-smallstep
Expand Down
36 changes: 13 additions & 23 deletions roles/step_acme_cert/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ before setting up a renewal service using `step-cli ca renew`s `--daemon` mode.
- Debian 10 or newer
- CentOS 8 or newer
- This role requires root access. Make sure to run this role with `become: yes` or equivalent
- `step-cli` must be installed.
- The host must be bootstrapped with `step_bootstrap_host`.
In particular, `step_service_user` must have a functional `step-cli` environment.
- The host must be bootstrapped with `step_bootstrap_host` and the root user must be able to access the CA.

## Role Variables

Expand All @@ -25,12 +23,6 @@ before setting up a renewal service using `step-cli ca renew`s `--daemon` mode.
- Can be an absolute path or a command (make sure the executable is in $PATH) for all users
- Default: `step-cli`

##### `step_service_user`
- User that will perform the ACME request and run the renewal job
- The user must already exist
- Use the `step_bootstrap_host` role to create and initialize the service user
- Default: `step`

### CA

##### `step_acme_cert_ca_provisioner`
Expand All @@ -39,7 +31,8 @@ before setting up a renewal service using `step-cli ca renew`s `--daemon` mode.

##### `step_acme_cert_webroot_path`
- If set, this role will use `step-cli`s webroot mode to get a new certificate. You need to make sure that `step_service_user` has write permissions in this directory.
- If empty, this role will use the standalone mode instead, causing `step-cli` to bind itself to port 80. The `step-cli` binary installed via the `step_cli` role already has the required capabilites to do so regardless of the user running it. If you installed `step-cli` yourself, you will need to make sure that the executable can bind to port 80 via some other means.
- If empty, this role will use the standalone mode instead, causing `step-cli` to bind itself to port 80. Make sure that no other services are listening on this port.
Note that `step-cli` only needs to bind to this port when getting a *new* certificate. It does not need to bind if it is only *renewing* a certificate.
- Default: ""

### Certificate
Expand All @@ -55,7 +48,7 @@ before setting up a renewal service using `step-cli ca renew`s `--daemon` mode.

##### `step_acme_cert_duration`
- Valid duration of the certificate
- Default: `24h` (the step-ca default value)
- Default: undefined (uses the default for the given provisioner, typically 24h)

##### `step_acme_cert_contact`
- Contact email for the CA for important notifications
Expand All @@ -64,12 +57,9 @@ before setting up a renewal service using `step-cli ca renew`s `--daemon` mode.
##### `step_acme_cert_certfile`/`step_acme_cert_keyfile`
- Details about the cert/key files on disk
- Is a dict with the following elements:
- `path`: Absolute path to the cert/key file. Defaults to `/etc/step/step.crt|step.key`, which is created by `step_bootstrap_host`.
The directories must already exist.
- `path`: Absolute path to the cert/key file. Defaults to `/etc/ssl/step.crt|step.key`. The directory must already exist.
- `mode`: File mode for the cert/key file. Defaults to `644` for the cert and `600` for the key
- `owner`/`group`: Owner and group of the file. Defaults to `{{ step_service_user }}`.
- Make sure that `{{ step_service_user }}` can write still write to the cert/key file if you decide to change the `mode`/`owner`/`group` parameters,
or renewal might fail.
- `owner`/`group`: Owner and group of the file. Defaults to root.

### Renewal

Expand All @@ -79,12 +69,12 @@ before setting up a renewal service using `step-cli ca renew`s `--daemon` mode.
- Default: `step-renew`

##### `step_acme_cert_renewal_when`
- Renew the cert when it expires in this amount of time
- Default: `8h` (1/3 of the 24h step-ca default)
- Renew the cert when its remaining valid time crosses this threshold
- Default: undefined (uses the smallstep default: 1/3 of the certificates valid duration, i.e. 8 hours for a 24h cert)

##### `step_acme_cert_renewal_reload_services`
- Reload or restart these systemd services upon a cert renewal
- Must be a list of services
- Reload or restart these systemd services after a cert renewal
- Must be a list of systemd units
- Example: `["nginx", "mysqld"]`
- Default: `[]`

Expand All @@ -93,7 +83,7 @@ before setting up a renewal service using `step-cli ca renew`s `--daemon` mode.
```
- hosts: all
roles:
# Bootstrap the host to create the service user
# Bootstrap the host
- role: maxhoesel.smallstep.step_bootstrap_host
vars:
step_bootstrap_ca_url: https://myca.localdomain
Expand All @@ -103,7 +93,7 @@ before setting up a renewal service using `step-cli ca renew`s `--daemon` mode.
- role: maxhoesel.smallstep.step_acme_cert
vars:
step_acme_cert_ca_provisioner: ACME
# Use webroot instead of standalone, as that requires binding to port 80 -> root. Make sure that the user has write permissions to the webroot
step_acme_cert_webroot_path: /var/www/html
# Use webroot instead of standalone if you are running a HTTP server
#step_acme_cert_webroot_path: /var/www/html
become: yes
```
17 changes: 8 additions & 9 deletions roles/step_acme_cert/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
---
step_cli_executable: step-cli
step_service_user: step

#step_acme_cert_ca_url:
#step_acme_cert_ca_provisioner:
step_acme_cert_webroot_path: ""

step_acme_cert_name: "{{ ansible_fqdn }}"
step_acme_cert_san: []
step_acme_cert_duration: 24h
#step_acme_cert_duration: 24h
step_acme_cert_contact: root@localhost

step_acme_cert_certfile:
path: /etc/step/step.crt
path: /etc/ssl/step.crt
mode: "644"
owner: "{{ step_service_user }}"
group: "{{ step_service_user }}"
owner: root
group: root
step_acme_cert_keyfile:
path: /etc/step/step.key
path: /etc/ssl/step.key
mode: "600"
owner: "{{ step_service_user }}"
group: "{{ step_service_user }}"
owner: root
group: root

step_acme_cert_renewal_service: step-renew
step_acme_cert_renewal_when: 8h
#step_acme_cert_renewal_when: 8h
step_acme_cert_renewal_reload_services: []
8 changes: 4 additions & 4 deletions roles/step_acme_cert/molecule/default/converge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@
step_acme_cert_webroot_path: "{{ webroots[ansible_os_family] }}"
step_acme_cert_duration: 1h
step_acme_cert_certfile:
path: /etc/step/step.crt
path: /etc/ssl/step.crt
mode: "644"
owner: "{{ step_service_user }}"
owner: root
group: "{{ webgroup[ansible_os_family] }}"
step_acme_cert_keyfile:
path: /etc/step/step.key
path: /etc/ssl/step.key
mode: "640"
owner: "{{ step_service_user }}"
owner: root
group: "{{ webgroup[ansible_os_family] }}"
step_acme_cert_renewal_service: step-renew-webroot
step_acme_cert_renewal_when: 59m # force renewal to happen every minute
Expand Down
4 changes: 2 additions & 2 deletions roles/step_acme_cert/molecule/default/files/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ http {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;

ssl_certificate /etc/step/step.crt;
ssl_certificate_key /etc/step/step.key;
ssl_certificate /etc/ssl/step.crt;
ssl_certificate_key /etc/ssl/step.key;

location / {
}
Expand Down
4 changes: 2 additions & 2 deletions roles/step_acme_cert/molecule/default/files/nginx_site.conf
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ server {

root /var/www/html;

ssl_certificate /etc/step/step.crt;
ssl_certificate_key /etc/step/step.key;
ssl_certificate /etc/ssl/step.crt;
ssl_certificate_key /etc/ssl/step.key;

index index.html index.htm index.nginx-debian.html;

Expand Down
3 changes: 0 additions & 3 deletions roles/step_acme_cert/molecule/default/prepare.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@
- "existing.crt"
- "existing.key"

- name: Install step-cli
include_role:
name: step_cli
- name: Install step-ca
include_role:
name: step_ca
Expand Down
3 changes: 1 addition & 2 deletions roles/step_acme_cert/tasks/get_cert.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@
key_file: "{{ step_acme_cert_keyfile.path }}"
force: yes
name: "{{ step_acme_cert_name }}"
not_after: "{{ step_acme_cert_duration }}"
not_after: "{{ step_acme_cert_duration|default(omit) }}"
san: "{{ step_acme_cert_san }}"
standalone: "{{ step_acme_cert_webroot_path | bool }}"
step_cli_executable: "{{ step_cli_executable }}"
webroot: "{{ step_acme_cert_webroot_path }}"
become: yes
become_user: "{{ step_service_user }}"

- name: Cert and key permissions are set
file:
Expand Down
10 changes: 9 additions & 1 deletion roles/step_acme_cert/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@
path: "{{ step_acme_cert_certfile.path }}"
register: step_acme_cert_current_cert

- name: Check if certificate is expired
community.crypto.x509_certificate_info:
path: "{{ step_acme_cert_certfile.path }}"
valid_at:
now: "+0s"
register: step_acme_cert_certinfo
when: step_acme_cert_current_cert.stat.exists

- include: get_cert.yml
when: not step_acme_cert_current_cert.stat.exists
when: not step_acme_cert_current_cert.stat.exists or not step_acme_cert_certinfo.valid_at.now

- include: renewal.yml
Loading

0 comments on commit 1885f43

Please sign in to comment.