ComputeCanada / magic_castle

Terraform modules to replicate the HPC user experience in the cloud
MIT License
118 stars 35 forks source link

Providing the Puppet hieradata encryption keys as a terraform variable #260

Closed ocaisa closed 4 months ago

ocaisa commented 11 months ago

Currently, one needs to bring a cluster up and manually extract/use (or replace) the Puppet hieradata encryption keys to create the profile::slurm::controller::tfe_token for cluster scaling. If one could provide the keys as a terraform variable (and from that deploy them on the puppet server) then you can set profile::slurm::controller::tfe_token in advance since you know the key, and get scaling working without the need for additional steps after the cluster comes up.

ocaisa commented 11 months ago

As a terraform variable, you can store/maintain the keys on Terraform Cloud.

ocaisa commented 11 months ago

This does mean the keys would become part of the terraform state, but I think this is ok since the use case is a cluster managed by Terraform Cloud.

The variable might look like:

puppet_pkcs7 = { public = file("public_key.pkcs7.pem"), private = file("private_key.pkcs7.pem") } 

and then you could have a conditional deploy step for the keys in https://github.com/ComputeCanada/magic_castle/blob/main/common/provision/main.tf

cmd-ntrf commented 4 months ago

I have started working on this.

cmd-ntrf commented 4 months ago

After thinking about this, one thing bugged me really bad: why do hiera-yaml need the public key to decrypt the secret? Public key is normally used to encrypt and private key to decrypt, why needing both when decrypting?

After reading PKCS7 RFC, I figured that what is called a "public key" in hiera-eyaml context, is actually a signing certificate that in the general context of PKCS7 can be used to validate the signature of the encrypted value while the private key is used to decrypt the encrypted text. The RFC mentions a situation where there no signing certificate. So I dug some more on the Ruby side of thing and eventually found out why hiera-eyaml requires the "public key" to decrypt: there was a bug in ruby-openssl. PKCS7#decrypt function did not support being provided nil for the signing certificate](https://github.com/rhenium/ruby-openssl/commit/769b5575d157aca77b78e44d94ea40ad08e3975e).

This bug was fixed in ruby-openssl 2.2.1. The author of the decrypt function in hiera-eyaml mentionned that he noted that both the public and the private key had to be provided when implementing it.

Puppet 7 ships with ruby 2.7 and ruby-openssl 2.1.4, so no dice to get the decrypt function with public key optional. However, Puppet 8.6 ships with ruby 3.2.3 and ruby-openssl 3.1.0.

I wanted to test this, so I installed Puppet 8, hiera-eyaml, and patched:

/opt/puppetlabs/puppet/lib/ruby/gems/3.2.0/gems/hiera-eyaml-3.4.0/lib/hiera/backend/eyaml/encryptors/pkcs7.rb

replacing

pkcs7.decrypt(private_key_rsa, public_key_x509)

by

pkcs7.decrypt(private_key_rsa, nil)

and it works just fine!

cmd-ntrf commented 4 months ago

I submitted a PR to hiera-eyaml - https://github.com/voxpupuli/hiera-eyaml/pull/364.

Since openssl is a default Ruby gem, it is bundled with a specific Ruby version and therefore depends on Puppet version. Puppet 7 uses Ruby 2.7, which highest openssl version is 2.1, see https://stdgems.org/default_gems.json. This unfortunately is too old. So to move forward with this, we will have to move to Puppet 8, which comes bundled with Ruby 3.2.

Steps to move forward with this:

In case none of my PR make it into hiera-eyaml, it is possible to generate a certificate with the private key with the serial number set to 1 as hiera-eyaml would do it. So we don't have to transmit the public key, just the private one.

openssl genrsa -out private_key.pkcs7.pem 2048
openssl req -new -key private_key.pkcs7.pem -out public_key.pkcs7.pem -set_serial 1 -batch