Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
docker-compose.override.yml
22 changes: 22 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
FROM golang:latest as go

RUN mkdir /build

COPY aws-env.go /build

WORKDIR /build

RUN go get -u github.com/aws/aws-sdk-go

RUN CGO_ENABLED=0 GOOS=linux go build -v -o awsenv .


FROM scratch

COPY --from=go /build/awsenv /awsenv

COPY --from=go /etc/ssl/certs /etc/ssl/certs

VOLUME /ssm

ENTRYPOINT ["/awsenv"]
2 changes: 0 additions & 2 deletions Makefile

This file was deleted.

104 changes: 50 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,83 +1,79 @@
aws-env - Secure way to handle environment variables in Docker
------------------------

aws-env is a small utility that tries to solve problem of passing environment variables to applications in a secure way, especially in Docker containers.
Forked from [Droplr/aws-env](https://github.com/Droplr/aws-env)

It uses [AWS Parameter Store](https://aws.amazon.com/ec2/systems-manager/parameter-store/) to populate environment variables while starting application inside the container.
Published as a [docker image](https://hub.docker.com/r/base2/awsenv/)

## Usage
## How it works

1. Add parameters to [Parameter Store](https://console.aws.amazon.com/ec2/v2/home#Parameters:) using hierarchy in names:
```
$ aws ssm put-parameter --name /prod/my-app/DB_USERNAME --value "Username" --type SecureString --key-id "alias/aws/ssm" --region us-west-2
$ aws ssm put-parameter --name /prod/my-app/DB_PASSWORD --value "SecretPassword" --type SecureString --key-id "alias/aws/ssm" --region us-west-2
```
Searches for SSM Parameters in your AWS account based on the variables provided and places them in a .env file

2. Install aws-env (choose proper [prebuilt binary](https://github.com/Droplr/aws-env/tree/master/bin))
```
$ wget https://github.com/Droplr/aws-env/raw/master/bin/aws-env-linux-amd64 -O aws-env
```bash
$ cat /ssm/.env
export DB_HOST=$'mysql'
export DB_USERNAME=$'Username'
export DB_PASSWORD=$'SecretPassword'
```

3. Start your application with aws-env
* `AWS_ENV_PATH` - path of parameters. If it won't be provided, aws-env will exit immediately. That way, you can run your Dockerfiles locally.
* `AWS_REGION` and AWS Credentials - [configuring credentials](https://github.com/aws/aws-sdk-go#configuring-credentials)
```
$ eval $(AWS_ENV_PATH=/prod/my-app/ AWS_REGION=us-west-2 ./aws-env) && node -e "console.log(process.env)"
## Parameter Hierarchy

Provide the hierachy structure using the `PATH` environment variable
```yml
PATH: /my-app/production/prod1
```

This path can be completely dynamic and the hierarchy can have a maximum depth of five levels. You can define a parameter at any level of the hierarchy. Both of the following examples are valid:
`/Level-1/Level-2/Level-3/Level-4/Level-5/parameter-name`
`/Level-1/parameter-name`

Under the hood, aws-env will export environment parameters fetched from AWS Parameter Store:
Higher levels of the hierarchy will override the lower levels if the same parameter name is found.<br />
*Example:*
`/my-app/production/prod1/EMAIL` would override the value of `/my-app/EMAIL` for the prod1 environment<br />
`/my-app/production/API_KEY` would override the value of `/my-app/API_KEY` for the environment type production<br />
`/my-app/develop/test/API_KEY` would override the value of `/my-app/develop/API_KEY` for the test environment

Add parameters to [Parameter Store](https://console.aws.amazon.com/ec2/v2/home#Parameters:) using hierarchy structure:
```
$ export DB_USERNAME=$'Username'
$ export DB_PASSWORD=$'SecretPassword'
$ aws ssm put-parameter --name /my-app/DB_HOST --value "mysql" --type SecureString --key-id "alias/aws/ssm" --region ap-southeast-2
$ aws ssm put-parameter --name /my-app/production/DB_USERNAME --value "Username" --type SecureString --key-id "alias/aws/ssm" --region ap-southeast-2
$ aws ssm put-parameter --name /my-app/production/prod1/DB_PASSWORD --value "SecretPassword" --type SecureString --key-id "alias/aws/ssm" --region ap-southeast-2
```

## Usage

## Example Dockerfile

```
FROM node:alpine
There are 2 ways this can be implemented

RUN apk update && apk upgrade && \
apk add --no-cache openssl ca-certificates
1. Include `base2/awsenv` as a side car container

RUN wget https://github.com/Droplr/aws-env/raw/master/bin/aws-env-linux-amd64 -O /bin/aws-env && \
chmod +x /bin/aws-env
* volume mount the `/ssm` directory
* eval the `/ssm/.env` file to export the environment parameters

CMD eval $(aws-env) && node -e "console.log(process.env)"
```
```yml
awsenv:
image: base2/awsenv
environment:
PATH: /my-app/production/prod1
AWS_REGION: ap-southeast-2

test:
image: my-app
volumes_from:
- awsenv
entrypoint: eval $(cat /ssm/.env)
```
$ docker build -t my-app .

$ docker run -v ~/.aws/:/root/.aws -e AWS_ENV_PATH="/prod/my-app/" -e AWS_REGION=us-west-2 -t my-app
```
2. Build `FROM base2/awsenv as awsenv` and extract the binary

For a local development, you you can still use:
* extract the binary from the `base2/awsenv` image to your `PATH`
* eval the `/ssm/.env` file to export the environment parameters

```
$ docker run -t my-app
```
```Dockerfile
FROM FROM base2/awsenv as awsenv

## Considerations
FROM debian:jessie

* As this script is still in development, its usage **may** change. Lock version to the
specific commit to be sure that your Dockerfiles will work correctly!
Example:
```
$ wget https://github.com/Droplr/aws-env/raw/befe6fa44ea508508e0bcd2c3f4ac9fc7963d542/bin/aws-env-linux-amd64
```
COPY --from=awsenv /awsenv /bin/awsenv

* Many Docker images (e.g. ruby) are using /bin/sh as a default shell. It crashes `$'string'`
notation that enables multi-line variables export. For this reason, to use aws-env, it's
required to switch shell to /bin/bash:
```
CMD ["/bin/bash", "-c", "eval $(aws-env) && rails s Puma"]
ENTRYPOINT awsenv && eval $(cat /ssm/.env)
```

* You should never pass AWS credentials inside the containers, instead use IAM Roles for that -
[Managing Secrets for Amazon ECS Applications Using Parameter Store and IAM Roles for Tasks](
https://aws.amazon.com/blogs/compute/managing-secrets-for-amazon-ecs-applications-using-parameter-store-and-iam-roles-for-tasks/)

* Always use KMS for parameters encryption - store them as "SecureString"
48 changes: 39 additions & 9 deletions aws-env.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,59 @@
package main

import (
"bytes"
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ssm"
"io/ioutil"
"log"
"os"
"strings"
)

func main() {
if os.Getenv("AWS_ENV_PATH") == "" {
log.Println("aws-env running locally, without AWS_ENV_PATH")
return

keys := strings.Split(os.Getenv("PATH"), "/")
params := make(map[string]string)

// Remove the empty string created by the split
if keys[0] == "" {
keys = keys[1:]
}

path := ""
// Loop through the sub paths and retrieve parameters
for i := range keys {
path = path + "/" + keys[i]
log.Printf("Retriving parameters in path %s", path)
ExportVariables(path, "", params)
}

var buffer bytes.Buffer
for key, value := range params {
buffer.WriteString(fmt.Sprintf("export %s=$'%s'\n", key, value))
}

ExportVariables(os.Getenv("AWS_ENV_PATH"), "")
dir := "/ssm"
// Create /ssm directory if it doesn't exist
if _, err := os.Stat(dir); os.IsNotExist(err) {
os.Mkdir(dir, 0755)
}

// Write evironment variables to .env file
err := ioutil.WriteFile("/ssm/.env", buffer.Bytes(), 0744)
if err != nil {
log.Panic(err)
}
}

func CreateClient() *ssm.SSM {
session := session.Must(session.NewSession())
return ssm.New(session)
}

func ExportVariables(path string, nextToken string) {
func ExportVariables(path string, nextToken string, params map[string]string ) {
client := CreateClient()

input := &ssm.GetParametersByPathInput{
Expand All @@ -43,20 +72,21 @@ func ExportVariables(path string, nextToken string) {
}

for _, element := range output.Parameters {
PrintExportParameter(path, element)
env, value := PrintExportParameter(path, element)
params[env] = value
}

if output.NextToken != nil {
ExportVariables(path, *output.NextToken)
ExportVariables(path, *output.NextToken, params)
}
}

func PrintExportParameter(path string, parameter *ssm.Parameter) {
func PrintExportParameter(path string, parameter *ssm.Parameter) (string, string) {
name := *parameter.Name
value := *parameter.Value

env := strings.Trim(name[len(path):], "/")
value = strings.Replace(value, "\n", "\\n", -1)

fmt.Printf("export %s=$'%s'\n", env, value)
return env, value
}
Binary file removed bin/aws-env-darwin-386
Binary file not shown.
Binary file removed bin/aws-env-darwin-amd64
Binary file not shown.
Binary file removed bin/aws-env-linux-386
Binary file not shown.
Binary file removed bin/aws-env-linux-amd64
Binary file not shown.
Binary file removed bin/aws-env-windows-386
Binary file not shown.
Binary file removed bin/aws-env-windows-amd64
Binary file not shown.
12 changes: 0 additions & 12 deletions build.sh

This file was deleted.

9 changes: 9 additions & 0 deletions docker-compose.override.yml.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
version: '2'


services:

awsenv:
environment:
AWS_ACCESS_KEY_ID: ""
AWS_SECRET_ACCESS_KEY: ""
19 changes: 19 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
version: '2'

services:

awsenv:
build: ./
image: base2/awsenv
# tty: true
# command: ash
environment:
PATH: /catch/development/dev
AWS_REGION: ap-southeast-2

test:
image: debian:jessie
tty: true
volumes_from:
- awsenv
# command: bash -c "eval $$(cat /ssm/.env) && printenv"