How dynamic credentialing makes apps portable

David Thor - 06/04/20

dynamic credentials

In my last post we talked about how to leverage secret managers to safely store and cycle application credentials in production. In this post we're going to take the concept of credential cycling a step further to streamline the ability for an app or service to be deployed to parallel environments through dynamic credentialing.

Allowing apps to be deployed without manual credential generation is critical to making it portable and deployable by anyone – an important principal that enables peer developers to run and integrate with your application in their own private sandboxing or integration tests. In this tutorial, we'll describe what dynamic credentialing is and apply it to a practical use-case – integration with a hosted service, Auth0.

Why we need dynamic credentialing

If you've written any backend app recently, you've likely had to connect to some hosted API service. This might be an AWS service like SNS, SQS, or S3, or it might be an independent product like Stripe or Auth0. All of these services require credentials in order to access them and scope your access to your own account with the provider. As a result, it's also likely that you parameterized your app to allow operators to provide these credentials at runtime.

Parameterizing an application to receive credentials from an operator is an extraordinarily common and useful way for a service to be deployed to multiple environments in parallel. It allows an operator to run your app in dev, staging, and production environments with different parameter values that access different accounts or scopes. This is critical to operating the service in a safe way, as you wouldn't want an unstable dev environment to access production data and scopes.

However, the creation of these credentials is yet another manual step an operator must perform in order to stand up a new environment. While creating these credentials once or twice for static production and staging environments isn't terribly cumbersome, the friction of credential creation is an inhibitor to others who would seek to stand up their own environments. This friction prevents developers from standing up private sandboxes, environments to execute integration tests, and from deploying the service easily to private customer environments.

Dynamic credentialing has many benefits, but an often overlooked benefit is the ability for unlimited parallel environments to be generated without endless, manual creation of credentials. By loading credentials dynamically, applications are able to be freely provisioned without the manual step of credential generation for each environment. This can have enormous value for larger teams especially who benefit from the democratization of deployments to provision private sandboxes or end-to-end integration test environments without any manual steps required.

Implemeting dynamic credentialing with Hashicorp Vault

In a previous post, I outlined how you can setup and connect to Hashicorp Vault to store and manage application secrets. What I didn't go into then was some of Vault's more advanced features – namely dynamic secrets. In this tutorial, we'll walk through how to setup a postgres instance, how to use the database engine to setup a dynamic secret in vault, and how applications call into vault to acquire distinct credentials for the postgres instance.

Just like our last walk-through, you'll need a postgres instance to get started. Let's go ahead and create one locally with Docker. Be sure to take note of the username and password you use when creating the instance too. Those will act as your root credentials, and Vault will need them in follow-up steps in order to request new credentials.

# Your postgres instance will be available on localhost:5432 once complete
$ docker run -d -p 5432:5432 \
    -e POSTGRES_USER=root \
    -e POSTGRES_PASSWORD=password \
    -e POSTGRES_DB=postgres \
    postgres:11

Now that you have a postgres instance running, let's go ahead and enable and configure the database secrets engine in vault:

# Enable the secrets engine
$ vault secrets enable database

# Create our dynamic secret
$ vault write database/config/postgresql \
    plugin_name=postgresql-database-plugin \
    allowed_roles=readonly \
    connection_url=postgresql://root:password@localhost:5432/postgres?sslmode=disable

The steps above, you configured the secrets engine to point to your new postgres instance, and also gave permission to a role, readonly, as an allowed member for the plugin. Now we actually need to define that role.

$ vault write database/roles/readonly db_name=postgresql \
    creation_statements=@readonly.sql \
    default_ttl=1h max_ttl=24h

The above command will create the role, but you'll notice that in the command we refer to the contents of readonly.sql as the creation_statements. This file contains the commands that will be used to generate postgres credentials, and looks something like the following:

CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO "{{name}}";

The handlebars parameter references like {{name}} will be populated by vault automatically.

Now that we have a role capable of creating our new postgres credentials, we need to create an app policy and token that can be used to request these credentials from vault. To begin, write the following basic vault policy to policy.hcl:

path "database/creds/readonly" {
  capabilities = [ "read" ]
}

Next, we'll write that policy to vault, and generate a corresponding access token:

$ vault policy write apps policy.hcl
$ vault token create -policy="apps"

Key                  Value
---                  -----
token                ...
token_accessor       ...
token_duration       768h
token_renewable      true
token_policies       ["apps" "default"]
identity_policies    []
policies             ["apps" "default"]

Finally, we're ready to invoke vault to request a dynamic secret! Every time you run the command below, Vault will respond with an entirely new username/password combo:

VAULT_TOKEN=<token from prior command> vault read database/creds/readonly

Congratulations! You've successfully setup dynamic credentialing in Vault. Now, every service that needs access to your postgres DB can cite the same secret and each will be issued distinct, independent credentials. Used correctly, you'll be able to distribute service to peer teams in a more portable way so long as everyone is sharing a vault instance.

Special thanks to the Hashicorp team for maintaining such excellent documentation and tutorials!


Want to help inspire our own solution for dynamic credentialing that doesn't involve a broker like Vault? We're actively designing this feature for our microservices framework, and would love your input!

Join the conversation on Github