Add encryption role and related files

This commit is contained in:
2024-09-29 16:11:31 +02:00
parent a03aca39df
commit 9ae5a6446b
24 changed files with 299 additions and 332 deletions
+24 -1
View File
@@ -88,6 +88,26 @@ fail2ban_jails:
unattended_reboot: true
unattended_reboot_time: "04:30"
# Encrypted volumes
encryption_volumes:
- name: "microk8s_config"
size: "0.1G"
key_file_url: https://a13labs-infra-53d7as6g1sh19k.s3.fr-par.scw.cloud/keys/00_50_56_52_8c_74.key
fetch_key_service_before: snap.microk8s.daemon-kubelite.service
fetch_key_service_after: network-online.target
fetch_key_service_wantedby: snap.microk8s.daemon-kubelite.service
mount_service_before: snap.microk8s.daemon-kubelite.service
mount_service_wantedby: snap.microk8s.daemon-kubelite.service
- name: "microk8s_persistent"
size: "150G"
key_file_url: https://a13labs-infra-53d7as6g1sh19k.s3.fr-par.scw.cloud/keys/00_50_56_52_8c_74.key
fetch_key_service_before: snap.microk8s.daemon-kubelite.service
fetch_key_service_after: network-online.target
fetch_key_service_wantedby: snap.microk8s.daemon-kubelite.service
mount_service_before: snap.microk8s.daemon-kubelite.service
mount_service_wantedby: snap.microk8s.daemon-kubelite.service
mount_permission: "0755"
# Kubernetes specific settings
microk8s_version: 1.29
microk8s_allowed_hosts:
@@ -96,7 +116,7 @@ microk8s_alt_names: "{{ hostname }}"
# Let's encrypt issuer
microk8s_issuer_email: c.alexandre.pires@alexpires.me
microk8s_encryption_cfg: /a13labs/microk8s_encryption.yaml
microk8s_encryption_cfg: /mnt/microk8s_config/microk8s_encryption.yaml
microk8s_encryption_secret: "{{ lookup('env', 'MICROK8S_ENCRYPTION_SECRET') }}"
microk8s_users:
- username: operator
@@ -108,3 +128,6 @@ microk8s_users:
# Duo Security
duo_users: ["*", "!provision", "!git"]
duo_api_hostname: api-7cda1654.duosecurity.com
# Apps
persistent_data: "/mnt/microk8s_persistent"
+7 -148
View File
@@ -1,151 +1,10 @@
---
- name: Setup encrypted volume for a13labs
- name: Setup encrypted volumes
hosts: linux
become: true
vars:
disk_image_path: /a13labs.img
disk_image_size: 1G
luks_device_name: a13labs
mount_point: /a13labs
key_file_path: /root/a13labs.key
key_file_url: https://a13labs-infra-{{ lookup('env','BUCKET_SUFFIX') }}.s3.fr-par.scw.cloud
key_fetch_service: a13labs-key.service
gather_facts: true
tasks:
- name: Install cryptsetup and curl
ansible.builtin.apt:
name:
- cryptsetup
- curl
state: present
when: ansible_os_family == 'Debian'
- name: Get mac address
ansible.builtin.shell: |
set -o pipefail
ip link show eth0 | awk '/ether/ {print $2}'
args:
executable: /bin/bash
register: mac_address
changed_when: false
- name: Sanitize mac address
ansible.builtin.set_fact:
mac_address_sanitized: "{{ mac_address.stdout | regex_replace('[:]', '_') }}"
changed_when: false
- name: Create disk image
ansible.builtin.command: fallocate -l {{ disk_image_size }} {{ disk_image_path }}
args:
creates: "{{ disk_image_path }}"
register: disk_image
- name: Create bucket URL
ansible.builtin.set_fact:
key_file_url: "{{ key_file_url }}/keys/{{ mac_address_sanitized }}.key"
changed_when: false
- name: Create encrypted volume # noqa no-handler
when: disk_image is changed
block:
- name: Download key file from Key server
ansible.builtin.uri:
url: "{{ key_file_url }}"
dest: "{{ key_file_path }}"
mode: '0600'
args:
creates: "{{ key_file_path }}"
- name: Set up LUKS encryption with key file
ansible.builtin.command: cryptsetup -q luksFormat {{ disk_image_path }} {{ key_file_path }}
changed_when: false
- name: Open LUKS device with key file
ansible.builtin.command: cryptsetup luksOpen {{ disk_image_path }} {{ luks_device_name }} -d {{ key_file_path }}
register: luks_device
changed_when: false
- name: Create file system
ansible.builtin.command: mkfs.ext4 /dev/mapper/{{ luks_device_name }}
changed_when: false
- name: Close LUKS device
ansible.builtin.command: cryptsetup close {{ luks_device_name }}
changed_when: false
- name: Delete key file
ansible.builtin.file:
path: "{{ key_file_path }}"
state: absent
- name: Create mount point
ansible.builtin.file:
path: "{{ mount_point }}"
state: directory
mode: "0750"
- name: Create systemd service to fetch key file and open LUKS device
ansible.builtin.copy:
dest: /etc/systemd/system/{{ key_fetch_service }}
mode: '0644'
content: |
[Unit]
Description=Open LUKS device
After=network-online.target
Before=snap.microk8s.daemon-kubelite.service
[Service]
Type=oneshot
RemainAfterExit=true
ExecStartPre=/usr/bin/curl -o {{ key_file_path }} "{{ key_file_url }}"
ExecStart=/usr/sbin/cryptsetup luksOpen {{ disk_image_path }} {{ luks_device_name }} -d {{ key_file_path }}
ExecStartPost=/bin/rm -f {{ key_file_path }}
ExecStop=/usr/sbin/cryptsetup close {{ luks_device_name }}
[Install]
WantedBy=snap.microk8s.daemon-kubelite.service
- name: Create systemd service to mount the file system
ansible.builtin.copy:
dest: /etc/systemd/system/{{ mount_point | basename }}.mount
mode: '0644'
content: |
[Unit]
Description=Mount LUKS device
After={{ key_fetch_service }}
Requires={{ key_fetch_service }}
Before=snap.microk8s.daemon-kubelite.service
[Mount]
What=/dev/mapper/{{ luks_device_name }}
Where={{ mount_point }}
Type=ext4
Options=defaults
[Install]
WantedBy=snap.microk8s.daemon-kubelite.service
- name: Reload systemd daemon
ansible.builtin.systemd:
daemon_reload: true
- name: Enable the key fetch service
ansible.builtin.systemd:
name: "{{ key_fetch_service }}"
enabled: true
- name: Enable the mount device
ansible.builtin.systemd:
name: "{{ mount_point | basename }}.mount"
enabled: true
- name: Start the key fetch service
ansible.builtin.systemd:
name: "{{ key_fetch_service }}"
state: started
- name: Start the mount device
ansible.builtin.systemd:
name: "{{ mount_point | basename }}.mount"
state: started
roles:
- role: "encryption"
tags:
- roles
- roles::encryption
+2 -15
View File
@@ -7,9 +7,8 @@
tasks:
- name: Set required facts
ansible.builtin.set_fact:
gitea_home: "/var/lib/gitea"
gitea_config_dir: "/a13labs/gitea"
gitea_mysql_home: "/var/lib/mysql.gitea"
gitea_home: "{{ persistent_data | default('/var/lib') }}/gitea"
gitea_mysql_home: "{{ persistent_data | default('/var/lib') }}/mysql.gitea"
gitea_user_id: 1000
gitea_group_id: 1000
mysql_user_id: 999
@@ -58,18 +57,6 @@
- tasks
- tasks::folders
- name: Create required gitea config folder
become: true
ansible.builtin.file:
state: directory
path: "{{ gitea_config_dir }}"
mode: "0750"
owner: "{{ gitea_user_id }}"
group: "{{ gitea_group_id }}"
tags:
- tasks
- tasks::folders
- name: Create required mysql data folder
become: true
ansible.builtin.file:
+1 -1
View File
@@ -7,7 +7,7 @@
tasks:
- name: Set required facts
ansible.builtin.set_fact:
m3uproxy_home: "/var/lib/m3uproxy"
m3uproxy_home: "{{ persistent_data | default('/var/lib') }}/m3uproxy"
m3uproxy_user_id: 1000
m3uproxy_group_id: 1000
tags: always
+2 -2
View File
@@ -7,8 +7,8 @@
tasks:
- name: Set required facts
ansible.builtin.set_fact:
nextcloud_home: "/var/lib/nextcloud"
nextcloud_mysql_home: "/var/lib/mysql.nextcloud"
nextcloud_home: "{{ persistent_data | default('/var/lib') }}/nextcloud"
nextcloud_mysql_home: "{{ persistent_data | default('/var/lib') }}/mysql.nextcloud"
nextcloud_user_id: 33
nextcloud_group_id: 33
mysql_user_id: 999
+2 -2
View File
@@ -7,8 +7,8 @@
tasks:
- name: Set required facts
ansible.builtin.set_fact:
wordpress_home: "/var/lib/wordpress"
wordpress_mysql_home: "/var/lib/mysql.wordpress"
wordpress_home: "{{ persistent_data | default('/var/lib') }}/wordpress"
wordpress_mysql_home: "{{ persistent_data | default('/var/lib') }}/mysql.wordpress"
wordpress_user_id: 33
wordpress_group_id: 26
mysql_user_id: 999
-10
View File
@@ -1,10 +0,0 @@
---
exclude_paths:
- .git/
- .github/
- tests/
enable_list:
- fqcn-builtins # opt-in
warn_list:
- line-length
- yaml[line-length]
-15
View File
@@ -1,15 +0,0 @@
---
extends: default
ignore: ".tox*\n.git*"
rules:
line-length:
max: 120
level: warning
comments:
min-spaces-from-content: 1
comments-indentation: false
braces:
max-spaces-inside: 1
octal-values:
forbid-implicit-octal: true
forbid-explicit-octal: true
-130
View File
@@ -1,130 +0,0 @@
---
- name: Check if we're using the default SSH port
ansible.builtin.wait_for:
port: 22
state: started
host: "{{ ansible_ssh_host }}"
connect_timeout: 5
timeout: 10
register: default_ssh
ignore_errors: true
ignore_unreachable: true
- name: Check custom SSH port
when: sshd_port is defined
block:
- name: Change to port '{{ sshd_port }}'
ansible.builtin.set_fact:
ansible_port: "{{ sshd_port }}"
- name: Check if we're using the SSH port '{{ sshd_port }}'
ansible.builtin.wait_for:
port: "{{ sshd_port }}"
state: started
host: "{{ ansible_ssh_host }}"
connect_timeout: 5
timeout: 10
ignore_errors: true
register: configured_ssh
ignore_unreachable: true
- name: Set inventory ansible_port to default
ansible.builtin.set_fact:
ansible_port: 22
when: default_ssh is defined
and default_ssh.state | d() == "started"
register: sshd_port_set
- name: SSH is properly configured at port '{{ sshd_port }}'
ansible.builtin.set_fact:
ansible_port: "{{ sshd_port }}"
when: configured_ssh is defined
and configured_ssh.state is defined
and configured_ssh.state | d() == "started"
register: sshd_port_set
- name: Configure custom SSH port
when: sshd_port is defined
and default_ssh is defined
and default_ssh.state | d() == "started"
block:
- name: Changing default ssh port to '{{ sshd_port }}'
become: true
ansible.builtin.lineinfile:
dest: /etc/ssh/sshd_config
regexp: '^Port\s'
line: "Port {{ sshd_port }}"
state: present
register: sshd_port_config
- name: Force sshd to restart # noqa no-handler
when: sshd_port_config.changed
become: true
ansible.builtin.service:
name: ssh
state: restarted
- name: Change to port '{{ sshd_port }}' # noqa no-handler
when: sshd_port_config.changed
ansible.builtin.set_fact:
ansible_port: "{{ sshd_port }}"
- name: Check if we're using the SSH port '{{ sshd_port }}' # noqa no-handler
when: sshd_port_config.changed
ansible.builtin.wait_for:
port: "{{ sshd_port }}"
state: started
host: "{{ ansible_ssh_host }}"
connect_timeout: 5
timeout: 10
ignore_errors: true
register: configured_ssh
- name: SSH is properly configured at port '{{ sshd_port }}'
when: sshd_port_config.changed
and configured_ssh is defined
and configured_ssh.state is defined
and configured_ssh.state | d() == "started"
ansible.builtin.set_fact:
ansible_port: "{{ sshd_port }}"
register: sshd_port_set
- name: Fail if SSH port was not auto-detected (unknown)
ansible.builtin.fail:
msg: "Host was unreachable! Check ssh port!"
when: sshd_port_set is undefined
- name: Confirm host connection works
ansible.builtin.ping:
- name: Run deferred setup to gather facts
ansible.builtin.setup:
- name: Setting hostname '{{ hostname }}'
become: true
ansible.builtin.hostname:
name: "{{ hostname }}"
- name: Update Package Cache (apt/Ubuntu)
become: true
tags: always
ansible.builtin.apt:
update_cache: true
changed_when: false
when: ansible_distribution == "Ubuntu"
- name: Update Package Cache (dnf/CentOS)
become: true
tags: always
ansible.builtin.dnf:
update_cache: true
changed_when: false
when: ansible_distribution == "CentOS"
- name: Update Package Cache (yum/Amazon)
become: true
tags: always
ansible.builtin.dnf:
update_cache: true
changed_when: false
when: ansible_distribution == "Amazon"
+38
View File
@@ -0,0 +1,38 @@
Role Name
=========
A brief description of the role goes here.
Requirements
------------
Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required.
Role Variables
--------------
A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well.
Dependencies
------------
A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles.
Example Playbook
----------------
Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too:
- hosts: servers
roles:
- { role: username.rolename, x: 42 }
License
-------
BSD
Author Information
------------------
An optional section for the role authors to include contact information, or a website (HTML is not allowed).
@@ -0,0 +1,4 @@
---
encryption_volumes: []
encryption_volumes_path: /var/lib/encrypted_volumes
encryption_volumes_default_size: 1G
@@ -0,0 +1,2 @@
---
# handlers file for encryption
@@ -0,0 +1,8 @@
---
- name: Converge
hosts: all
gather_facts: true
tasks:
- name: Include encryption role
ansible.builtin.include_role:
name: "encryption"
@@ -0,0 +1,12 @@
---
driver:
name: podman
platforms:
- name: instance
image: ubuntu:20.04
pre_build_image: true
privileged: true
provisioner:
name: ansible
verifier:
name: ansible
@@ -0,0 +1,10 @@
---
- name: Prepare
hosts: all
gather_facts: false
tasks:
- name: Update cache # noqa no-changed-when
ansible.builtin.raw: apt update
- name: Install required packages # noqa no-changed-when
ansible.builtin.raw: apt install -y python3 sudo cron
+22
View File
@@ -0,0 +1,22 @@
---
- name: Install cryptsetup and curl
become: true
ansible.builtin.apt:
name:
- cryptsetup
- curl
state: present
when: ansible_os_family == 'Debian'
- name: Create folder to store volume
become: true
ansible.builtin.file:
path: "{{ encryption_volumes_path }}"
state: directory
mode: "0750"
- name: Create encrypted volumes
ansible.builtin.include_tasks: volume.yml
loop: "{{ encryption_volumes }}"
loop_control:
loop_var: item
+149
View File
@@ -0,0 +1,149 @@
- name: Check for required variables
ansible.builtin.fail:
msg: "The following variables are required: size, name, key_file_url, mount_point"
when:
- item.size is not defined
- item.name is not defined
- item.key_file_url is not defined
- item.mount_point is not defined
- name: Set required facts
ansible.builtin.set_fact:
encryption_volumes_path: "{{ encryption_volumes_path | default('/var/lib/encrypted_volumes') }}"
size: "{{ item.size | default(encryption_volumes_default_size) }}"
fstype: "{{ item.fstype | default('ext4') }}"
key_file_url: "{{ item.key_file_url }}"
key_file_path: "{{ ansible_env.TMPDIR }}/{{ item.name }}.key"
disk_image_path: "{{ encryption_volumes_path }}/{{ item.name }}.img"
device_name: "{{ item.name }}_luks"
key_fetch_service: "{{ item.name }}_key.service"
mount_point: "{{ item.mount_point | default('/mnt/' + item.name) }}"
fetch_key_service_before: "{{ item.fetch_key_service_before | default('') }}"
fetch_key_service_after: "{{ item.fetch_key_service_after | default('') }}"
fetch_key_service_wantedby: "{{ item.fetch_key_service_wantedby | default('mulit-user.target') }}"
mount_service_before: "{{ item.mount_service_before | default('') }}"
mount_service_wantedby: "{{ item.mount_service_wantedby | default('multi-user.target') }}"
mount_permission: "{{ item.mount_permission | default('0750') }}"
- name: Set transformed facts
ansible.builtin.set_fact:
mount_service: "{{ mount_point | regex_replace('^/', '') | replace('/', '-') }}.mount"
- name: Create disk image '{{ item.name }}'
become: true
ansible.builtin.command: |
fallocate -l {{ size }} {{ disk_image_path }}
args:
creates: "{{ disk_image_path }}"
register: disk_image
- name: Create encrypted volume # noqa no-handler
when: disk_image is changed
become: true
block:
- name: Download key file from Key server
ansible.builtin.uri:
url: "{{ key_file_url }}"
dest: "{{ key_file_path }}"
mode: "0600"
args:
creates: "{{ key_file_path }}"
- name: Format LUKS device
ansible.builtin.command: |
cryptsetup -q luksFormat {{ disk_image_path }} {{ key_file_path }}
changed_when: false
- name: Open LUKS device with key file
ansible.builtin.command: |
cryptsetup luksOpen {{ disk_image_path }} {{ device_name }} -d {{ key_file_path }}
register: luks_device
changed_when: false
- name: Create file system
ansible.builtin.command: |
mkfs.{{ fstype }} /dev/mapper/{{ device_name }}
changed_when: false
- name: Close LUKS device
ansible.builtin.command: cryptsetup close {{ device_name }}
changed_when: false
- name: Delete key file
ansible.builtin.file:
path: "{{ key_file_path }}"
state: absent
- name: Create mount point
become: true
ansible.builtin.file:
path: "{{ mount_point }}"
state: directory
mode: "{{ mount_permission }}"
- name: Create service to fetch key file and open LUKS device
become: true
ansible.builtin.copy:
dest: /etc/systemd/system/{{ key_fetch_service }}
mode: "0644"
content: |
[Unit]
Description={{ item.name }} key fetch service
After={{ fetch_key_service_after }}
Before={{ fetch_key_service_before }}
[Service]
Type=oneshot
RemainAfterExit=true
ExecStartPre=/usr/bin/curl -o {{ key_file_path }} "{{ key_file_url }}"
ExecStart=/usr/sbin/cryptsetup luksOpen {{ disk_image_path }} {{ device_name }} -d {{ key_file_path }}
ExecStartPost=/bin/rm -f {{ key_file_path }}
ExecStop=/usr/sbin/cryptsetup close {{ device_name }}
[Install]
WantedBy={{ fetch_key_service_wantedby }}
- name: Create service to mount the local filesystem
become: true
ansible.builtin.copy:
dest: /etc/systemd/system/{{ mount_service }}
mode: "0644"
content: |
[Unit]
Description={{ item.name }} mount service
After={{ key_fetch_service }}
Requires={{ key_fetch_service }}
Before={{ mount_service_before }}
[Mount]
What=/dev/mapper/{{ device_name }}
Where={{ mount_point }}
Type=ext4
Options=defaults
[Install]
WantedBy={{ mount_service_wantedby }}
- name: Reload systemd daemon
become: true
ansible.builtin.systemd:
daemon_reload: true
- name: Enable the services
become: true
ansible.builtin.systemd:
name: "{{ item }}"
enabled: true
loop:
- "{{ key_fetch_service }}"
- "{{ mount_service }}"
- name: Start the services
become: true
ansible.builtin.systemd:
name: "{{ item }}"
state: started
loop:
- "{{ key_fetch_service }}"
- "{{ mount_service }}"
+2
View File
@@ -0,0 +1,2 @@
localhost
+5
View File
@@ -0,0 +1,5 @@
---
- hosts: localhost
remote_user: root
roles:
- encryption
+2
View File
@@ -0,0 +1,2 @@
---
# vars file for encryption
+2 -2
View File
@@ -37,8 +37,8 @@ locals {
}
}
gitea_path = "/var/lib/gitea"
gitea_mysql_path = "/var/lib/mysql.gitea"
gitea_path = "/mnt/microk8s_persistent/gitea"
gitea_mysql_path = "/mnt/microk8s_persistent/mysql.gitea"
}
#
+1 -1
View File
@@ -1,5 +1,5 @@
locals {
m3uproxy_path = "/var/lib/m3uproxy"
m3uproxy_path = "/mnt/microk8s_persistent/m3uproxy"
m3uproxy_cache_path = "${local.m3uproxy_path}/cache"
squid_app_path = "${local.m3uproxy_path}/squid"
geoip_path = "${local.m3uproxy_path}/geoip"
+2 -3
View File
@@ -74,9 +74,8 @@ locals {
}
}
nextcloud_path = "/var/lib/nextcloud"
nextcloud_mysql_path = "/var/lib/mysql.nextcloud"
nextcloud_path = "/mnt/microk8s_persistent/nextcloud"
nextcloud_mysql_path = "/mnt/microk8s_persistent/mysql.nextcloud"
}
#
+2 -2
View File
@@ -1,6 +1,6 @@
locals {
wordpress_path = "/var/lib/wordpress"
wordpress_mysql_path = "/var/lib/mysql.wordpress"
wordpress_path = "/mnt/microk8s_persistent/wordpress"
wordpress_mysql_path = "/mnt/microk8s_persistent/mysql.wordpress"
}
resource "kubernetes_namespace" "wordpress" {