Skip to content

Commit 2e5a7b0

Browse files
committed
Add readme contents and quick-start demo
1 parent b32572c commit 2e5a7b0

22 files changed

+1136
-3
lines changed

README.md

+120-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,120 @@
1-
# vault-lambda-extension
1+
# vault-lambda-extension
2+
3+
This repository contains the source code for Hashicorp's Vault AWS Lambda extension.
4+
The extension utilises the AWS Lambda Extensions API to read secrets from your
5+
Vault deployment and write the result to disk before the Lambda function itself
6+
starts to execute. To use it, include the following ARN as a layer in your
7+
Lambda function:
8+
9+
```text
10+
TBD
11+
```
12+
13+
The extension authenticates with Vault using [AWS IAM auth][vault-aws-iam-auth],
14+
and writes the result as JSON to disk. It also writes a vault token to
15+
`/tmp/vault/token`. All configuration is supplied via environment variables.
16+
17+
## Getting Started
18+
19+
### Quickstart
20+
21+
The [quick-start](./quick-start) directory has a fully worked example, for which
22+
you will need an AWS account and some comand line tools. Follow the readme in
23+
that directory if you'd like to try out the extension from scratch. **Please
24+
note it will create real infrastructure with an associated cost as per AWS'
25+
pricing.**
26+
27+
### Adding the extension to your existing Lambda and Vault infrastructure
28+
29+
Requirements:
30+
31+
* ARN of the role your Lambda runs as
32+
* An instance of Vault accessible from AWS Lambda
33+
* An authenticated `vault` client
34+
* A secret in Vault that you want your Lambda to access, and a policy giving read access to it
35+
36+
First, set up AWS IAM auth on Vault, and attach a policy to your ARN:
37+
38+
```bash
39+
vault auth enable aws
40+
vault write -force auth/aws/config/client
41+
vault write auth/aws/role/vault-lambda-role \
42+
auth_type=iam \
43+
bound_iam_principal_arn="${YOUR_ARN}" \
44+
policies="${YOUR_POLICY}" \
45+
ttl=1h
46+
```
47+
48+
Add the extension to your Lambda layers using the console or [cli][lambda-add-layer-cli]:
49+
50+
```text
51+
TBD
52+
```
53+
54+
Configure the extension using [Lambda environment variables][lambda-env-vars]:
55+
56+
```bash
57+
VAULT_ADDR=http://vault.example.com:8200 # Your Vault address
58+
VAULT_AUTH_PROVIDER=aws # The AWS IAM auth mount point, i.e. the path segment after auth/ from above
59+
VAULT_AUTH_ROLE=vault-lambda-role # The Vault role to authenticate as. Must be configured for the ARN of your Lambda's role
60+
VAULT_SECRET_PATH=secret/lambda-app/token # The path to a secret in Vault. Can be static or dynamic.
61+
# Unless VAULT_SECRET_FILE is specified, JSON response will be written to /tmp/vault/secret.json
62+
```
63+
64+
If everything is correctly set up, your Lambda function can then read secret
65+
material from `/tmp/vault/secret.json`. The exact contents of the JSON object
66+
will depend on the secret read, but its schema is the [Secret struct][vault-api-secret-struct]
67+
from the Vault API module.
68+
69+
## Configuration
70+
71+
The extension is configured via [Lambda environment variables][lambda-env-vars].
72+
Most of the [Vault CLI client's environment variables][vault-env-vars] are available,
73+
as well as some additional variables to configure auth, which secret(s) to read and
74+
where to write secrets. At least one valid secret to read must be specified.
75+
76+
Environment variable | Description | Required | Example value
77+
------------------------|-------------|----------|--------------
78+
`VAULT_ADDR` | Vault address to connect to | Yes | `https://x.x.x.x:8200`
79+
`VAULT_AUTH_PROVIDER` | Name of the configured AWS IAM auth route on Vault | Yes | `aws`
80+
`VAULT_AUTH_ROLE` | Vault role to authenticate as | Yes | `lambda-app`
81+
`VAULT_SECRET_PATH` | Secret path to read, written to `/tmp/vault/secret.json` unless `VAULT_SECRET_FILE` is specified | No | `database/creds/lambda-app`
82+
`VAULT_SECRET_FILE` | Path to write the JSON response for `VAULT_SECRET_PATH` | No | `/tmp/db.json`
83+
`VAULT_SECRET_PATH_FOO` | Additional secret path to read, where FOO can be any name, as long as a matching `VAULT_SECRET_FILE_FOO` is specified | No | `secret/lambda-app/token`
84+
`VAULT_SECRET_FILE_FOO` | Must exist for any correspondingly named `VAULT_SECRET_PATH_FOO`. Name has no further effect beyond matching to the correct path variable | No | `/tmp/token`
85+
86+
The remaining environment variables are not required, and function exactly as
87+
described in the [Vault Commands (CLI)][vault-env-vars] documentation. However,
88+
note that `VAULT_CLIENT_TIMEOUT` cannot extend the timeout beyond the 10s
89+
timeout imposed by the Extensions API.
90+
91+
Environment variable | Description | Required | Example value
92+
------------------------|-------------|----------|--------------
93+
`VAULT_CACERT` | Path to a PEM-encoded CA certificate _file_ on the local disk | No | `/tmp/ca.crt`
94+
`VAULT_CAPATH` | Path to a _directory_ of PEM-encoded CA certificate files on the local disk | No | `/tmp/certs`
95+
`VAULT_CLIENT_CERT` | Path to a PEM-encoded client certificate on the local disk | No | `/tmp/client.crt`
96+
`VAULT_CLIENT_KEY` | Path to an unencrypted, PEM-encoded private key on disk which corresponds to the matching client certificate | No | `/tmp/client.key`
97+
`VAULT_CLIENT_TIMEOUT` | Timeout for Vault requests. Default value is 60s. **Any value over 10s will exceed the Extensions API timeout and therefore have no effect** | No | `5s`
98+
`VAULT_MAX_RETRIES` | Maximum number of retries on `5xx` error codes. Defaults to 2 | No | `2`
99+
`VAULT_SKIP_VERIFY` | Do not verify Vault's presented certificate before communicating with it. Setting this variable is not recommended and voids Vault's [security model][vault-security-model] | No | `true`
100+
`VAULT_TLS_SERVER_NAME` | Name to use as the SNI host when connecting via TLS | No | `vault.example.com`
101+
`VAULT_RATE_LIMIT` | Only applies to a single invocation of the extension. See [Vault Commands (CLI)][vault-env-vars] documentation for details | No | `10`
102+
`VAULT_NAMESPACE` | The namespace to use for the command | No | `education`
103+
`VAULT_SRV_LOOKUP` | The Vault client will lookup DNS SRV records for the host. See [Vault Commands (CLI)][vault-env-vars] documentation for details | No | `true`
104+
`VAULT_MFA` | MFA credentials. See [Vault Commands (CLI)][vault-env-vars] documentation for details | No | `true`
105+
106+
## Limitations
107+
108+
For this early release, the extension does not support automatic secret renewal.
109+
This means once a secret is written to disk, it will not be refreshed once it
110+
expires. This may cause problems if you use [provisioned concurrency][lambda-provisioned-concurrency]
111+
or if your Lambda is invoked often enough that execution contexts live beyond
112+
the lifetime of the secret.
113+
114+
[vault-aws-iam-auth]: https://www.vaultproject.io/docs/auth/aws
115+
[vault-env-vars]: https://www.vaultproject.io/docs/commands#environment-variables
116+
[vault-api-secret-struct]: https://github.com/hashicorp/vault/blob/api/v1.0.4/api/secret.go#L15
117+
[vault-security-model]: https://www.vaultproject.io/docs/internals/security
118+
[lambda-env-vars]: https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html
119+
[lambda-add-layer-cli]: https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html#configuration-layers-using
120+
[lambda-provisioned-concurrency]: https://docs.aws.amazon.com/lambda/latest/dg/configuration-concurrency.html#configuration-concurrency-provisioned

main.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,14 @@ func main() {
8383
log.Fatalf("%s: nil client returned: %s", printPrefix, err)
8484
}
8585

86-
err = ioutil.WriteFile("/tmp/vault_token", []byte(client.Token()), 0644)
86+
if _, err = os.Stat(defaultSecretDirectory); os.IsNotExist(err) {
87+
err = os.MkdirAll(defaultSecretDirectory, 0755)
88+
if err != nil {
89+
log.Fatalf("Failed to create directory /tmp/vault: %s", err)
90+
}
91+
}
92+
93+
err = ioutil.WriteFile(path.Join(defaultSecretDirectory, "token"), []byte(client.Token()), 0644)
8794
if err != nil {
8895
log.Fatal(err)
8996
}
@@ -102,7 +109,7 @@ func main() {
102109
}
103110
dir := path.Dir(s.filePath)
104111
if _, err = os.Stat(dir); os.IsNotExist(err) {
105-
err = os.MkdirAll(dir, 0644)
112+
err = os.MkdirAll(dir, 0755)
106113
if err != nil {
107114
log.Fatalf("%s: Failed to create directory %q for secret %s: %s", printPrefix, dir, s.name, err)
108115
}

quick-start/README.md

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Vault Lambda extension Quick Start
2+
3+
Creates the infrastructure required for running a demo of the Vault Lambda extension:
4+
5+
* An EC2 instance with a vault server running on it with auto-unseal from KMS
6+
* A new SSH key pair used to SSH into the instance
7+
* IAM role for the Lambda to run as, which will be able to auth against the Vault instance using AWS IAM auth
8+
* Configures Vault
9+
* A Lambda function which requests database credentials from the extension and then uses them to list users on the database
10+
11+
**NB: This demo will create real infrastructure in AWS with an associated
12+
cost. Make sure you tear down the infrastructure once you are finished with
13+
the demo.**
14+
15+
**NB: This is not a production-ready deployment, and is for demonstration
16+
purposes only.**
17+
18+
## Prerequisites
19+
20+
* `bash`, `zip`
21+
* Golang
22+
* Terraform
23+
* AWS account with access key ID and secret access key
24+
* AWS CLI configured with the same account
25+
26+
## Usage
27+
28+
```bash
29+
./build.sh
30+
cd terraform
31+
32+
export AWS_ACCESS_KEY_ID = "<YOUR_AWS_ACCESS_KEY_ID>"
33+
export AWS_SECRET_ACCESS_KEY = "<YOUR_AWS_SECRET_ACCESS_KEY>"
34+
terraform init
35+
terraform apply
36+
37+
# Then run the `aws lambda invoke` command from the terraform output
38+
39+
# Remember to clean up the billed resources once you're finished
40+
terraform destroy
41+
```
42+
43+
## Credit
44+
45+
Adapted from AWS KMS guides in the [vault-guides](https://github.com/hashicorp/vault-guides) repo.
46+
Specifically, mostly from [this guide](https://learn.hashicorp.com/tutorials/vault/agent-aws).

quick-start/build.sh

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/bin/sh
2+
3+
set -euo pipefail
4+
5+
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6+
pushd "${DIR}/demo-function"
7+
8+
echo "Removing old builds..."
9+
rm -f bin/main 2> /dev/null
10+
rm -f demo-function.zip 2> /dev/null
11+
12+
echo "Build function..."
13+
GOOS=linux GOARCH=amd64 go build -ldflags '-s -w' -a -o bin/main main.go
14+
15+
echo
16+
17+
echo "Making new zip..."
18+
zip -j -D -r demo-function.zip bin/bootstrap bin/main
19+
20+
popd # ${DIR}

quick-start/demo-function/.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
main
2+
*.zip
+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
5+
LAMBDA_TASK_ROOT=/var/task
6+
TMPFILE=/tmp/data
7+
8+
# Graceful Shutdown
9+
_term() {
10+
echo "[runtime] Received SIGTERM"
11+
# forward SIGTERM to child procs and exit
12+
kill -TERM "$PID" 2>/dev/null
13+
echo "[runtime] Exiting"
14+
exit 0
15+
}
16+
17+
forward_sigterm_and_wait() {
18+
trap _term SIGTERM
19+
wait "$PID"
20+
trap - SIGTERM
21+
}
22+
23+
# Initialization - load function handler
24+
echo "[runtime] handler in bootstrap: ${_HANDLER}"
25+
# source $LAMBDA_TASK_ROOT/"$(echo $_HANDLER | cut -d. -f1).sh"
26+
#.$LAMBDA_TASK_ROOT/"$(echo $_HANDLER | cut -d. -f1)"
27+
28+
echo "[runtime] Initializing..."
29+
30+
# Processing
31+
while true
32+
do
33+
echo "[runtime] Waiting for invocation..."
34+
35+
HEADERS="$(mktemp)"
36+
37+
# Get an event. The HTTP request will block until one is received
38+
curl -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next" > $TMPFILE &
39+
PID=$!
40+
forward_sigterm_and_wait
41+
42+
EVENT_DATA=$(<$TMPFILE)
43+
44+
echo "[runtime] Received invocation: $EVENT_DATA"
45+
46+
# Extract request ID by scraping response headers received above
47+
REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2)
48+
49+
echo "[runtime] Executing function: $_HANDLER"
50+
51+
# Execute the handler function from the script
52+
RESPONSE=$($(echo ".$LAMBDA_TASK_ROOT/$_HANDLER" | cut -d. -f2) "$EVENT_DATA")
53+
54+
echo "[runtime] Sending invocation response: $RESPONSE"
55+
56+
# Send the response
57+
curl -sS -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response" -d "$RESPONSE" > $TMPFILE
58+
PID=$!
59+
forward_sigterm_and_wait
60+
61+
STATUS_RESP=$(<$TMPFILE)
62+
63+
echo "[runtime] Runtime API response: $STATUS_RESP"
64+
done

quick-start/demo-function/go.mod

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module github.com/hashicorp/vault-ext-proto
2+
3+
go 1.15
4+
5+
require (
6+
github.com/aws/aws-lambda-go v1.19.1
7+
github.com/hashicorp/vault/api v1.0.4
8+
github.com/lib/pq v1.8.0
9+
)

0 commit comments

Comments
 (0)