ExCredstash

Hex.pmCIDocumentation

Elixir implementation of credstash - a utility for managing credentials using AWS KMS and DynamoDB.

Features

Installation

As a Library

Add ex_credstash to your list of dependencies in mix.exs:

def deps do
[
{:ex_credstash, "~> 0.1.0"}
]
end

Then run:

mix deps.get

As a CLI Tool

Install from Hex.pm

mix escript.install hex ex_credstash

Make sure ~/.mix/escripts is in your PATH:

export PATH="$HOME/.mix/escripts:$PATH"

Build from Source

git clone https://github.com/relaypro-open/ex_credstash.git
cd ex_credstash
mix deps.get
mix escript.build

This creates a credstash binary in the project root. You can move it to a directory in your PATH:

sudo mv credstash /usr/local/bin/

Usage

Library Usage

# Set up the DynamoDB table
ExCredstash.setup(region: "us-east-1")
# Store a secret
{:ok, version} = ExCredstash.put("db_password", "super_secret", region: "us-east-1")
# Store with specific version
{:ok, "0000000000000000005"} = ExCredstash.put("api_key", "key123",
region: "us-east-1",
version: 5
)
# Store with encryption context
ExCredstash.put("secret", "value",
region: "us-east-1",
context: %{"environment" => "production"}
)
# Store multiple secrets at once
{:ok, versions} = ExCredstash.put_all(%{
"key1" => "val1",
"key2" => "val2"
}, region: "us-east-1")
# Retrieve a secret (latest version)
{:ok, "super_secret"} = ExCredstash.get("db_password", region: "us-east-1")
# Retrieve a specific version
{:ok, "old_secret"} = ExCredstash.get("db_password", region: "us-east-1", version: 1)
# Retrieve all secrets (decrypted)
{:ok, %{"db_password" => "secret1", "api_key" => "secret2"}} =
ExCredstash.get_all(region: "us-east-1")
# List all credentials (metadata only, not decrypted)
{:ok, [%{name: "db_password", version: "0000000000000000001", comment: nil}]} =
ExCredstash.list(region: "us-east-1")
# List unique credential names
{:ok, ["api_key", "db_password"]} = ExCredstash.keys(region: "us-east-1")
# Delete all versions of a secret
{:ok, 3} = ExCredstash.delete("old_secret", region: "us-east-1")

CLI Usage

# Set up the DynamoDB table
credstash setup -r us-east-1
# Store a secret
credstash put db_password "super_secret" -r us-east-1
# Store with auto-versioning (default behavior)
credstash put api_key "key123" -a -r us-east-1
# Store with explicit version
credstash put api_key "key456" -v 2 -r us-east-1
# Store with custom KMS key
credstash put secret "value" -k alias/my-key -r us-east-1
# Store with encryption context
credstash put secret "value" environment=production app=myapp -r us-east-1
# Store with comment
credstash put api_key "key123" -c "API key for external service" -r us-east-1
# Read secret value from stdin
echo "secret" | credstash put my_secret -r us-east-1
# Read secret value from file
credstash put certificate @/path/to/cert.pem -r us-east-1
# Retrieve a secret
credstash get db_password -r us-east-1
# Retrieve specific version
credstash get db_password -v 1 -r us-east-1
# Retrieve without trailing newline
credstash get db_password -n -r us-east-1
# Retrieve with encryption context
credstash get secret environment=production -r us-east-1
# Retrieve all secrets as JSON
credstash getall -f json -r us-east-1
# Retrieve all secrets as dotenv format
credstash getall -f dotenv -r us-east-1
# List all credentials
credstash list -r us-east-1
# List unique credential names
credstash keys -r us-east-1
# Delete a secret (all versions)
credstash delete old_secret -r us-east-1
# Use a custom table
credstash -t my-secrets list -r us-east-1

Configuration

Application Config

# config/config.exs
config :ex_credstash,
region: "us-east-1",
table: "credential-store",
kms_key: "alias/credstash"

Environment Variables

AWS Credentials

ExCredstash uses the standard AWS credential chain:

  1. Environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
  2. Shared credentials file (~/.aws/credentials)
  3. IAM role (for EC2 instances, ECS tasks, Lambda functions)

AWS Setup

KMS Key

Create a KMS key for credstash:

aws kms create-key --description "Credstash key"
aws kms create-alias --alias-name alias/credstash --target-key-id <key-id>

Or use an existing key by setting the kms_key configuration.

IAM Permissions

The following IAM permissions are required:

For reading secrets:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["kms:Decrypt"],
"Resource": "arn:aws:kms:REGION:ACCOUNT:key/KEY-ID"
},
{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:Query",
"dynamodb:Scan"
],
"Resource": "arn:aws:dynamodb:REGION:ACCOUNT:table/credential-store"
}
]
}

For writing secrets:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["kms:GenerateDataKey"],
"Resource": "arn:aws:kms:REGION:ACCOUNT:key/KEY-ID"
},
{
"Effect": "Allow",
"Action": [
"dynamodb:PutItem",
"dynamodb:Query"
],
"Resource": "arn:aws:dynamodb:REGION:ACCOUNT:table/credential-store"
}
]
}

For setup (creating table):

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:CreateTable",
"dynamodb:DescribeTable",
"dynamodb:TagResource"
],
"Resource": "arn:aws:dynamodb:REGION:ACCOUNT:table/credential-store"
}
]
}

For deleting secrets:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:Query",
"dynamodb:DeleteItem"
],
"Resource": "arn:aws:dynamodb:REGION:ACCOUNT:table/credential-store"
}
]
}

Running Tests

Unit Tests

Unit tests run without AWS credentials and mock AWS SDK responses:

mix test

Integration Tests

Integration tests verify real AWS operations and require:

  1. AWS Credentials: Set up via environment variables or ~/.aws/credentials:

    export AWS_ACCESS_KEY_ID="your-access-key"
    export AWS_SECRET_ACCESS_KEY="your-secret-key"
    export AWS_REGION="us-east-1"
  2. KMS Key: A KMS key with alias alias/credstash (or configure a different key)

  3. Run integration tests:

    mix test --include integration

Development

# Run tests
mix test
# Run formatter
mix format
# Check formatting
mix format --check-formatted
# Run type checker
mix dialyzer
# Run linter
mix credo
# Build escript
mix escript.build
# Compile with warnings as errors
mix compile --warnings-as-errors

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/my-feature)
  3. Run tests and ensure they pass (mix test)
  4. Run formatter (mix format)
  5. Commit your changes (git commit -am 'Add new feature')
  6. Push to the branch (git push origin feature/my-feature)
  7. Create a Pull Request

Acknowledgments