Skip to content

Commit

Permalink
chore: copy step_acme_cert to seed generic step_certificate
Browse files Browse the repository at this point in the history
  • Loading branch information
eengstrom committed Oct 18, 2021
1 parent 57f8cec commit 13dae4b
Show file tree
Hide file tree
Showing 17 changed files with 714 additions and 0 deletions.
131 changes: 131 additions & 0 deletions roles/step_certificate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# maxhoesel.smallstep.step_acme_cert

Get a certificate from a CA with ACME and setup automatic renewal using `step-cli renew`.

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.

## Requirements

- The following distributions are currently supported:
- Ubuntu 18.04 LTS or newer
- Debian 10 or newer
- CentOS 8 or newer
- 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.

## Role Variables

### General

##### `step_cli_executable`
- Path or name of the step-cli executable to use for executing commands in this role
- 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
- Example: `/etc/step-cli`
- Default: `/root/.step/`

### CA

##### `step_acme_cert_ca_provisioner`
- Name of the provisioner on the CA that will issue the ACME cert
- Required: Yes

##### `step_acme_cert_webroot_path`
- If set, this role will use `step-cli`s webroot mode to get a new certificate.
- 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

##### `step_acme_cert_name`
- The subject name that the certificate will be issued for
- Default: `{{ ansible_fqdn }}`

##### `step_acme_cert_san`
- Subject Alternate Names to add to the cert
- Must be a list of valid SANs
- Default: `[]`

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

##### `step_acme_cert_contact`
- Contact email for the CA for important notifications
- Default: `root@localhost`

##### `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.
- `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.

### Renewal

##### `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.
- Default: `step-renew`

##### `step_acme_cert_renewal_when`
- 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 after a cert renewal
- Must be a list of systemd units
- Example: `["nginx", "mysqld"]`
- Default: `[]`

## Example Playbooks

---
**NOTE**

Make sure that you are familiar with the way ACME works. You will need a functioning DNS environment at the very least
to make use of ACME certs.

---

```yaml
# Configure your CA to include an ACME provisioner
- hosts: step_ca
become: yes
tasks:
- name: Add an ACME provisioner to the CA
maxhoesel.smallstep.step_ca_provisioner:
name: ACME
type: ACME
become_user: step-ca
notify: reload step-ca
handlers:
- name: reload step-ca
systemd:
name: step-ca
state: reloaded

- hosts: clients
tasks:
# Bootstrap the host to trust the CA
- role: maxhoesel.smallstep.step_bootstrap_host
vars:
step_bootstrap_ca_url: https://myca.localdomain
step_bootstrap_fingerprint: your CAs fingerprint
become: yes

# 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_role:
name: maxhoesel.smallstep.step_acme_cert
vars:
step_acme_cert_ca_provisioner: ACME
become: yes

```
29 changes: 29 additions & 0 deletions roles/step_certificate/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
step_cli_executable: step-cli
step_cli_steppath: /root/.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_contact: root@localhost

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
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

