One of the main challenges with using Terraform in production is to securely manage the secrets required for various cloud services, databases, and APIs. While many organizations heavily rely on Hashicorp Vault or use cloud-native KMS services for the purpose of storing and retrieving passwords, secrets, and API keys.
In this article, we shall explore - an open-source alternative to securely store and retrieve secrets with Terraform. This solution is well-defined and widely adopted, mainly with organizations that are already using CyberArk solutions for identity and access management.
Conjur is an open-source security service that helps to manage and protect secrets, API keys, and other sensitive information. It integrates with Terraform to securely manage secrets and reduce the risk of unauthorized access.
Summon is another open-source tool that can be used to inject secrets into Terraform configuration files. Summon retrieves secrets from Conjur and other secret stores and decrypts them, and then injects them into the Terraform environment.
Here is an overview of how to secure Terraform secrets with Conjur and inject them using Summon:
Install Conjur:
First, you need to install Conjur. You can install it on-premises or use the Conjur Enterprise SaaS version. Conjur provides a secure secret store where you can store your secrets, API keys, and other sensitive information.
Install Summon:
Install Summon on your system. Summon is available for Linux, macOS, and Windows.
Configure Conjur:
Configure Conjur to store your secrets. You can store the secrets as key-value pairs or in JSON format.
Configure Terraform:
Modify your Terraform configuration file to include placeholders for the secrets you want to inject. For example, you can use the syntax ${conjur:variable_name} to represent a Conjur secret.
Hands-on Solution:
With this scenario, will learn how to use Terraform provider for Conjur to retrieve secrets from Conjur Open Source to Terraform. First, we will make use of Terraform to spin up a Postgres database container. A random password for the database will be generated, and it will be secured by Conjur.
Next, in order for the database client to connect to the newly provisioned database, CyberArk summon will be used for secret injection. The random password from Conjur will be retrieved by the Terraform provider for Conjur It will be then injected into the new Postgres container as an environment variable for authentication.
Let's provision a Postgres database using Terraform.
Install Terraform and Conjur:
ubuntu $ terraform version
Terraform v1.3.6
on linux_amd64
Your version of Terraform is out of date! The latest version
is 1.3.9. You can update by downloading from https://www.terraform.io/downloads.html
ubuntu $ conjur --version
Conjur CLI version 7.0.1
Copyright (c) 2021 CyberArk Software Ltd. All rights reserved.
<www.cyberark.com>
ubuntu $
Verify, that you can log in to CyberArk Conjur CLI :
ubuntu $ conjur login -i admin -p b81t11ebd2en115rjc3bbyfhhhtvcttyc0bm42jcagzreb8pd7
WARNING: No supported keystore found! Saving credentials in plaintext in '/root/.netrc'. Make sure to logoff after you have finished using the CLI
Successfully logged in to Conjur
ubuntu $
Conjur Policy:
Next, we need to create an application identity for Conjur based on the API key.
Conjur Policy is a policy language used to define and enforce security policies for applications and services. It is used to define the access control rules for applications and services, such as who can access what resources and what actions they can perform. Conjur Policy is written in the declarative language and is composed of a set of rules that define the access control policies for applications and services.
The policy is then enforced by the Conjur Policy Engine, which evaluates the policy and enforces the rules. The policy engine first checks if the user making a request has the necessary permissions to perform the requested action. If the user does not have the necessary permissions, then the policy engine will deny the request. If the user does have the necessary permissions, then the policy engine will allow the request.
Root Policy:
ubuntu $ cat policy/root.yml
- !policy
id: terraform
- !policy
id: postgres
ubuntu $
The root policy contains 2 policies, terraform and postgres.
ubuntu $ conjur policy load -b root -f policy/root.yml
{
"created_roles": {},
"version": 1
}
ubuntu $
Terraform Policy:
ubuntu $ cat policy/terraform.yml
- !layer
- !host frontend-01
- !grant
role: !layer
member: !host frontend-01
ubuntu $
This policy does the following:
Declares a layer that inherits the name of the policy under which it is loaded. In our example, the layer name will become terraform.
Declares a host named frontend-01
Adds the host into the layer. A layer may have more than one host. You may need to change the following items in your own environment:
Change the host name to match the DNS host name of your Jenkins host. Change it in both the !host statement and the !grant statement.
Optionally declare additional Jenkins hosts. Add each new host as a member in the !grant statement.
Load the policy:
ubuntu $ conjur policy load -b terraform -f policy/terraform.yml | tee frontend.out
{
"created_roles": {
"default:host:terraform/frontend-01": {
"id": "default:host:terraform/frontend-01",
"api_key": "1qrgc4y3vxyyjc1qwms6k3ezk1d13n75fg13zqbxf714gr8wm2bmwa2x"
}
},
"version": 1
}
ubuntu $
As it creates each new host, Conjur returns an API key.
Protected Resource:
ubuntu $ cat policy/postgres.yml
- &variables
- !variable admin-password
- !group secrets-users
- !permit
resource: *variables
privileges: [ read, execute ]
roles: !group secrets-users
- !layer
- !host client-01
- !grant
role: !layer
member: !host client-01
# Entitlements
- !grant
role: !group secrets-users
member: !layer /terraform
- !grant
role: !group secrets-users
member: !layer
ubuntu $
This policy does the following:
Declares the variables to be retrieved by Terraform.
Declares the groups that have read & execute privileges on the variables.
Declares a layer that inherits the name of the policy under which it is loaded. In our example, the layer name will become postgres.
Declares a host named client-01
Add the host into the layer. A layer may have more than one host.
Add postgres & terraform layer to the group.
Load the policy and add the variable:
ubuntu $ conjur policy load -b postgres -f policy/postgres.yml | tee postgres.out
{
"created_roles": {
"default:host:postgres/client-01": {
"id": "default:host:postgres/client-01",
"api_key": "2tvda8436tqwxg3p5ng3v3bg62jzje3yw341x7b02nqsa3w1exgjdr"
}
},
"version": 1
}
ubuntu $ dbpass=$(openssl rand -hex 12)
ubuntu $ conjur variable set -i postgres/admin-password -v "$dbpass"
Successfully set value for variable 'postgres/admin-password'
ubuntu $
Provision Postgres DB with Terraform:
Review the TF Files, there are three terraform files
conjur.tf contains the path of secret in Conjur
docker.tf specifies the docker settings
postgre.tf specifies the database configuration, including the use of POSTGRE_PASSWORD environment variable
conjur.tf
ubuntu $ cat conjur.tf
terraform {
required_providers {
conjur = {
source = "cyberark/conjur"
version = "0.6.3"
}
docker = {
source = "kreuzwerker/docker"
version = "2.11.0"
}
}
}
provider "conjur" {
appliance_url = "https://proxy:8443"
account = "default"
}
docker.tf
ubuntu $ cat docker.tf
resource "docker_network" "demo" {
name = "demo"
}
postgres.tf
ubuntu $ cat postgres.tf
data "conjur_secret" "admin-password" {
name = "postgres/admin-password"
}
resource "docker_image" "postgres" {
name = "postgres:9.3"
}
resource "docker_container" "postgres" {
name = "postgres"
image = "${docker_image.postgres.latest}"
env = ["POSTGRES_PASSWORD=${data.conjur_secret.admin-password.value}"]
networks_advanced {
name="${docker_network.demo.name}"
}
ports {
internal = 5432
external = 5432
}
}
Terraform Apply:
export CONJUR_AUTHN_LOGIN="host/terraform/frontend-01"export CONJUR_AUTHN_API_KEY=$(grep api_key frontend.out | cut -d: -f2 | tr -d ' \r\n' | xargs)
export CONJUR_SSL_CERTIFICATE="$(openssl s_client -showcerts -connect proxy:8443 < /dev/null 2> /dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p')"
terraform init
terraform apply
docker_image.postgres: Creating...
docker_network.demo: Creating...
docker_image.postgres: Creation complete after 0s [id=sha256:c230b2f564daa6fa26a71c988fff466aca3dd04a9c12ea2dce82dc22105a65abpostgres:9.3]
docker_network.demo: Creation complete after 2s [id=410996d7575c6315ce275b407d5061cba8494f53290c5601c187d7f4136e35b5]
docker_container.postgres: Creating...
docker_container.postgres: Creation complete after 0s [id=4330bfbd393909f4fdd017a56deeff12e7b70fbf25d54bfcfe879d5837bacdc8]
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
We successfully provisioned the Postgres DB docker container and passed the Postgres Password, which is securely stored on the CyberArk Conjur.
Connecting to Postgres DB:
we have the Postgres DB client installed:
ubuntu $ psql --version
psql (PostgreSQL) 12.14 (Ubuntu 12.14-0ubuntu0.20.04.1)
ubuntu $
Install Summon:
Installing summon is simple. Simply install 2 files and summon is ready to go.
curl -sSL https://raw.githubusercontent.com/cyberark/summon/main/install.sh | bash
curl -sSL https://raw.githubusercontent.com/cyberark/summon-conjur/main/install.sh | bash
Review secrets.yml:
To inject the password to Postgres client using Summon, secrets.yml file is needed.
To review, execute cat secrets.yml
PGPASSWORD: !var postgres/admin-password
Client Application Identity:
We can configure the client application identity using environment variables
export CONJUR_AUTHN_LOGIN="host/postgres/client-01"export CONJUR_AUTHN_API_KEY=$(grep api_key postgres.out | cut -d: -f2 | tr -d ' \r\n' | xargs)
export CONJUR_APPLIANCE_URL=https://proxy:8443export CONJUR_ACCOUNT=defaultexport CONJUR_SSL_CERTIFICATE="$(openssl s_client -showcerts -connect proxy:8443 < /dev/null 2> /dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p')"
Connect to Postgres DB using Summon:
Let's login to the database, we shall use summon, which will inject the secret for us by executing:
ubuntu $ summon psql -h host01 -U postgres
psql (12.14 (Ubuntu 12.14-0ubuntu0.20.04.1), server 9.3.25)
Type "help" for help.
postgres=#
By following these steps, you can securely manage your Terraform secrets with Conjur and inject them using Summon. This approach helps to reduce the risk of unauthorized access to your sensitive information and provides a more secure environment for your infrastructure.
Comments