Forking a Terraform Provider for PGP encryption: Adding support for new key types¶
Coop Norge SA uses Terraform ⧉ heavily to manage cloud resources as infrastructure as code. Some of the created cloud resources also creates a set of credentials, required to access these resources. If the credentials should be distributed to a team inside Coop, they can be distributed directly to a secure key vault in the cloud, but if they need to be distributed to an external party (such as one of Coop's partners), another secure way must be used. Coop Norge SA opted to share secrets by encrypting files containing the secrets with asymmetric cryptographic keys, using Pretty Good Privacy (PGP) ⧉.
Since the cloud resources, and key vault distribution, are managed by
Terraform, it also makes sense to distribute the encrypted keys by using
Terraform, so that any changes to the credentials would be redistributed. For
that task, an open source Terraform Provider named
invidian/gpg ⧉ has been
used. That provider was written in Go, and used the
golang.org/x/crypto/openpgp
package to do the encryption part, and worked
fine for a long time.
Key Types¶
The golang.org/x/crypto/openpgp
package has been able to encrypt messages
using RSA ⧉ for a while,
but as the modern world has moved towards
elliptic-curve cryptography (ECC) ⧉,
the Go cryptography libraries chose to not implement ECC support in their
OpenPGP package. They also ultimately ended up choosing to
freeze and deprecate ⧉ the entire
golang.org/x/crypto/openpgp
package, and recommends Go developers to use
community forks instead.
The GNU Privacy Guard (GnuPG) ⧉, known as the most common
OpenPGP implementation (the gpg
command line interface tool), has on the
other hand made ECC the default key type.
GitHub also uses GPG keys ⧉ to sign commits and tags, and they support ECC keys. GitHub recommends the use of GnuPG, so developers are likely to create a GPG key set up with the default values (ECC).
The structure of a GPG key¶
Without going into too much detail of what composes a GPG key, it typically contains the following parts:
- 1 primary key for signing and certification
- One of more subkeys for encryption
GnuPG's default ECC configuration when creating a new key creates a GPG key with the following components:
- A primary key of type
ed25519
(for use with Edwards-curve Digital Signature Algorithm (ECDSA) ⧉) for signing and certifying. - A subkey of type
cv25519
(for use with the Elliptic Curve Diffie-Hellman Algorithm (ECDH) ⧉) for encrypting and decrypting messages.
GitHub's main concern lies with the primary signing key, while Coop's use case is with the encryption subkeys.
The frictions¶
To safely create the encrypted messages, and only allow the intended recipients to be able to decrypt the messages, the developers needs to upload their GPG public keys to the Terraform repository. On a Terraform apply, it would encrypt each message to all of it's intended recipient's public key, and then that encrypted message can be securely distributed via unsecure channels like email or Slack.
Whenever a developer attempted to upload their existing GPG key (containing an ECC encryption key), they got an error stating that the system only accepted RSA key, and a link to the internal developer documentation with steps on how to generate a new RSA GPG key. This became a point of frequent friction for developers.
The forking¶
The Terraform provider that was used to provide GPG encryption in Terraform, had turned out to be stale, not getting any updates (e.g. it was using Go v1.17 released in August 2021, when the newest as of writing this blog is v1.23 released in August 2024), and the sole developer had stopped responding to issues on GitHub. Coop decided to fork the repository ⧉, and add support for ECC keys, as that was the most pressing issue. Since the Go cryptography package was deprecated, it had to be replaced with a community-driven GPG package, and it was decided to go with ProtonMail's implementation.
The migration was pretty quick, as ProtonMail's
gopenpgp
⧉ package was quite similar
to the golang.org/x/crypto/openpgp
package. Some steps were done to refactor
the encryption part away from the Terraform provider part, to make it easier to
unit test.
The publishing¶
The provider must first be published before it can be accessed from Terraform code. Coop is using Spacelift ⧉ to run Terraform code, which has support for a private provider registry. Since other developers might want to use this improved Terraform provider, it was decided to open source the provider, and publish it on the public Terraform provider registry instead of a private registry.
To publish a Terraform provider, prebuilt artifacts has to be created, and uploaded to a GitHub Release ⧉, and a signed checksum has to be created, with a GPG key that has been uploaded to the Terraform provider registry. Luckily, the Terraform documentation includes a complete config of GoReleaser ⧉ that does all of this.
Ironically, Terraform's public provider registry does not yet support Git
tags signed with ECC keys, so a different supported signing algorithm had to be
chosen (e.g. dsa3072
).
Want to use the provider?¶
Since the provider is published to the public Terraform provider registry, the provider documentation ⧉ should be enough to get you started. Feature requests and bug reports can be reported to the official provider repository ⧉.
Next steps¶
Now that everything has been set up and migrated, and the provider is published to the Terraform provider registry, there are several tasks that can be started:
- Configure automatic publishing from GitHub Actions, instead of having to do manual publishing from a developer's computer.
- Advertise that Coop has forked and improved the GPG provider.
- Publish/mirror the provider to OpenTofu's provider registry.
Notes¶
A Special thanks to Mateusz Gozdek ⧉ for the initial implementation of the Terraform provider.