April 6 & 7
Learn about Vault, Consul, & more at HashiDays Sydney in Australia Register Now

Access Management

Vault Agent with AWS

Nearly all requests to Vault must be accompanied by an authentication token. This includes all API requests, as well as via the Vault CLI and other libraries.

Vault provides a number of different authentication methods to assist in delivery of this initial token. If you can securely get the first secret from an originator to a consumer, all subsequent secrets transmitted between this originator and consumer can be authenticated with the trust established by the successful distribution and user of that first secret. Getting the first secret to the consumer, is the secure introduction challenge.

Secure Introduction

To that end, Vault provides integration with native authentication capabilities in various environments, for example: IAM in AWS and Google Cloud, Managed Service Identities in Azure, and Service Accounts in Kubernetes.

Challenge

Although a number of auth methods are available, the client is still responsible for managing the lifecycle of its Vault tokens. Therefore, the challenge becomes how to enable authentication to Vault and manage the lifecycle of tokens in a standard way without having to write custom logic.

Solution

Vault Agent provides a number of different helper features, specifically addressing the following challenges:

  • Automatic authentication
  • Secure delivery/storage of tokens
  • Lifecycle management of these tokens (renewal & re-authentication)

NOTE: The Vault Agent Auto-Auth functionality addresses the challenges related to obtaining and managing authentication tokens (secret zero).

Prerequisites

To complete this section of the guide, you will need an AWS account and associated credentials that allow for the creation of resources.

Download demo assets

Clone or download the demo assets from the hashicorp/vault-guides GitHub repository to perform the steps described in this guide.

Clone the repository:

$ git clone https://github.com/hashicorp/vault-guides.git

Or download the repository:

Download

This repository contains supporting content for all of the Vault learn guides. The content specific to this guide can be found within a sub-directory.

Step 1: Provision the Cloud Resources

Be sure to set your working directory to where the /identity/vault-agent-demo/terraform-aws folder is located.

The working directory should contain the provided Terraform files:

$ cd vault-guides/identity/vault-agent-demo/terraform-aws

$ tree
.
├── aws.tf
├── iam.tf
├── kms.tf
├── network.tf
├── outputs.tf
├── security-groups.tf
├── templates
│   ├── userdata-vault-client.tpl
│   └── userdata-vault-server.tpl
├── terraform.tfvars.example
├── variables.tf
├── vault-client.tf
├── vault-server.tf
└── versions.tf

1 directory, 13 files

NOTE: The example Terraform in this repository is created for the demo purpose, and not suitable for production use. For production deployment, refer the following examples:

  1. Set your AWS credentials as environment variables:

    $ export AWS_ACCESS_KEY_ID = "<YOUR_AWS_ACCESS_KEY_ID>"
    
    $ export AWS_SECRET_ACCESS_KEY = "<YOUR_AWS_SECRET_ACCESS_KEY>"
    
  2. Create a file named terraform.tfvars and specify the key_name. In addition, specify the variable values (variables.tf) which you wish to overwrite its default. (Use the provided terraform.tfvars.example as a base.)

    Example terraform.tfvars:

    # SSH key name to access EC2 instances (should already exist)
    key_name = "vault-test"
    
    # All resources will be tagged with this
    environment_name = "va-demo"
    

    If you don't have an EC2 key pair, follow the AWS documentation to create one.

  3. Perform a terraform init to pull down the necessary provider resources. Then terraform plan to verify your changes and the resources that will be created. If all looks good, then perform a terraform apply to provision the resources.

    $ terraform init
    Initializing provider plugins...
    ...
    Terraform has been successfully initialized!
    
    $ terraform plan
    ...
    Plan: 20 to add, 0 to change, 2 to destroy.
    
    $ terraform apply
    ...
    Apply complete! Resources: 20 added, 0 changed, 2 destroyed.
    
    Outputs:
    
    endpoints =
    Vault Server IP (public):  54.219.129.15
    Vault Server IP (private): 10.0.101.50
    
    For example:
      ssh -i vault-test.pem ubuntu@54.219.129.15
    
    Vault Client IP (public):  54.183.212.51
    Vault Client IP (private): 10.0.101.209
    
    For example:
      ssh -i vault-test.pem ubuntu@54.183.212.51
    

    The Terraform output will display the public IP address to SSH into your Vault server and client instances.

Step 2: Configure AWS IAM Auth Method

