Skip to content
This repository has been archived by the owner on Jan 15, 2024. It is now read-only.

Add tasks for generating TLS certificates #17

Merged
merged 13 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ jobs:
steps:
- uses: UCL-MIRSG/.github/actions/[email protected]
with:
ansible-roles-config: ./meta/requirements.yml
pre-commit-config: ./.pre-commit-config.yaml
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,32 @@ This role is for installing [docker-ce](https://docs.docker.com/engine/install/)
| `docker_repo_baseurl` | URL to the directory containing the repodata. Defaults to `https://download.docker.com/linux/centos` |
| `docker_yum_package` | The name of the Docker package. Defaults to `docker` |

If you would like to [configure](https://docs.docker.com/engine/security/protect-access/#use-tls-https-to-protect-the-docker-daemon-socket)
your Docker server such that clients can connect to it via TLS, you can also use this role to generate the necessary certificates.
The following variables can be used to configure certificate creation and signing:

| Name | Description |
| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `docker_generate_certificates` | If `true`, CA, server, and client certificates will be generated. Defaults to `false` |
| `docker_certificate_directory` | Directory in which to store the certificates. Defaults to `/home/docker/.docker` |
| `docker_config_dir` | Docker configuration directory. Defaults to `/etc/docker` |
| `docker_daemon_conf_file` | Docker daemon configuration filename. Defaults to `/etc/docker/daemon.json` |
| `docker_server_hostname` | Hostname of your Docker server. Used for the `commonName` field of the certificate signing request subject. Defaults to `"{{ ansible_host }}"` |
| `docker_server_ip` | IP address of your Docker server. Defaults to `0.0.0.0` |
| `docker_ca_key` | Filename for the CA certificate key. Defaults to `/home/docker/.docker/ca.key` |
| `docker_ca_csr` | Filename for the CA certificate signing request. Defaults to `/home/docker/.docker/ca.csr` |
| `docker_ca_cert` | Filename for the CA certificate. Defaults to `/home/docker/.docker/ca.pem` |
| `docker_server_key` | Filename for the server certificate key. Defaults to `/home/docker/.docker/server-key.pem` |
| `docker_server_csr` | Filename for the server certificate signing request. Defaults to `/home/docker/.docker/server.csr` |
| `docker_server_cert` | Filename for the server certificate. Defaults to `/home/docker/.docker/server-cert.pem` |
| `docker_client_hostnames` | List of hostnames of clients that will connect to the server. Defaults to `[]` |
| `docker_client_certificate_directory` | Directory in which to store the client certificates. Defaults to `/home/docker/.docker/client_certs` |
| `docker_client_certificate_cache_directory` | Directory in which to client certificates will be copied to. Defaults to `~/ansible_persistent_files/docker_certificates` |

If you have specified a list of clients in `docker_client_hostnames`, the certificate for each client will be stored locally on your Ansible
controller in the folder `docker_client_certificate_cache_directory`. You will then need to copy these certificates to the corresponding
client.

## Installation

Include in a requirements.yml file as follows:
Expand Down
32 changes: 30 additions & 2 deletions defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,38 @@
# defaults for mirsg.docker
docker_owner: "root"
docker_group: "root"
docker_config_dir: "/etc/docker"
docker_daemon_conf_file: "/etc/docker/daemon.json"

# mirsg.docker service
docker_service_directory: "/etc/systemd/system/docker.service.d"
docker_service_name: "docker"

# mirsg.docker install
docker_rpm_gpg_key_url: "https://download.docker.com/linux/centos/gpg"
docker_repo_baseurl: "https://download.docker.com/linux/centos/$releasever/$basearch/stable"
docker_yum_package: "docker"

# mirsg.docker certificates
docker_generate_certificates: false
docker_certificate_directory: "/home/docker/.docker"

# mirsg.docker configuration
docker_config_dir: "/etc/docker"
docker_daemon_conf_file: "/etc/docker/daemon.json"
docker_server_hostname: "{{ ansible_host }}"
docker_server_ip: "0.0.0.0"
docker_server_port: "2376"

# mirsg.docker CA certificate
docker_ca_key: "{{ docker_certificate_directory }}/ca.key"
docker_ca_csr: "{{ docker_certificate_directory }}/ca.csr"
docker_ca_cert: "{{ docker_certificate_directory }}/ca.pem"

# mirsg.docker server certificate
docker_server_key: "{{ docker_certificate_directory }}/server-key.pem"
docker_server_csr: "{{ docker_certificate_directory }}/server.csr"
docker_server_cert: "{{ docker_certificate_directory }}/server-cert.pem"

# mirsg.docker client certificates
docker_client_hostnames: [] # list of hostnames of clients that will connect to the server
docker_client_certificate_directory: "{{ docker_certificate_directory }}/client_certs"
docker_client_certificate_cache_directory: "{{ lookup('env', 'HOME') }}/ansible_persistent_files/docker_certificates"
3 changes: 3 additions & 0 deletions meta/requirements.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
collections:
- community.crypto
9 changes: 8 additions & 1 deletion molecule/centos7/molecule.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
---
dependency:
name: galaxy
options:
role-file: meta/requirements.yml
force: true

driver:
name: docker

platforms:
- name: instance
- name: server
image: centos:7
dockerfile: ../resources/Dockerfile.j2
command: ""
Expand All @@ -19,7 +22,11 @@ provisioner:
config_options:
defaults:
callbacks_enabled: profile_tasks, timer, yaml
inventory:
links:
host_vars: ../resources/inventory/host_vars/
playbooks:
prepare: ./prepare.yml
converge: ../resources/converge.yml
env:
ANSIBLE_VERBOSITY: "1"
Expand Down
30 changes: 30 additions & 0 deletions molecule/centos7/prepare.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
- name: Prepare
hosts: all
become: false
gather_facts: true
tasks:
- name: Install EPEL-release
ansible.builtin.yum:
name: "epel-release"
state: installed

- name: Install Python
ansible.builtin.package:
name: "{{ item }}"
update_cache: true
state: present
loop:
- python
- python-pip
- python-setuptools

- name: Update pip
ansible.builtin.pip:
name: pip
version: "20.3.4"

- name: Install cryptography with pip - needed to generate certificates
ansible.builtin.pip:
name:
- cryptography
3 changes: 3 additions & 0 deletions molecule/resources/inventory/host_vars/server.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
docker_generate_certificates: true
docker_client_hostnames: ["docker-client.com"]
9 changes: 8 additions & 1 deletion molecule/rocky8/molecule.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
---
dependency:
name: galaxy
options:
role-file: meta/requirements.yml
force: true

driver:
name: docker

platforms:
- name: instance
- name: server
image: rockylinux:8
dockerfile: ../resources/Dockerfile.j2
command: ""
Expand All @@ -21,7 +24,11 @@ provisioner:
config_options:
defaults:
callbacks_enabled: profile_tasks, timer, yaml
inventory:
links:
host_vars: ../resources/inventory/host_vars/
playbooks:
prepare: ./prepare.yml
converge: ../resources/converge.yml
env:
ANSIBLE_VERBOSITY: "1"
Expand Down
30 changes: 30 additions & 0 deletions molecule/rocky8/prepare.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
- name: Prepare
hosts: all
become: false
gather_facts: true
tasks:
- name: Install EPEL-release
ansible.builtin.yum:
name: "epel-release"
state: installed

- name: Install Python
ansible.builtin.package:
name: "{{ item }}"
update_cache: true
state: present
loop:
- python3
- python3-pip
- python3-setuptools

- name: Update pip
ansible.builtin.pip:
name: pip
version: "21.3.1"

- name: Install cryptography with pip - needed to generate certificates
ansible.builtin.pip:
name:
- cryptography
36 changes: 36 additions & 0 deletions tasks/ca-cert.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
- name: Ensure docker cert dir exists
ansible.builtin.file:
path: "{{ docker_certificate_directory }}"
state: directory
owner: "{{ docker_owner }}"
group: "{{ docker_group }}"
mode: "0700"

- name: Generate CA private key
community.crypto.openssl_privatekey:
path: "{{ docker_ca_key }}"
owner: "{{ docker_owner }}"
group: "{{ docker_group }}"
mode: "0400"

- name: Generate CA CSR
community.crypto.openssl_csr:
path: "{{ docker_ca_csr }}"
privatekey_path: "{{ docker_ca_key }}"
common_name: "{{ docker_server_hostname }}"
subject_alt_name: "IP:{{ docker_server_ip }}"
basic_constraints_critical: true
basic_constraints: ["CA:TRUE"]

- name: Generate self-signed CA certificate
community.crypto.x509_certificate:
path: "{{ docker_ca_cert }}"
privatekey_path: "{{ docker_ca_key }}"
csr_path: "{{ docker_ca_csr }}"
provider: selfsigned
owner: "{{ docker_owner }}"
group: "{{ docker_group }}"
mode: "0400"
notify:
- Restart docker
48 changes: 48 additions & 0 deletions tasks/client-certs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
- name: Ensure docker client cert dir exists on server
ansible.builtin.file:
path: "{{ docker_client_certificate_directory }}"
state: directory
owner: "{{ docker_owner }}"
group: "{{ docker_group }}"
mode: "0700"

- name: Generate OpenSSL client private key
community.crypto.openssl_privatekey:
path: "{{ docker_client_certificate_directory }}/key.pem"
owner: "{{ docker_owner }}"
group: "{{ docker_group }}"
mode: "0400"

- name: Generate OpenSSL CSR for each client using private key
community.crypto.openssl_csr:
path: "{{ docker_client_certificate_directory }}/{{ item }}.csr"
privatekey_path: "{{ docker_client_certificate_directory }}/key.pem"
common_name: "{{ item }}"
register: new_docker_client_csr_generated
loop: "{{ docker_client_hostnames }}"

- name: Generate client certificates signed by server CA
community.crypto.x509_certificate:
path: "{{ docker_client_certificate_directory }}/{{ item }}.cert"
csr_path: "{{ docker_client_certificate_directory }}/{{ item }}.csr"
provider: ownca
ownca_path: "{{ docker_ca_cert }}"
ownca_privatekey_path: "{{ docker_ca_key }}"
mode: "0400"
owner: "{{ docker_owner }}"
group: "{{ docker_group }}"
loop: "{{ docker_client_hostnames }}"

- name: Copy signed client certificates to temp dir on Ansible controller
ansible.builtin.fetch:
src: "{{ docker_client_certificate_directory }}/{{ item }}.cert"
dest: "{{ docker_client_certificate_cache_directory }}/{{ item }}.cert"
flat: true
loop: "{{ docker_client_hostnames }}"

- name: Copy private key to temp dir on Ansible controller
ansible.builtin.fetch:
src: "{{ docker_client_certificate_directory }}/key.pem"
dest: "{{ docker_client_certificate_cache_directory }}/key.pem"
flat: true
31 changes: 31 additions & 0 deletions tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,37 @@
mode: "0644"
notify: Reload docker

- name: Generate CA, server, and client certificates
when: docker_generate_certificates
notify:
- Restart docker
block:
- name: Ensure docker config directory exists - {{ docker_config_dir }}
ansible.builtin.file:
path: "{{ docker_config_dir }}"
owner: "{{ docker_owner }}"
group: "{{ docker_group }}"
state: directory
mode: "0700"

- name: Write docker daemon configuration file
ansible.builtin.template:
src: daemon.json.j2
dest: "{{ docker_daemon_conf_file }}"
owner: "{{ docker_owner }}"
group: "{{ docker_group }}"
mode: "0640"

- name: Generate CA certificate
ansible.builtin.import_tasks: ca-cert.yml

- name: Generate server TLS certificate
ansible.builtin.import_tasks: server-cert.yml

- name: Generate TLS certificates for each client
ansible.builtin.import_tasks: client-certs.yml
when: docker_client_hostnames

- name: "Ensure docker service configuration is reloaded before restarting the service"
ansible.builtin.meta: flush_handlers

Expand Down
27 changes: 27 additions & 0 deletions tasks/server-cert.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
- name: Generate server private key
community.crypto.openssl_privatekey:
path: "{{ docker_server_key }}"
owner: "{{ docker_owner }}"
group: "{{ docker_group }}"
mode: "0400"

- name: Generate server CSR
community.crypto.openssl_csr:
path: "{{ docker_server_csr }}"
privatekey_path: "{{ docker_server_key }}"
common_name: "{{ docker_server_hostname }}"
subject_alt_name: "IP:{{ docker_server_ip }}"

- name: Generate server certificate
community.crypto.x509_certificate:
path: "{{ docker_server_cert }}"
csr_path: "{{ docker_server_csr }}"
provider: ownca
ownca_path: "{{ docker_ca_cert }}"
ownca_privatekey_path: "{{ docker_ca_key }}"
owner: "{{ docker_owner }}"
group: "{{ docker_group }}"
mode: "0400"
notify:
- Restart docker
7 changes: 7 additions & 0 deletions templates/daemon.json.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"hosts": ["tcp://{{ docker_server_ip }}:{{ docker_server_port }}", "unix:///var/run/docker.sock"],
"tlsverify": true,
"tlscacert": "{{ docker_ca_cert }}",
"tlscert": "{{ docker_server_cert }}",
"tlskey": "{{ docker_server_key }}"
}