diff --git a/ansible/.vault b/ansible/.vault deleted file mode 100644 index de75a08..0000000 --- a/ansible/.vault +++ /dev/null @@ -1 +0,0 @@ -../repository.vault \ No newline at end of file diff --git a/ansible/host_vars/prod-01.alexpires.me.yml b/ansible/host_vars/prod-01.alexpires.me.yml index 96f6a98..40cc881 100644 --- a/ansible/host_vars/prod-01.alexpires.me.yml +++ b/ansible/host_vars/prod-01.alexpires.me.yml @@ -138,6 +138,10 @@ encryption_volumes: mount_service_before: snap.microk8s.daemon-kubelite.service mount_service_wantedby: snap.microk8s.daemon-kubelite.service mount_permission: "0755" +encryption_aws_access_key_id: "{{ lookup('env', 'KMS_ACCESS_KEY') }}" +encryption_aws_secret_access_key: "{{ lookup('env', 'KMS_SECRET_KEY') }}" +encryption_kms_arn: "arn:aws:kms:eu-west-1:001604727293:key/977d08fd-8230-40bf-8a4f-d8a3264c5990" +encryption_kms_socket: /var/run/kmsplugin/socket.sock # Kubernetes specific settings microk8s_version: 1.29 @@ -149,6 +153,7 @@ microk8s_alt_names: "{{ hostname }}" microk8s_issuer_email: c.alexandre.pires@alexpires.me microk8s_encryption_cfg: /mnt/microk8s_config/microk8s_encryption.yaml microk8s_encryption_secret: "{{ lookup('env', 'MICROK8S_ENCRYPTION_SECRET') }}" +microk8s_encryption_kms_socket: /var/run/kmsplugin/socket.sock microk8s_users: - username: operator enabled: true diff --git a/ansible/playbook_encryption.yml b/ansible/playbook_encryption.yml index c9f129d..a91e9b5 100644 --- a/ansible/playbook_encryption.yml +++ b/ansible/playbook_encryption.yml @@ -8,3 +8,5 @@ tags: - roles - roles::encryption + - roles::encryption::kms + - roles::encryption::volume diff --git a/ansible/roles/encryption/defaults/main.yml b/ansible/roles/encryption/defaults/main.yml index 4cb3eb8..419cf7a 100644 --- a/ansible/roles/encryption/defaults/main.yml +++ b/ansible/roles/encryption/defaults/main.yml @@ -2,3 +2,9 @@ encryption_volumes: [] encryption_volumes_path: /var/lib/encrypted_volumes encryption_volumes_default_size: 1G + +encryption_aws_access_key_id: +encryption_aws_secret_access_key: +encryption_kms_arn: +encryption_kms_region: eu-west-1 +encryption_kms_socket: /var/run/kmsplugin/socket.sock diff --git a/ansible/roles/encryption/files/aws-encryption-provider b/ansible/roles/encryption/files/aws-encryption-provider new file mode 100755 index 0000000..198f127 Binary files /dev/null and b/ansible/roles/encryption/files/aws-encryption-provider differ diff --git a/ansible/roles/encryption/tasks/kms.yml b/ansible/roles/encryption/tasks/kms.yml new file mode 100644 index 0000000..94c09fd --- /dev/null +++ b/ansible/roles/encryption/tasks/kms.yml @@ -0,0 +1,57 @@ +- name: Check if required facts are set + ansible.builtin.fail: + msg: "The following variables are required: encryption_aws_access_key_id, encryption_aws_secret_access_key, encryption_kms_arn" + when: + - encryption_aws_access_key_id is not defined + - encryption_aws_secret_access_key is not defined + - encryption_kms_arn is not defined + +- name: Copy aws-encryption-provider binary to the host + become: true + ansible.builtin.copy: + src: aws-encryption-provider + dest: /usr/local/bin/aws-encryption-provider + mode: '0755' + +- name: Copy start script to the host + become: true + ansible.builtin.template: + src: start-encryption-provider.sh.j2 + dest: /usr/local/bin/start-encryption-provider.sh + mode: '0755' + owner: root + group: root + +- name: Create KMS socket directory + become: true + ansible.builtin.file: + path: /var/run/kmsplugin + state: directory + mode: '0755' + owner: root + group: root + +- name: Create systemd service file for aws-encryption-provider + become: true + ansible.builtin.template: + src: aws-encryption-provider.service.j2 + dest: /etc/systemd/system/aws-encryption-provider.service + mode: '0644' + +- name: Reload systemd daemon + become: true + ansible.builtin.systemd: + daemon_reload: true + name: aws-encryption-provider + +- name: Enable aws-encryption-provider service + become: true + ansible.builtin.systemd: + name: aws-encryption-provider + enabled: true + +- name: Start aws-encryption-provider service + become: true + ansible.builtin.systemd: + name: aws-encryption-provider + state: started diff --git a/ansible/roles/encryption/tasks/main.yml b/ansible/roles/encryption/tasks/main.yml index 792e4ee..e3fbcd1 100644 --- a/ansible/roles/encryption/tasks/main.yml +++ b/ansible/roles/encryption/tasks/main.yml @@ -5,8 +5,12 @@ name: - cryptsetup - curl + - libc6 state: present when: ansible_os_family == 'Debian' + tags: + - roles::encryption::packages + - roles::encryption::volume - name: Create folder to store volume become: true @@ -14,6 +18,8 @@ path: "{{ encryption_volumes_path }}" state: directory mode: "0750" + tags: + - roles::encryption::volume - name: Copy required scripts become: true @@ -24,9 +30,19 @@ with_items: - "open_volume" - "read_system_id" + tags: + - roles::encryption::volume - name: Create encrypted volumes ansible.builtin.include_tasks: volume.yml loop: "{{ encryption_volumes }}" loop_control: loop_var: item + tags: + - roles::encryption::volume + +- name: Create KMS service + ansible.builtin.include_tasks: kms.yml + when: encryption_aws_access_key_id is defined and encryption_aws_secret_access_key is defined and encryption_kms_arn is defined + tags: + - roles::encryption::kms diff --git a/ansible/roles/encryption/tasks/volume.yml b/ansible/roles/encryption/tasks/volume.yml index e45d6dd..d588a42 100644 --- a/ansible/roles/encryption/tasks/volume.yml +++ b/ansible/roles/encryption/tasks/volume.yml @@ -89,45 +89,17 @@ - name: Create service to fetch key file and open LUKS device become: true - ansible.builtin.copy: + ansible.builtin.template: + src: key_fetch.service.j2 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 - ExecStart=/usr/local/bin/open_volume "{{ key_server }}" {{ disk_image_path }} {{ device_name }} - 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: + ansible.builtin.template: + src: mount.service.j2 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 diff --git a/ansible/roles/encryption/templates/aws-encryption-provider.service.j2 b/ansible/roles/encryption/templates/aws-encryption-provider.service.j2 new file mode 100644 index 0000000..8cf0efa --- /dev/null +++ b/ansible/roles/encryption/templates/aws-encryption-provider.service.j2 @@ -0,0 +1,13 @@ +[Unit] +Description=AWS Encryption Provider +After=network.target +Before=snap.microk8s.daemon-kubelite.service + +[Service] +Type=simple +ExecStart=/usr/local/bin/start-encryption-provider.sh +Restart=on-failure +User=root + +[Install] +WantedBy=snap.microk8s.daemon-kubelite.service \ No newline at end of file diff --git a/ansible/roles/encryption/templates/key_fetch.service.j2 b/ansible/roles/encryption/templates/key_fetch.service.j2 new file mode 100644 index 0000000..0bec475 --- /dev/null +++ b/ansible/roles/encryption/templates/key_fetch.service.j2 @@ -0,0 +1,13 @@ +[Unit] +Description={{ item.name }} key fetch service +After={{ fetch_key_service_after }} +Before={{ fetch_key_service_before }} + +[Service] +Type=oneshot +RemainAfterExit=true +ExecStart=/usr/local/bin/open_volume "{{ key_server }}" {{ disk_image_path }} {{ device_name }} +ExecStop=/usr/sbin/cryptsetup close {{ device_name }} + +[Install] +WantedBy={{ fetch_key_service_wantedby }} diff --git a/ansible/roles/encryption/templates/mount.service.j2 b/ansible/roles/encryption/templates/mount.service.j2 new file mode 100644 index 0000000..1d5f6d7 --- /dev/null +++ b/ansible/roles/encryption/templates/mount.service.j2 @@ -0,0 +1,14 @@ +[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 }} diff --git a/ansible/roles/encryption/templates/start-encryption-provider.sh.j2 b/ansible/roles/encryption/templates/start-encryption-provider.sh.j2 new file mode 100644 index 0000000..44e10a4 --- /dev/null +++ b/ansible/roles/encryption/templates/start-encryption-provider.sh.j2 @@ -0,0 +1,6 @@ +#!/bin/bash +# This script is used to start the encryption provider for the application. +# Managed by Ansible. +export AWS_ACCESS_KEY_ID={{ encryption_aws_access_key_id }} +export AWS_SECRET_ACCESS_KEY={{ encryption_aws_secret_access_key }} +/usr/local/bin/aws-encryption-provider --key={{ encryption_kms_arn }} --region={{ encryption_kms_region }} --listen={{ encryption_kms_socket }} diff --git a/ansible/roles/microk8s/defaults/main.yml b/ansible/roles/microk8s/defaults/main.yml index 1ddd90c..884d7c2 100644 --- a/ansible/roles/microk8s/defaults/main.yml +++ b/ansible/roles/microk8s/defaults/main.yml @@ -39,3 +39,4 @@ microk8s_iac_user: "provision" microk8s_encryption_secret: "a13labs" microk8s_encryption_cfg: "/etc/microk8s_encryption.yaml" +microk8s_encryption_kms_socket: diff --git a/ansible/roles/microk8s/tasks/microk8s/install.yml b/ansible/roles/microk8s/tasks/microk8s/install.yml index 95f226f..f8aaa19 100644 --- a/ansible/roles/microk8s/tasks/microk8s/install.yml +++ b/ansible/roles/microk8s/tasks/microk8s/install.yml @@ -7,21 +7,8 @@ - name: Create encryption configuration become: true - ansible.builtin.copy: - content: | - --- - apiVersion: apiserver.config.k8s.io/v1 - kind: EncryptionConfiguration - resources: - - resources: - - secrets - - configmaps - providers: - - aescbc: - keys: - - name: k8s-crypto - secret: {{ microk8s_encryption_secret }} - - identity: {} + ansible.builtin.template: + src: encryption-provider-config.yml.j2 dest: "{{ microk8s_encryption_cfg }}" mode: "0600" diff --git a/ansible/roles/microk8s/templates/encryption-provider-config.yml.j2 b/ansible/roles/microk8s/templates/encryption-provider-config.yml.j2 new file mode 100644 index 0000000..b96d5fc --- /dev/null +++ b/ansible/roles/microk8s/templates/encryption-provider-config.yml.j2 @@ -0,0 +1,20 @@ +--- +apiVersion: apiserver.config.k8s.io/v1 +kind: EncryptionConfiguration +resources: + - resources: + - secrets + - configmaps + providers: +{% if microk8s_encryption_kms_socket is defined %} + - kms: + apiVersion: v2 + name: aws-encryption-provider + endpoint: unix://{{ microk8s_encryption_kms_socket }} + timeout: 3s +{% endif %} + - aescbc: + keys: + - name: k8s-crypto + secret: {{ microk8s_encryption_secret }} + - identity: {} diff --git a/ansible/sectool.env b/ansible/sectool.env index 766eecb..86acd96 100644 --- a/ansible/sectool.env +++ b/ansible/sectool.env @@ -7,3 +7,6 @@ USER_PASSWORD_OPERATOR=$USER_PASSWORD_OPERATOR ANSIBLE_FORCE_COLOR=True BUCKET_SUFFIX=$BUCKET_SUFFIX MICROK8S_ENCRYPTION_SECRET=$MICROK8S_ENCRYPTION_SECRET +KMS_ACCESS_KEY=$KMS_ACCESS_KEY +KMS_SECRET_KEY=$KMS_SECRET_KEY +KMS_KEY_ARN=$KMS_KEY_ARN diff --git a/terraform/aws-iac/kms.tf b/terraform/aws-iac/kms.tf new file mode 100644 index 0000000..4a3cf6c --- /dev/null +++ b/terraform/aws-iac/kms.tf @@ -0,0 +1,106 @@ +data "aws_iam_policy_document" "kms_key_policy" { + statement { + sid = "Enable IAM User Permissions" + effect = "Allow" + actions = ["kms:*"] + resources = ["*"] + + principals { + type = "AWS" + identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"] + } + } + + statement { + sid = "Allow IAM User Access" + effect = "Allow" + actions = [ + "kms:Encrypt", + "kms:Decrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:DescribeKey" + ] + resources = ["*"] + + principals { + type = "AWS" + identifiers = [aws_iam_user.k8s_secrets_user.arn] + } + + condition { + # Only allow from specific IP addresses + test = "IpAddress" + variable = "aws:SourceIp" + values = ["${join(",", local.instance_ips)}"] + } + } +} + +resource "aws_kms_key" "k8s_secrets" { + description = "KMS key for encrypting Kubernetes secrets" + deletion_window_in_days = 30 + enable_key_rotation = true + + policy = data.aws_iam_policy_document.kms_key_policy.json +} + +resource "aws_iam_user" "k8s_secrets_user" { + name = "alexpires-me-microk8s" +} + +resource "aws_iam_access_key" "k8s_secrets_user_key" { + user = aws_iam_user.k8s_secrets_user.name +} + +data "aws_iam_policy_document" "k8s_secrets_user_policy" { + statement { + effect = "Allow" + + actions = [ + "kms:Encrypt", + "kms:Decrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:DescribeKey" + ] + + resources = [aws_kms_key.k8s_secrets.arn] + condition { + # Only allow from specific IP addresses + test = "IpAddress" + variable = "aws:SourceIp" + values = ["${join(",", local.instance_ips)}"] + } + } +} + +resource "aws_iam_user_policy" "k8s_secrets_user_policy" { + name = "alexpires-me-microk8s-policy" + user = aws_iam_user.k8s_secrets_user.name + policy = data.aws_iam_policy_document.k8s_secrets_user_policy.json +} + +data "aws_caller_identity" "current" {} + +locals { + instance_ips = [ + "${local.vps_1.ip}/32", + ] + + vps_1 = { + id = data.terraform_remote_state.contabo.outputs.instances[0].id + name = data.terraform_remote_state.contabo.outputs.instances[0].name + ip = data.terraform_remote_state.contabo.outputs.instances[0].ip_config[0].v4[0].ip + } +} + +data "terraform_remote_state" "contabo" { + backend = "s3" + config = { + bucket = "a13labs.infra.eu" + key = "contabo.core.infra.tfstate" + region = "eu-west-1" + } +} +