Skip to content

Latest commit

 

History

History
 
 

2022-05-24-VaultTFC

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

Using Vault with TFC

Here's the big idea. When you're using workspaces in TFC to provision infrastructure, you need credentials for Terraform to use against whatever clouds you're deploying on. What are your options for storing those credentials?

  • Store it as an environment variable in the workspace
  • Store it as an environment variable in a variable set
  • Use a hosted runner with machine authentication
  • Use Vault to dynamically generate credentials

There's pros and cons of each. Let's briefly review them and then circle back.

Store it as an environment variable in the workspace

This works just fine if you only have a few workspaces. But as your workspace usage grows, you'll need to set it on every workspace that shares the same credentials. If you need to update those credentials, you'll need to go to every workspace and update the settings. This is tedious and error prone. Plus, it also implies many of your workspaces will be using the same credentials, which may or may not be what you want.

Store it as an environment variable in a variable set

You can assign a variable set to one or more workspaces, so this solves the problem of adding the credentials to every workspace manually. It also solves the problem of rotating a credential. You've still got multiple workspaces using the same long-lived credentials, which may not be ideal. But I think for most folks this solution is perfectly acceptable.

Use a hosted runner with machine authentication

By default, TFC uses runner machine hosted by HashiCorp on their infrastructure. Since the machines don't have an identity associated with your cloud accounts, they cannot use machine-based authentication to provision infrastructure. If you happen to be at the Business tier, you can run self-hosted agents in your environment. Now you can use machine authentication, and you don't have to store credentials in TFC. Of course, that means you'll be managing a fleet of machines, and you'll need to control what roles each machine is assigned. You're probably not going to spin up a machine for every workspace, so you'll be sharing credentials once again.

Use Vault to dynamically generate credentials

Instead of using these long-lived credentials, what if you could have Vault dynamically generate credentials for each Terraform run and then dispose of them after the run completes? That seems useful! Of course, now you need a way to authenticate back to Vault. There are a bunch of auth methods in Vault, but none of them are TFC. Hey HashiCorp! That seems like a useful thing to add!!!

Until there is a TFC auth method, we're stuck back at square one. If you want to use Vault, there are a few auth methods that make sense:

  • AppRole: Store the Role ID and Secret ID in TFC
  • Token: Store a super long lived token in TFC
  • Cloud IAM: Use a hosted machine in one of the clouds

The first two options still require you to store some type of credential in TFC. You can store it with the workspace or as a variable set. On the bright side, if someone leaks the credentials, they are time bound and simple to revoke. And the interceptor would need to know the address of your Vault server and the auth method path on the Vault server. Unlike if they got the cloud credentials, where they could start reeking havoc right away.

The third option puts us back in the Business tier of TFC with a self-hosted runner.

Why don't we try setting up TFC to work with a Vault instance, leveraging the AppRole auth method. We'll need to store a Role ID and Secret ID. For our purposes, we won't set a TTL or limit the number of uses for the Secret ID. Generally speaking, you would generate a different Secret ID for each machine leveraging the role, but we just have TFC.

Deploying the Vault resources

In the enable_approle directory is a Terraform configuration that will create the following resources:

  • Vault
    • AppRole auth method
    • Vault policy for AppRole role
    • AppRole role with role ID
    • AppRole secret ID for the role ID
    • Azure secrets backend
    • Azure secrets backend role with Contributor Access
  • Azure
    • Azure AD application
    • Azure AD service principal
    • Role assignments to Microsoft Graph
    • Role assignment of Owner to the Azure Subscription

The service principal created in Azure AD will be used by Vault to provision dynamic service principals from the Azure secret method and grant them Contributor access on the current Azure subscription. In a real world scenario, you would have a list of subscriptions that you want to grant permissions to, and maybe multiple role IDs, one per environment.

You'll need an instance of Vault running to deploy the resources. I recommend using the Development tier of HCP Vault.

Follow the instructions file in the directory to get things deployed.

Deploying the Azure resources

The files in the use_approle directory will do the following:

  • Vault
    • Authenticate to the provider using the Role ID and Secret ID we generated earlier
    • Use the Azure secrets data source to create an Azure service principal dynamically
  • Azure
    • Authenticate to the provider using our shiny new service principal
    • Create a resource group

Follow the instructions file in the directory to get things deployed.

There is another option, but it requires stepping outside of the standard VCS workflow and using the API workflow instead.

Moving to TFC

Obviously this is all running locally and not on TFC. Moving things will require either checking your stuff into source control or updating the backend for Terraform with a cloud block. Once you've done so, you can migrate your variable values as needed to the workspace or a variable set.

GitHub Actions

Vault supports GitHub authentication. If you're storing your code in GitHub anyway, you could have GitHub actions kick off when a PR or merge comes in to trigger a Terraform run on TFC. As part of the GitHub Actions, it could authenticate to Vault and generate a single use token for the Terraform run. Then TFC uses that token to dynamically generate whatever credentials it needs.

Unfortunately, the token generated during a GitHub Actions run doesn't have the required read:org permissions that Vault GitHub authentication needs. You cannot change the permissions assigned to the GitHub Actions token (for safety reasons), so you would need to generate a new token and store in the Secrets for the repository or environment. This honestly isn't much better than storing everything in TFC, but it is an option.

I'll leave that as a later exercise for you and me. But I thought I would bring it up since it's interesting.