ExCredstash

View Source

Hex.pm CI Documentation

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

Features

  • Full compatibility with Python credstash data format
  • Both library and CLI interfaces
  • AES-256-CTR encryption with HMAC verification
  • Automatic version management
  • KMS encryption context support
  • Multiple digest algorithms (SHA, SHA224, SHA256, SHA384, SHA512, MD5)

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_REGION or AWS_DEFAULT_REGION - AWS region
  • CREDSTASH_TABLE - DynamoDB table name (default: "credential-store")
  • CREDSTASH_KEY_ID - KMS key ID or alias (default: "alias/credstash")

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