Skip to content

Commit

Permalink
[Roles] Use non-root users for step with bootstrap and acme_cert roles (
Browse files Browse the repository at this point in the history
#307)

* WIP

* WIP

* idempotency fix

* WIP

* WIP
  • Loading branch information
maxhoesel authored Jul 25, 2023
1 parent e6c6111 commit 0560b2f
Show file tree
Hide file tree
Showing 32 changed files with 568 additions and 112 deletions.
14 changes: 1 addition & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,21 +131,9 @@ Below are some examples to showcase the different options. These examples assume
become_user: step-ca
```
### About `$STEPPATH`

All modules in this collection respect the `$STEPPATH` environment variable used to customize the step-cli config directory.
If you want to use a custom `$STEPPATH` for your environment, you can use the `step_cli_steppath` role variables
and the `environment` ansible parameter for modules:

All modules in this collection respect the `$STEPPATH` environment variable used to customize the step-cli config directory:

```yaml
- name: Initialize a host with a custom STEPPATH
maxhoesel.smallstep.step_bootstrap_host:
vars:
step_bootstrap_ca_url: https://my-ca.localdomain
step_bootstrap_fingerprint: "your root CA certs fingerprint"
step_cli_steppath: /etc/step-cli
- name: Use the custom $STEPPATH in a module
maxhoesel.smallstep.step_ca_certificate:
# params go here
Expand Down
3 changes: 2 additions & 1 deletion galaxy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ build_ignore:
- requirements.txt
- tox.ini
- '**/requirements.txt'
dependencies: {}
dependencies:
community.general: ">=1.0.0"
description: Install, configure and use the Smallstep CA server and CLI tool
issues: https://github.com/maxhoesel-ansible/ansible-collection-smallstep/issues
license_file: LICENSE
Expand Down
28 changes: 23 additions & 5 deletions roles/step_acme_cert/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ Get a certificate from a CA with ACME and setup automatic renewal using `step-cl
This role uses `step-cli` to request and save a certificate from the configured CA,
before setting up a renewal service using `step-cli ca renew`s `--daemon` mode.

Note that this is not the only way to use ACME with step-ca!
You can also configure a traditional ACME client such as certbot, Caddy or acme.sh to talk to step-ca.
[This post](https://smallstep.com/blog/private-acme-server/) shows some examples, you can use other Ansible content to automate this.
The advantage of the `step` method is that no additional tools are required.

## Requirements

- The following distributions are currently supported:
Expand All @@ -13,7 +18,7 @@ before setting up a renewal service using `step-cli ca renew`s `--daemon` mode.
- Fedora 36 or newer
- A CentOS-compatible distribution like RockyLinux/AlmaLinux 8 or newer. RockyLinux is used for testing
- This role requires root access. Make sure to run this role with `become: yes` or equivalent
- The host must be bootstrapped with `step_bootstrap_host` and the root user must be able to access the CA.
- The host must be bootstrapped with `step_bootstrap_host` and at least one user must be able to access the CA.

## Role Variables

Expand All @@ -24,8 +29,15 @@ 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_cli_steppath`
- Optionally set a custom `$STEPPATH` from which to read the step config
##### `step_acme_cert_user`
- The user account that will generate, own and renew the certificate
- This user must have been boostrapped with `step_bootstrap_host` before
- Default: `root`

##### `step_acme_cert_steppath`
- Set this if `step_acme_cert_user` requires a custom `$STEPPATH` from which to read the step config
- ⚠️ Deprecated ⚠️ If `step_acme_cert_user` ir `root` and `step_cli_steppath` is set, this role will read the users steppath from it.
This behavior exists to preserve backwards-compatibility with older role versions that could only use the root user and will be removed in a future release.
- Example: `/etc/step-cli`
- Default: `$HOME/.step/`

Expand Down Expand Up @@ -63,12 +75,16 @@ 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/ssl/step.crt|step.key`. The directory must already exist.
- `path`: Absolute path to the cert/key file. Defaults to `/etc/ssl/step.crt|step.key`. The directory must already exist and the user must have write access
- `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 root.
- `owner`/`group`: Owner and group of the file. Defaults to `step_acme_cert_user`.

### Renewal

This role configures automatic cert renewal using a systemd service.
The service will monitor the certificate using `step-cli ca renew`s deamon mode and renew it when its expiry time approaches.
The daemon will run as the user defined in `step_acme_cert_user`.

##### `step_acme_cert_renewal_service`
- Name of the systemd service that will handle cert renewals
- If you have multiple cert/key pairs on one system, you will have to set a unique service name for each pair. If you only have one, then you can leave this as is.
Expand All @@ -81,6 +97,8 @@ before setting up a renewal service using `step-cli ca renew`s `--daemon` mode.
##### `step_acme_cert_renewal_reload_services`
- Reload or restart these systemd services after a cert renewal
- Must be a list of systemd units
- If `step_acme_cert_user` is not root, a sudoers entry will be added to permit the user to reload-restart these service units.
This sudoers policy is restricted to the single command needed to achieve this. Requires `sudo` to be installed
- Example: `["nginx", "mysqld"]`
- Default: `[]`

Expand Down
12 changes: 7 additions & 5 deletions roles/step_acme_cert/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
---
step_cli_executable: step-cli
step_cli_steppath: "{{ ansible_env.HOME }}/.step"

step_acme_cert_user: root
step_acme_cert_steppath: "{{ ansible_env.HOME }}/.step"

#step_acme_cert_ca_provisioner:
step_acme_cert_webroot_path: ""
Expand All @@ -14,14 +16,14 @@ step_acme_cert_certfile: "{{ step_acme_cert_certfile_defaults }}"
step_acme_cert_certfile_defaults:
path: /etc/ssl/step.crt
mode: "644"
owner: root
group: root
owner: "{{ step_acme_cert_user }}"
group: "{{ step_acme_cert_user }}"
step_acme_cert_keyfile: "{{ step_acme_cert_keyfile_defaults }}"
step_acme_cert_keyfile_defaults:
path: /etc/ssl/step.key
mode: "600"
owner: root
group: root
owner: "{{ step_acme_cert_user }}"
group: "{{ step_acme_cert_user }}"

step_acme_cert_renewal_service: step-renew
#step_acme_cert_renewal_when: 8h
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,36 +22,24 @@
include_role:
name: step_acme_cert
vars:
step_service_user: step
step_acme_cert_webroot_path: "{{ webroots[ansible_os_family] }}"
step_acme_cert_duration: 1h
step_acme_cert_certfile:
# Lazy evaluation testing
#path: /etc/ssl/step.crt
mode: "644"
owner: root
group: "{{ webgroup[ansible_os_family] }}"
step_acme_cert_keyfile:
#path: /etc/ssl/step.key
mode: "640"
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
step_acme_cert_renewal_reload_services: [nginx]

- name: Install Nginx site [Debian]
copy:
src: nginx_site.conf
template:
src: ../templates/nginx_site.conf
dest: /etc/nginx/sites-enabled/default
owner: root
group: root
mode: "644"
notify: restart nginx
when: ansible_os_family == "Debian"
- name: Install Nginx config [RedHat]
copy:
src: nginx.conf
template:
src: ../templates/nginx.conf
dest: /etc/nginx/nginx.conf
owner: root
group: root
Expand Down
14 changes: 14 additions & 0 deletions roles/step_acme_cert/molecule/default/molecule.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,25 @@ platforms:
network: molecule-step-acme-cert

provisioner:
playbooks:
verify: ../verify.yml
converge: ../converge.yml
inventory:
group_vars:
ca:
step_ca_user: step-ca
all:
# Test legacy steppath behavior
step_cli_steppath: /etc/step-cli-molecule
step_acme_cert_ca_provisioner: ACME
step_bootstrap_ca_url: https://step-ca:9000

certfile: /etc/ssl/step.crt
keyfile: /etc/ssl/step.key

step_acme_cert_certfile:
mode: "644"
group: "{{ webgroup[ansible_os_family] }}"
step_acme_cert_keyfile:
mode: "640"
group: "{{ webgroup[ansible_os_family] }}"
119 changes: 119 additions & 0 deletions roles/step_acme_cert/molecule/non_root/molecule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
platforms:
# Use the smallstep-provided CA image so that we don't have to set up the CA ourselves
- name: step-ca
groups:
- ca
image: "docker.io/smallstep/step-ca:${STEP_CA_VERSION}"
# we don't actually use the container with ansible, leave it as is
override_command: false
pre_build_image: true
env:
DOCKER_STEPCA_INIT_NAME: "Molecule_Bootstrap_CA"
DOCKER_STEPCA_INIT_DNS_NAMES: "step-ca,localhost"
network: molecule-step-acme-cert

- name: step-host-ubuntu-22
groups:
- clients
- ubuntu
image: "docker.io/geerlingguy/docker-ubuntu2204-ansible"
systemd: always
override_command: false
pre_build_image: true
network: molecule-step-acme-cert

- name: step-host-ubuntu-20
groups:
- clients
- ubuntu
image: "docker.io/geerlingguy/docker-ubuntu2004-ansible"
systemd: always
override_command: false
pre_build_image: true
network: molecule-step-acme-cert

- name: step-host-ubuntu-18
groups:
- clients
- ubuntu
image: "docker.io/geerlingguy/docker-ubuntu1804-ansible"
systemd: always
override_command: false
pre_build_image: true
network: molecule-step-acme-cert

- name: step-host-debian-11
groups:
- clients
- debian
image: "docker.io/geerlingguy/docker-debian11-ansible"
systemd: always
override_command: false
pre_build_image: true
network: molecule-step-acme-cert

- name: step-host-debian-10
groups:
- clients
- debian
image: "docker.io/geerlingguy/docker-debian10-ansible"
systemd: always
override_command: false
pre_build_image: true
network: molecule-step-acme-cert

- name: step-host-rockylinux-9
groups:
- clients
- rockylinux
image: "docker.io/geerlingguy/docker-rockylinux9-ansible"
systemd: always
override_command: false
pre_build_image: true
network: molecule-step-acme-cert

- name: step-host-rockylinux-8
groups:
- clients
- rockylinux
image: "docker.io/geerlingguy/docker-rockylinux8-ansible"
systemd: always
override_command: false
pre_build_image: true
network: molecule-step-acme-cert

- name: step-host-fedora-36
groups:
- clients
- fedora
image: "docker.io/geerlingguy/docker-fedora36-ansible"
systemd: always
override_command: false
pre_build_image: true
network: molecule-step-acme-cert

provisioner:
playbooks:
verify: ../verify.yml
converge: ../converge.yml
inventory:
group_vars:
ca:
step_ca_user: step-ca
all:
step_acme_cert_user: max
step_acme_cert_steppath: "/home/max/custom-steppath"
step_acme_cert_ca_provisioner: ACME
step_bootstrap_ca_url: https://step-ca:9000

certfile: /etc/ssl/non-root/nginx-test.cert
keyfile: /etc/ssl/non-root/nginx-test.key

step_acme_cert_certfile:
path: /etc/ssl/non-root/nginx-test.cert
mode: "644"
group: "{{ webgroup[ansible_os_family] }}"
step_acme_cert_keyfile:
path: /etc/ssl/non-root/nginx-test.key
mode: "640"
group: "{{ webgroup[ansible_os_family] }}"
68 changes: 68 additions & 0 deletions roles/step_acme_cert/molecule/non_root/prepare.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
- hosts: ubuntu:debian
tasks:
- name: Update apt
apt:
update_cache: yes

- hosts: rockylinux:fedora
tasks:
# Required to prevent issues with ansible_default_ipv4 missing
- name: Install iproute
package:
name: iproute

- hosts: localhost
tasks:
# Somewhat hacky way to customize the container image, but we make do with what we have
- name: Add ACME provisioner # noqa no-changed-when
ansible.builtin.command: "podman exec step-ca step ca provisioner add {{ step_acme_cert_ca_provisioner }} --type=ACME"
- name: Get CA PID
ansible.builtin.command: podman exec step-ca pgrep -f step-ca
register: _step_ca_pid
changed_when: false
- name: Reload step-ca # noqa no-changed-when
ansible.builtin.command: "podman exec step-ca kill -1 {{ _step_ca_pid.stdout }}"

- hosts: clients
vars:
webgroup:
Debian: www-data
RedHat: nginx
tasks:
- name: Install nginx
package:
name: nginx
- name: Stop nginx
systemd:
name: nginx
state: stopped
enabled: no

- name: Create test user
ansible.builtin.user:
name: max
create_home: yes
- name: Create certificate directory
ansible.builtin.file:
state: directory
path: /etc/ssl/non-root
owner: max
group: "{{ webgroup[ansible_os_family] }}"
mode: "750"

- name: Get CA fingerprint
ansible.builtin.command: podman exec step-ca step certificate fingerprint certs/root_ca.crt
register: _ca_fingerprint
changed_when: false
check_mode: false
run_once: true
delegate_to: localhost

- name: Bootstrap host
include_role:
name: maxhoesel.smallstep.step_bootstrap_host
vars:
step_bootstrap_fingerprint: "{{ _ca_fingerprint.stdout }}"
step_bootstrap_users:
- user: max
steppath: "{{ step_acme_cert_steppath }}"
1 change: 1 addition & 0 deletions roles/step_acme_cert/molecule/non_root/requirements.txt
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/ssl/step.crt;
ssl_certificate_key /etc/ssl/step.key;
ssl_certificate {{ certfile}};
ssl_certificate_key {{ keyfile }};

location / {
}
Expand Down
Loading

0 comments on commit 0560b2f

Please sign in to comment.