Skip to content

Commit

Permalink
Merge pull request #303 from ComputeCanada/puppetserver_pkcs7
Browse files Browse the repository at this point in the history
Fix issue #260
  • Loading branch information
cmd-ntrf authored May 7, 2024
2 parents 5dfc90a + 0cecd1f commit 1dbec22
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 103 deletions.
1 change: 1 addition & 0 deletions aws/infrastructure.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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]
}

Expand Down
1 change: 1 addition & 0 deletions azure/infrastructure.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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 ]
}

Expand Down
13 changes: 6 additions & 7 deletions common/configuration/puppet.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,18 @@ runcmd:
- systemctl daemon-reload
- systemctl enable puppetserver
# Install gem dependencies
- "/opt/puppetlabs/puppet/bin/gem install autosign:1.0.1 hiera-eyaml:3.4.0 faraday:2.8.1 faraday-net_http:3.0.2 puppet_forge:4.1.0 r10k:4.0.1"
- "/opt/puppetlabs/puppet/bin/gem install autosign:1.0.1 faraday:2.8.1 faraday-net_http:3.0.2 puppet_forge:4.1.0 r10k:4.0.1"
- curl -L -o /opt/puppetlabs/puppet/lib/ruby/vendor_gems/gems/hiera-eyaml-3.4.0/lib/hiera/backend/eyaml/encryptors/pkcs7.rb https://raw.githubusercontent.com/MagicCastle/hiera-eyaml/6e40e4618579b4804c4b6157279d057365bbd561/lib/hiera/backend/eyaml/encryptors/pkcs7.rb
# Enable autosign with password
- chgrp puppet /etc/autosign.conf
- chown puppet:puppet /var/log/autosign.log
- /opt/puppetlabs/bin/puppet config set autosign /opt/puppetlabs/puppet/bin/autosign-validator --section server
- /opt/puppetlabs/bin/puppet config set allow_duplicate_certs true --section server
# Generate hieradata asymmetric encryption key
# Generate bootstrap hieradata asymmetric encryption key
- mkdir -p /etc/puppetlabs/puppet/eyaml
- /opt/puppetlabs/puppet/bin/eyaml createkeys --pkcs7-private-key=/etc/puppetlabs/puppet/eyaml/private_key.pkcs7.pem --pkcs7-public-key=/etc/puppetlabs/puppet/eyaml/public_key.pkcs7.pem
- /opt/puppetlabs/puppet/bin/eyaml createkeys --pkcs7-private-key=/etc/puppetlabs/puppet/eyaml/boot_private_key.pkcs7.pem --pkcs7-public-key=/etc/puppetlabs/puppet/eyaml/boot_public_key.pkcs7.pem
- chown -R puppet:puppet /etc/puppetlabs/puppet/eyaml
- chmod 0400 /etc/puppetlabs/puppet/eyaml/private_key.pkcs7.pem
- chmod 0400 /etc/puppetlabs/puppet/eyaml/boot_private_key.pkcs7.pem
- "(cd /etc/puppetlabs/puppet/eyaml; openssl req -x509 -nodes -newkey rsa:2048 -keyout boot_private_key.pkcs7.pem -out boot_public_key.pkcs7.pem -batch)"
- chown -R root:puppet /etc/puppetlabs/puppet/eyaml
- chmod 0640 /etc/puppetlabs/puppet/eyaml/boot_private_key.pkcs7.pem
# Setup puppet environment code and modules
- rm -rf /etc/puppetlabs/code/environments/production
- git clone ${puppetenv_git} /etc/puppetlabs/code/environments/main
Expand Down
65 changes: 44 additions & 21 deletions common/provision/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,41 @@ variable "terraform_facts" { }
variable "hieradata" { }
variable "sudoer_username" { }
variable "tf_ssh_key" { }
variable "eyaml_key" { }