step_acme_cert_renewal_service: step-renew
#step_acme_cert_renewal_when: 8h
step_acme_cert_renewal_reload_services: []
4 changes: 4 additions & 0 deletions roles/step_certificate/handlers/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- name: restart renewal service
service:
name: '{{ step_acme_cert_renewal_service }}'
state: restarted
86 changes: 86 additions & 0 deletions roles/step_certificate/meta/argument_specs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
argument_specs:
main:
short_description: Main entrypoint
options:
step_cli_executable:
type: path
description: Path or name of the step-cli executable to use for executing commands in this role
default: step-cli
step_acme_cert_ca_provisioner:
type: str
required: yes
description: Name of the provisioner on the CA that will issue the ACME cert
step_acme_cert_webroot_path:
type: str
default: ""
description: Path to the webroot for ACME, or empty for standalone mode
# Certificate Options
step_acme_cert_name:
type: str
default: '{{ ansible_fqdn }}'
description: The subject name that the certificate will be issued for
step_acme_cert_san:
type: list
elements: str
default: []
description: Subject Alternate Names to add to the cert
step_acme_cert_duration:
type: str
description: Valid duration of the certificate
step_acme_cert_contact:
type: str
default: root@localhost
description: Contact email for the CA for important notifications
step_acme_cert_certfile:
type: dict
description: Details about the cert file on disk
options:
path:
type: path
default: /etc/ssl/step.crt
description: Absolute path to the cert file
mode:
type: str
default: '644'
description: File mode for the cert file
owner:
type: str
default: root
description: Owner of the file
group:
type: str
default: root
description: Group of the file
step_acme_cert_keyfile:
type: dict
description: Details about the key file on disk
options:
path:
type: path
default: /etc/ssl/step.key
description: Absolute path to the key file
mode:
type: str
default: '600'
description: File mode for the key file
owner:
type: str
default: root
description: Owner of the file
group:
type: str
default: root
description: Group of the file
# Renewal Options
step_acme_cert_renewal_service:
type: str
default: step-renew
description: Name of the systemd service that will handle cert renewals
step_acme_cert_renewal_when:
type: str
description: Renew the cert when its remaining valid time crosses this threshold
step_acme_cert_renewal_reload_services:
type: list
elements: str
default: []
description: Reload or restart these systemd services after a cert renewal
68 changes: 68 additions & 0 deletions roles/step_certificate/molecule/default/converge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
- name: Converge
hosts: clients
vars:
webroots:
Debian: /var/www/html
RedHat: /usr/share/nginx/html
webgroup:
Debian: www-data
RedHat: nginx
tasks:
- name: Get standalone cert
include_role:
name: "step_acme_cert"
vars:
step_cli_steppath: /etc/step-cli-molecule

- name: Start nginx
systemd:
name: nginx
state: started

- name: Get cert via webroot
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]
step_cli_steppath: /etc/step-cli-molecule

- name: Install Nginx site [Debian]
copy:
src: 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
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: "644"
notify: restart nginx
when: ansible_os_family == "RedHat"
handlers:
- name: restart nginx
systemd:
name: nginx
state: restarted
10 changes: 10 additions & 0 deletions roles/step_certificate/molecule/default/files/existing.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-----BEGIN CERTIFICATE-----
MIIBXzCCAQWgAwIBAgIQBz1USaMKt+hX3E7NcG+nCDAKBggqhkjOPQQDAjAOMQww
CgYDVQQDEwNmb28wHhcNMjEwMzA5MTkzOTUzWhcNMzEwMzA3MTkzOTUzWjAOMQww
CgYDVQQDEwNmb28wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARb1UAYfvY5Tl4u
Oj5/4aNzK5H0Z0qn/o1S9QtFrev9x6W6DRtFo0LSrNDwb3Z57f79k4BEQoe6YQbR
bKlYSXYoo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAd
BgNVHQ4EFgQUpUnhM8zYfky4yoU/rV6uDIM0vDkwCgYIKoZIzj0EAwIDSAAwRQIg
KU0olBNzqkJ91MtvvGR7twX2mieyjqfdCRFNc6L6YFoCIQDAAXAW+ncWnaI6HAR9
irL8wNYy84tnZiLg48j7HP5vLQ==
-----END CERTIFICATE-----
5 changes: 5 additions & 0 deletions roles/step_certificate/molecule/default/files/existing.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIIuXoMWikPEYrOG2+ATCw0FEgaVew89g4ys3K363z4MDoAoGCCqGSM49
AwEHoUQDQgAEW9VAGH72OU5eLjo+f+GjcyuR9GdKp/6NUvULRa3r/celug0bRaNC
0qzQ8G92ee3+/ZOAREKHumEG0WypWEl2KA==
-----END EC PRIVATE KEY-----
55 changes: 55 additions & 0 deletions roles/step_certificate/molecule/default/files/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

include /usr/share/nginx/modules/*.conf;

events {
worker_connections 1024;
}

http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;

sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;

include /etc/nginx/mime.types;
default_type application/octet-stream;

include /etc/nginx/conf.d/*.conf;

server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
root /usr/share/nginx/html;

include /etc/nginx/default.d/*.conf;

listen 443 ssl default_server;
listen [::]:443 ssl default_server;

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

location / {
}

error_page 404 /404.html;
location = /40x.html {
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
}
Loading

0 comments on commit 13dae4b

Please sign in to comment.