From f2da9e35fa6c08caa478bc1ce59b5d374a018598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix-Antoine=20Fortin?= Date: Fri, 3 May 2024 11:21:52 -0400 Subject: [PATCH] Fix issue #260 --- aws/infrastructure.tf | 1 + azure/infrastructure.tf | 1 + common/provision/main.tf | 15 +++- common/variables.tf | 10 +++ docs/README.md | 135 +++++++++++++++++------------------- gcp/infrastructure.tf | 1 + openstack/infrastructure.tf | 1 + 7 files changed, 89 insertions(+), 75 deletions(-) diff --git a/aws/infrastructure.tf b/aws/infrastructure.tf index 3ed1eaeb..d8e12d34 100644 --- a/aws/infrastructure.tf +++ b/aws/infrastructure.tf @@ -43,6 +43,7 @@ module "provision" { terraform_facts = module.configuration.terraform_facts hieradata = var.hieradata sudoer_username = var.sudoer_username + eyaml_key = var.eyaml_key depends_on = [aws_instance.instances, aws_eip.public_ip] } diff --git a/azure/infrastructure.tf b/azure/infrastructure.tf index 7c27b4b3..0f0716b2 100644 --- a/azure/infrastructure.tf +++ b/azure/infrastructure.tf @@ -43,6 +43,7 @@ module "provision" { terraform_facts = module.configuration.terraform_facts hieradata = var.hieradata sudoer_username = var.sudoer_username + eyaml_key = var.eyaml_key depends_on = [ azurerm_linux_virtual_machine.instances ] } diff --git a/common/provision/main.tf b/common/provision/main.tf index 917674ea..f3b662f0 100644 --- a/common/provision/main.tf +++ b/common/provision/main.tf @@ -5,6 +5,7 @@ variable "terraform_facts" { } variable "hieradata" { } variable "sudoer_username" { } variable "tf_ssh_key" { } +variable "eyaml_key" { } resource "terraform_data" "deploy_hieradata" { for_each = length(var.bastions) > 0 ? var.puppetservers : { } @@ -40,13 +41,23 @@ resource "terraform_data" "deploy_hieradata" { destination = "user_data.yaml" } + provisioner "file" { + content = var.eyaml_key + destination = "private_key.pkcs7.pem" + } + provisioner "remote-exec" { inline = [ - "sudo mkdir -p /etc/puppetlabs/data /etc/puppetlabs/facts", + "sudo mkdir -p /etc/puppetlabs/data /etc/puppetlabs/facts /etc/puppetlabs/puppet/eyaml", # puppet user and group have been assigned the reserved UID/GID 52 "sudo install -o root -g 52 -m 640 terraform_data.yaml user_data.yaml /etc/puppetlabs/data/", "sudo install -o root -g 52 -m 640 terraform_facts.yaml /etc/puppetlabs/facts/", - "rm -f terraform_data.yaml user_data.yaml terraform_facts.yaml", + # install the private key if it is a non-empty file + "test -s private_key.pkcs7.pem && sudo install -o 52 -g 52 -m 400 private_key.pkcs7.pem /etc/puppetlabs/puppet/eyaml", + # generate the public key X509 certificate from the private key file + # this is necessary to decrypt in hiera-eyaml 3.4.0 + "sudo openssl req -new -key private_key.pkcs7.pem -set_serial 1 -batch -out /etc/puppetlabs/puppet/eyaml/public_key.pkcs7.pem", + "rm -f terraform_data.yaml user_data.yaml terraform_facts.yaml private_key.pkcs7.pem", "[ -f /usr/local/bin/consul ] && [ -f /usr/bin/jq ] && consul event -token=$(sudo jq -r .acl.tokens.agent /etc/consul/config.json) -name=puppet $(date +%s) || true", ] } diff --git a/common/variables.tf b/common/variables.tf index 285df0dc..0efdabdd 100644 --- a/common/variables.tf +++ b/common/variables.tf @@ -154,3 +154,13 @@ variable "puppetfile" { default = "" description = "Additional content for the pupet environment Puppetfile. If the string includes a `forge` setting, the string replaces the original Puppetfile completely." } + +variable "eyaml_key" { + type = string + default = "" + description = "Private RSA key used to encrypt the data in the hieradata yaml fire" + validation { + condition = var.eyaml_key == "" || regex("^-----BEGIN PRIVATE KEY-----\n", var.eyaml_key) + error_message = "Unsupported private key format" + } +} diff --git a/docs/README.md b/docs/README.md index 6d13aef4..4ec8a2d0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -783,36 +783,76 @@ The file created from this string can be found on `puppet` as **Post build modification effect**: trigger scp of hieradata files at next `terraform apply`. Each instance's Puppet agent will be reloaded following the copy of the hieradata files. -#### 4.13.1. Encrypting hieradata secrets +### 4.14 eyaml_private (optional) + +**default value**: empty string + +Defines the private RSA key required to decrypt the values encrypted with hiera-eyaml PKCS7. +This key will be copied on the Puppet server. + +**Post build modification effect**: trigger scp of private key file at next `terraform apply`. + +#### 4.14.1 Generate eyaml encryption keys If you plan to track the cluster configuration files in git (i.e:`main.tf`, `user_data.yaml`), it would be a good idea to encrypt the sensitive property values. -Magic Castle uses [Puppet hiera-eyaml](https://github.com/voxpupuli/hiera-eyaml) to provide a +Magic Castle uses [hiera-eyaml](https://github.com/voxpupuli/hiera-eyaml) to provide a per-value encryption of sensitive properties to be used by Puppet. -To encrypt the data, you need to access the eyaml public certificate file of your cluster. -This file is located on the Puppet server at `/etc/puppetlabs/puppet/eyaml/public_key.pkcs7.pem`. -With the public certificate file, you can encrypt the values with eyaml: -```sh -/opt/puppetlabs/puppet/bin/eyaml encrypt -s 'your-secret' --pkcs7-public-key /etc/puppetlabs/puppet/eyaml/public_key.pkcs7.pem -o string +The private key and its corresponding public key wrapped in a X509 certificate can be generated with `openssl`: + +```shell +openssl req -x509 -nodes -set_serial 1 -newkey rsa:2048 -keyout private_key.pkcs7.pem -out public_key.pkcs7.pem -batch +``` + +or with `eyaml`: + +```shell +eyaml createkeys --pkcs7-public-key=public_key.pkcs7.pem --pkcs7-private-key=private_key.pkcs7.pem ``` -You can encrypt the value remotely using SSH jump host: +#### 4.14.2 Encrypting sensitive properties + +To encrypt a sensitive property with openssl: ```sh -ssh -J centos@your-cluster.yourdomain.cloud centos@puppet /opt/puppetlabs/puppet/bin/eyaml encrypt -s 'your-secret' --pkcs7-public-key=/etc/puppetlabs/puppet/eyaml/public_key.pkcs7.pem -o string +echo -n 'your-secret' | openssl smime -encrypt -aes-256-cbc -outform der public_key.pkcs7.pem | openssl base64 -A | xargs printf "ENC[PKCS7,%s]\n" ``` -In the preceding command, replace `puppet` by the hostname of your puppetserver (i.e.: `mgmt1`). -The openssl command-line can also be used to encrypt a value with the certificate file: +To encrypt a sensitive property with eyaml: ```sh -echo -n 'your-secret' | openssl smime -encrypt -aes-256-cbc -outform der public_key.pkcs7.pem | base64 -w0 | xargs printf "ENC[PKCS7,%s]\n" +eyaml encrypt -s 'your-secret' --pkcs7-public-key=public_key.pkcs7.pem -o string ``` -To learn more about `public_key.pkcs7.pem` and how it can be generated before the cluster creation, refer to -section [10.13 Generate and replace Puppet hieradata encryption keys](#1013-generate-and-replace-puppet-hieradata-encryption-keys). +#### 4.14.3 Terraform cloud -### 4.14 firewall_rules (optional) +To provide the value of this variable via Terraform Cloud, encode the private key content with base64: + +``` +openssl base64 -A -in private_key.pkcs7.pem +``` + +Define a variable in your main.tf: + +```hcl +variable "tfc_eyaml_key" {} +module "openstack" { + ... +} +``` + +Then make sure to decode it before passing it to the cloud provider module: + +```hcl +variable "tfc_eyaml_key" {} +module "openstack" { + ... + eyaml_key = base64decode(var.tfc_eyaml_key) + ... +} +``` + +### 4.15 firewall_rules (optional) **default value**: ```hcl @@ -846,7 +886,7 @@ about this requirement, refer to Magic Castle's **Post build modification effect**: modify the cloud provider firewall rules at next `terraform apply`. -### 4.15 generate_ssh_key (optional) +### 4.16 generate_ssh_key (optional) **default_value**: `false` @@ -867,7 +907,7 @@ next terraform apply. The Terraform public SSH key will be removed from the sudoer account `authorized_keys` file at next Puppet agent run. -### 4.16 software_stack (optional) +### 4.17 software_stack (optional) **default_value**: `"alliance"` @@ -880,7 +920,7 @@ Possible values are: **Post build modification effect**: trigger scp of hieradata files at next `terraform apply`. -### 4.17 pool (optional) +### 4.18 pool (optional) **default_value**: `[]` @@ -892,7 +932,7 @@ managed by the workload scheduler through Terraform API. For more information, r will be instantiated, others will stay uninstantiated or will be destroyed if previously instantiated. -### 4.18 skip_upgrade (optional) +### 4.19 skip_upgrade (optional) **default_value** = `false` @@ -903,7 +943,7 @@ all packages are upgraded. after the modification will take into consideration the new value of the parameter to determine whether they should upgrade the base image packages or not. -### 4.19 puppetfile (optional) +### 4.20 puppetfile (optional) **default_value** = `""` @@ -1730,58 +1770,7 @@ instances = { } ``` -### 10.13 Generate and replace Puppet hieradata encryption keys - -During the Puppet server initial boot, a pair of hiera-eyaml encryptions keys are generated in -`/opt/puppetlabs/puppet/eyaml`: - -- `private_key.pkcs7.pem` -- `public_key.pkcs7.pem` - -To encrypt the values before creating the cluster, the encryptions keys can be generated beforehand and then transferred on the Puppet server. - -The keys can be generated with `eyaml`: -``` -eyaml createkeys -``` - -or `openssl`: -```sh -openssl req -x509 -nodes -days 100000 -newkey rsa:2048 -keyout private_key.pkcs7.pem -out public_key.pkcs7.pem -subj '/' -``` - -The resulting public key can then be used to encrypt secrets, while the private and the public keys have to be transferred on the Puppet server to allow it to decrypt the values. In the following command examples, replace -`puppet` by the hostname of your puppetserver (i.e.: `mgmt1`). - -1. Transfer the keys on the Puppet server using SCP with SSH jumphost - ```sh - scp -J centos@cluster.yourdomain.cloud {public,private}_key.pkcs7.pem centos@puppet:~/ - ``` -2. Replace the existing keys by the one transferred: - ```sh - ssh -J centos@cluster.yourdomain.cloud centos@puppet sudo cp {public,private}_key.pkcs7.pem /etc/puppetlabs/puppet/eyaml - ``` -3. Remove the keys from the admin account home folder: - ```sh - ssh -J centos@cluster.yourdomain.cloud centos@puppet rm {public,private}_key.pkcs7.pem - ``` - -To backup the encryption keys from an existing Puppet server: - -1. Create a readable copy of the encryption keys in the sudoer home account - ```sh - ssh -J centos@cluster.yourdomain.cloud centos@puppet 'sudo rsync --owner --group --chown=centos:centos /etc/puppetlabs/puppet/eyaml/{public,private}_key.pkcs7.pem ~/' - ``` -2. Transfer the files locally: - ```sh - scp -J centos@cluster.yourdomain.cloud centos@puppet:~/{public,private}_key.pkcs7.pem . - ``` -3. Remove the keys from the sudoer account home folder: - ```sh - ssh -J centos@cluster.yourdomain.cloud centos@puppet rm {public,private}_key.pkcs7.pem - ``` - -### 10.14 Read and edit secret values generated at boot +### 10.13 Read and edit secret values generated at boot During the cloud-init initialization phase, [`bootstrap.sh`](https://github.com/ComputeCanada/puppet-magic_castle/blob/main/bootstrap.sh) @@ -1809,7 +1798,7 @@ the hieradata configuration file. Refer to section [4.13 hieradata](#413-hierada User defined values take precedence over boot generated values in the Magic Castle Puppet data hierarchy. -### 10.15 Expand a volume +### 10.14 Expand a volume Volumes defined in the `volumes` map can be expanded at will. After their creation, you can increase their size in the `main.tf` then call `terraform apply` and the associated block diff --git a/gcp/infrastructure.tf b/gcp/infrastructure.tf index 6355c0aa..068b2f5c 100644 --- a/gcp/infrastructure.tf +++ b/gcp/infrastructure.tf @@ -43,6 +43,7 @@ module "provision" { terraform_facts = module.configuration.terraform_facts hieradata = var.hieradata sudoer_username = var.sudoer_username + eyaml_key = var.eyaml_key depends_on = [ google_compute_instance.instances ] } diff --git a/openstack/infrastructure.tf b/openstack/infrastructure.tf index 7a062505..af16bf5e 100644 --- a/openstack/infrastructure.tf +++ b/openstack/infrastructure.tf @@ -38,6 +38,7 @@ module "provision" { terraform_facts = module.configuration.terraform_facts hieradata = var.hieradata sudoer_username = var.sudoer_username + eyaml_key = var.eyaml_key depends_on = [ local.network_provision_dep, openstack_compute_instance_v2.instances,