resource "terraform_data" "deploy_hieradata" {
locals {
provision_folder = "puppetserver_etc"
}

data "archive_file" "puppetserver_files" {
type = "zip"
output_path = "${path.module}/files/${local.provision_folder}.zip"

source {
content = var.terraform_data
filename = "${local.provision_folder}/data/terraform_data.yaml"
}

source {
content = var.terraform_facts
filename = "${local.provision_folder}/facts/terraform_facts.yaml"
}

source {
content = var.hieradata
filename = "${local.provision_folder}/data/user_data.yaml"
}

dynamic "source" {
for_each = var.eyaml_key != "" ? [var.eyaml_key] : []
content {
content = var.eyaml_key
filename = "${local.provision_folder}/puppet/eyaml/private_key.pkcs7.pem"
}
}
}

resource "terraform_data" "deploy_puppetserver_files" {
for_each = length(var.bastions) > 0 ? var.puppetservers : { }

connection {
Expand All @@ -20,33 +53,23 @@ resource "terraform_data" "deploy_hieradata" {
}

triggers_replace = {
user_data = md5(var.hieradata)
terraform_data = md5(var.terraform_data)
facts = md5(var.terraform_facts)
}

provisioner "file" {
content = var.terraform_data
destination = "terraform_data.yaml"
}

provisioner "file" {
content = var.terraform_facts
destination = "terraform_facts.yaml"
archive = data.archive_file.puppetserver_files.output_sha256
}

provisioner "file" {
content = var.hieradata
destination = "user_data.yaml"
source = "${path.module}/files/${local.provision_folder}.zip"
destination = "${local.provision_folder}.zip"
}

provisioner "remote-exec" {
inline = [
"sudo mkdir -p /etc/puppetlabs/data /etc/puppetlabs/facts",
# 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",
# unzip is not necessarily installed when connecting, but python is.
"/usr/libexec/platform-python -c 'import zipfile; zipfile.ZipFile(\"${local.provision_folder}.zip\").extractall()'",
"sudo chmod g-w,o-rwx $(find ${local.provision_folder}/ -type f)",
"sudo chown -R root:52 ${local.provision_folder}",
"sudo mkdir -p -m 755 /etc/puppetlabs/",
"sudo rsync -avh --no-t ${local.provision_folder}/ /etc/puppetlabs/",
"sudo rm -rf ${local.provision_folder}/ ${local.provision_folder}.zip",
"[ -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",
]
}
Expand Down
11 changes: 11 additions & 0 deletions common/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,14 @@ 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 = ""
sensitive = true
description = "Private RSA key used to encrypt the data in the hieradata yaml fire"
validation {
condition = can(regex("(|^-----BEGIN PRIVATE KEY-----\n)", var.eyaml_key))
error_message = "Unsupported private key format"
}
}
137 changes: 63 additions & 74 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -704,7 +704,7 @@ randomly generated one.
**Requirement**: Minimum length **8 characters**.
The password can be provided in a PKCS7 encrypted form. Refer to sub-section
[4.13.1 Encrypting hieradata secrets](#4131-encrypting-hieradata-secrets)
[4.14 eyaml_key](#414-eyaml_private-optional)
for instructions on how to encrypt the password.
**Post build modification effect**: trigger scp of hieradata files at next `terraform apply`.
Expand Down Expand Up @@ -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 -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
Expand Down Expand Up @@ -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`
Expand All @@ -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"`
Expand All @@ -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**: `[]`
Expand All @@ -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`
Expand All @@ -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** = `""`
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/terraform_cloud.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ To enable this feature:
Complete the file by replacing `<TFE API TOKEN> ` with the token generated at step 1
and `<TFE workspace id>` (i.e.: `ws-...`) by the id of the workspace created at step 2.
It is recommended to encrypt the TFE API token before committing `data.yaml` in git. Refer
to section [4.13.1 of README.md](./README.md#4131-encrypting-hieradata-secrets) to
to section [4.14 of README.md](./README.md#414-eyaml_private-optional) to
know how to encrypt the token.
6. Add `data.yaml` in git and push.
7. Modify `main.tf`:
Expand Down
1 change: 1 addition & 0 deletions gcp/infrastructure.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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 ]
}

Expand Down
1 change: 1 addition & 0 deletions openstack/infrastructure.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 1dbec22

Please sign in to comment.