In this step, you will configure Vault to allow AWS IAM authentication from specific IAM roles.

  1. SSH into the Vault Server instance.

    Output

    ssh -i <path_to_key> ubuntu@<public_ip_of_server>
    ...
    Are you sure you want to continue connecting (yes/no)? yes
    

    When you are prompted, enter "yes" to continue.

  2. To verify that Vault has been installed, run vault status command and notice that Initialized is false.

    $ vault status
    Key                      Value
    ---                      -----
    Recovery Seal Type       awskms
    Initialized              false
    Sealed                   true
    Total Recovery Shares    0
    Threshold                0
    Unseal Progress          0/0
    Unseal Nonce             n/a
    Version                  n/a
    HA Enabled               true
    
  3. Run the vault operator init command to initialize the Vault server:

    $ vault operator init
    
    ...
    
    Initial Root Token: s.20JnHBY66EKTj9zyR6SjTMNq
    
    Success! Vault is initialized
    ...
    
  4. Copy the Initial Root Token value.

  5. Log into Vault using the generated initial root token:

    $ vault login s.20JnHBY66EKTj9zyR6SjTMNq
    
  6. Examine and then execute the /home/ubuntu/aws_auth.sh script.

    # Review the contents of aws_auth.sh script
    $ cat aws_auth.sh
    
    # Execute the script
    $ ./aws_auth.sh
    Success! Enabled the kv secrets engine at: secret/
    Success! Data written to: secret/myapp/config
    Success! Uploaded policy: myapp
    Success! Enabled aws auth method at: aws/
    Success! Data written to: auth/aws/config/client
    Success! Data written to: auth/aws/role/dev-role-iam
    

    This script enables key/value v1 secrets engine at secret and writes some test data at secret/myapp/config. It also creates myapp policy, enables aws auth method and create a role named dev-role-iam.

  7. Check the myapp policy.

    $ vault policy read myapp
    path "secret/myapp/*" {
        capabilities = ["read", "list"]
    }
    
  8. Check the secrets written in secret/myapp/config.

    $ vault kv get secret/myapp/config
    ====== Data ======
    Key         Value
    ---         -----
    password    suP3rsec(et!
    ttl         30s
    username    appuser
    

Step 3: Leverage Vault Agent Auto-Auth

Now, you are going to see how Vault Agent Auto-Auth method works, and write out a token to an arbitrary location on disk. Vault Agent is a client daemon and its Auto-Auth feature allows for easy authentication to Vault.

Vault Agent

NOTE: To run Vault Agent, you need Vault 0.11 or later.

  1. Now, SSH into the Vault Client instance.

Output

ssh -i <path_to_key> ubuntu@<public_ip_of_client>
...
Are you sure you want to continue connecting (yes/no)? yes

When you are prompted, enter "yes" to continue.

  1. Vault binary is already installed and configured to talk to your Vault server on the client instance.

    $ vault status
    
    Key                      Value
    ---                      -----
    Recovery Seal Type       shamir
    Initialized              true
    Sealed                   false
    Total Recovery Shares    5
    Threshold                3
    Version                  1.3.0
    Cluster Name             vault-cluster-f846a973
    Cluster ID               eae2cbc6-d358-6f80-14bf-82d98d3945e7
    HA Enabled               true
    HA Cluster               https://10.0.101.53:8201
    HA Mode                  active
    
  2. In the client instance, explorer the Vault Agent configuration file (/home/ubuntu/vault-agent.hcl).

    $ cat vault-agent.hcl
    
    exit_after_auth = true
    pid_file = "./pidfile"
    
    auto_auth {
      method "aws" {
          mount_path = "auth/aws"
          config = {
              type = "iam"
              role = "dev-role-iam"
          }
      }
    
      sink "file" {
          config = {
              path = "/home/ubuntu/vault-token-via-agent"
          }
      }
    }
    
    vault {
      address = "http://10.0.101.53:8200"
    }
    

    The vault block points to the Vault server address. This should match to the private IP address of your Vault server host.

    The top level auto_auth block has two configuration entries: method and sinks. In this example, the Auto-Auth is configured to use the aws auth method enabled at the auth/aws path on the Vault server. The Vault Agent will use the dev-role-iam role to authenticate.

    The sink block specifies the location on disk where to write tokens. Vault Agent Auto-Auth sink can be configured multiple times if you want Vault Agent to place the token into multiple locations. In this example, the sink is set to /home/ubuntu/vault-token-via-agent.

    The exit_after_auth parameter is set to true; therefore, the agent will exit with code 0 after a single successful authentication. The default is false and the agent continues to run and automatically renew the client token for you.

  3. Execute the following command to get help:

    $ vault agent -h
    
  4. Now, you are ready to run the Vault Agent. Execute the following command:

    $ vault agent -config=/home/ubuntu/vault-agent.hcl -log-level=debug
    ==> Vault server started! Log data will stream in below:
    
    ==> Vault agent configuration:
    
                        Cgo: disabled
                  Log Level: debug
                    Version: Vault v1.3.0
    
    2019-12-05T06:09:40.760Z [INFO]  sink.file: creating file sink
    2019-12-05T06:09:40.760Z [INFO]  sink.file: file sink configured: path=/home/ubuntu/vault-token-via-agent mode=-rw-r-----
    2019-12-05T06:09:40.762Z [INFO]  auth.handler: starting auth handler
    2019-12-05T06:09:40.762Z [INFO]  auth.handler: authenticating
    2019-12-05T06:09:40.762Z [INFO]  template.server: starting template server
    2019-12-05T06:09:40.762Z [INFO]  template.server: no templates found
    2019-12-05T06:09:40.762Z [INFO]  template.server: template server stopped
    2019-12-05T06:09:40.762Z [INFO]  sink.server: starting sink server
    2019-12-05T06:09:41.050Z [INFO]  auth.handler: authentication successful, sending token to sinks
    2019-12-05T06:09:41.050Z [INFO]  auth.handler: starting renewal process
    2019-12-05T06:09:41.051Z [INFO]  sink.file: token written: path=/home/ubuntu/vault-token-via-agent
    2019-12-05T06:09:41.051Z [INFO]  sink.server: sink server stopped
    2019-12-05T06:09:41.051Z [INFO]  sinks finished, exiting
    
  5. Returned token is written in the /home/ubuntu/vault-token-via-agent file.

    $ more vault-token-via-agent
    s.6Wqj0YNiRngCCf6Cic3dA5Ul
    
  6. Let's try an API call using the token that Vault Agent pulled for us to test:

    $ curl --header "X-Vault-Token: $(cat /home/ubuntu/vault-token-via-agent)" \
          $VAULT_ADDR/v1/secret/myapp/config | jq
    {
      ...
      "data": {
        "password": "suP3rsec(et!",
        "ttl": "30s",
        "username": "appuser"
      },
      ...
    }
    

Step 4: Response Wrap the token

It may not be ideal to write the token as a plaintext depending on the firewall around the client instance. Vault Agent supports response-wrapping of the token to provide an additional layer of protection for the token. Tokens can be wrapped by either the auth method or by the sink configuration, with each approach solving for different challenges as described in Response-Wrapping Tokens.

  1. In the client instance, explorer the /home/ubuntu/vault-agent-wrapped.hcl file which supports response-wrapping of the token.

    $ cat /home/ubuntu/vault-agent-wrapped.hcl
    
    exit_after_auth = true
    pid_file = "./pidfile"
    
    auto_auth {
        method "aws" {
            mount_path = "auth/aws"
            config = {
                type = "iam"
                role = "dev-role-iam"
            }
        }
    
        sink "file" {
            wrap_ttl = "5m"
            config = {
                path = "/home/ubuntu/vault-token-via-agent"
            }
        }
    }
    
    vault {
      address = "http://10.0.101.53:8200"
    }
    
  2. From the client, run the Vault Agent again and inspect the output:

    $ vault agent -config=/home/ubuntu/vault-agent-wrapped.hcl -log-level=debug
    ==> Vault server started! Log data will stream in below:
    
    ==> Vault agent configuration:
    
                        Cgo: disabled
                  Log Level: debug
                    Version: Vault v1.3.0
    
    2019-12-05T06:25:59.096Z [INFO]  sink.file: creating file sink
    2019-12-05T06:25:59.096Z [INFO]  sink.file: file sink configured: path=/home/ubuntu/vault-token-via-agent mode=-rw-r-----
    2019-12-05T06:25:59.097Z [INFO]  auth.handler: starting auth handler
    2019-12-05T06:25:59.097Z [INFO]  auth.handler: authenticating
    2019-12-05T06:25:59.097Z [INFO]  template.server: starting template server
    2019-12-05T06:25:59.097Z [INFO]  template.server: no templates found
    2019-12-05T06:25:59.097Z [INFO]  template.server: template server stopped
    2019-12-05T06:25:59.097Z [INFO]  sink.server: starting sink server
    2019-12-05T06:25:59.421Z [INFO]  auth.handler: authentication successful, sending token to sinks
    2019-12-05T06:25:59.421Z [INFO]  auth.handler: starting renewal process
    2019-12-05T06:25:59.427Z [INFO]  auth.handler: renewed auth token
    2019-12-05T06:25:59.436Z [INFO]  sink.file: token written: path=/home/ubuntu/vault-token-via-agent
    2019-12-05T06:25:59.436Z [INFO]  sink.server: sink server stopped
    2019-12-05T06:25:59.436Z [INFO]  sinks finished, exiting
    
    # Examine the contents of sink file
    $ cat /home/ubuntu/vault-token-via-agent | jq
    {
      "token": "s.7h0DPYB4GzVlqxP3JbhDzDcV",
      "accessor": "1k1j6ieCnXyX9UTDZQ9M7ST0",
      "ttl": 300,
      "creation_time": "2018-11-20T00:58:15.270524147Z",
      "creation_path": "sys/wrapping/wrap",
      "wrapped_accessor": ""
    }
    

    Instead of a token value, you now have a JSON object containing a wrapping token as well as some additional metadata. In order to get to the true token, you need to first perform an unwrap operation.

  3. Unwrap the response-wrapped token and save it to a VAULT_TOKEN env var that other applications can use:

    # Execute 'vault unwrap' command and set the resulting token as VAULT_TOKEN
    $ export VAULT_TOKEN=$(vault unwrap -field=token $(jq -r '.token' /home/ubuntu/vault-token-via-agent))
    
    # See the unwrapped token value
    $ echo $VAULT_TOKEN
    s.3VaVYxv0rNgb5JLZBQqS8BXX
    
    # Test to make sure that the token has the read permission on 'secret/myapp/config'
    $ curl --header "X-Vault-Token: $VAULT_TOKEN" $VAULT_ADDR/v1/secret/myapp/config | jq
    

    Notice that the value saved to the VAULT_TOKEN is not the same as the token value in the /home/ubuntu/vault-token-via-agent file. The value in VAULT_TOKEN is the unwrapped token retrieved by Vault Agent.

  4. If you try to unwrap that same value again or wait longer than 5 minutes (wrap_ttl), it will throw an error:

    $ export VAULT_TOKEN=$(vault unwrap -field=token $(jq -r '.token' /home/ubuntu/vault-token-via-agent))
    
    Error unwrapping: Error making API request.
    
    URL: PUT http://active.vault-va-demo.service.dc1.consul:8200/v1/sys/wrapping/unwrap
    Code: 400. Errors:
    
    * wrapping token is not valid or does not exist
    

    A response-wrapped token can only be unwrapped once. Additional attempts to unwrap an already-unwrapped token will result in triggering an error.

Step 5: Clean Up

Once completed, execute the following commands to clean up:

$ terraform destroy -force

$ rm -rf .terraform terraform.tfstate*

Additional Discussion

In the above examples, you manually ran Vault Agent in order to demonstrate how it works. How you actually integrate Vault Agent into your application deployment workflow will vary with a number of factors. Some questions to ask to help determine appropriate usage:

  • What is the lifecycle of my application? Is it more ephemeral or long-lived?
  • What are the lifecycles of my authentication tokens? Are they long-lived and simply need to be renewed over and over to demonstrate liveliness of a service or do you want to enforce periodic re-authentications?
  • Do I have a number of applications running on my host that each needs their own token? Can I use a native authentication capability (e.g. AWS IAM, K8s, Azure MSI, Google Cloud IAM, etc.)?

The answers to these questions will help you determine if Vault Agent should run as a daemon or as a prerequisite of a service configuration. Take for example the following Systemd service definition for running Nomad:

[Unit]
Description=Nomad Agent
Requires=consul-online.target
After=consul-online.target

[Service]
KillMode=process
KillSignal=SIGINT
Environment=VAULT_ADDR=http://active.vault.service.consul:8200
Environment=VAULT_SKIP_VERIFY=true
ExecStartPre=/usr/local/bin/vault agent -config /etc/vault-agent.d/vault-agent.hcl
ExecStart=/usr/bin/nomad-vault.sh
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=2
StartLimitBurst=3
StartLimitIntervalSec=10
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

Notice the ExecStartPre directive that runs Vault Agent before the desired service starts. The service startup script expects a Vault token to be set as is demonstrated in the /usr/bin/nomad-vault.sh startup script:

#!/usr/bin/env bash

if [ -f /mnt/ramdisk/token ]; then
  exec env VAULT_TOKEN=$(vault unwrap -field=token $(jq -r '.token' /mnt/ramdisk/token)) \
    /usr/local/bin/nomad agent \
      -config=/etc/nomad.d \
      -vault-tls-skip-verify=true
else
  echo "Nomad service failed due to missing Vault token"
  exit 1
fi

Many applications that expect Vault tokens typically look for a VAULT_TOKEN env var. Here, you're using Vault Agent to obtain a token and write it out to a Ramdisk and as part of the Nomad startup script, you read the response-wrapped token from the Ramdisk and save it to our VAULT_TOKEN env var before actually starting Nomad.

Furthermore, since Vault Agent Auto-Auth only addresses the challenges of obtaining and managing authentication tokens (secret zero), you might want to use helper tools such as Consul Template and Envconsul to obtain secrets stored in Vault (e.g. DB credentials, PKI certificates, AWS access keys, etc.).

Help and Reference

To learn more about the response wrapping feature, refer the following: