Skip to content
Open
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
13 changes: 13 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.git
**/.terraform
**/.bootstrap
local/.env
*.md
docs
adrs
runbooks
examples
aws
gcp
azure
.github
61 changes: 61 additions & 0 deletions .github/workflows/build-ami.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Bake the Vault node AMI once and copy it to every region in AMI_REGIONS.
#
# Repository configuration:
# secret AWS_ROLE_ARN IAM role assumed through GitHub OIDC; Packer's EC2 policy plus ec2:CopyImage
# var AWS_REGION region the bake runs in (default us-east-1)
# var AMI_REGIONS comma-separated regions to copy the AMI to (optional)
name: build-ami

on:
workflow_dispatch:
inputs:
vault_version:
description: Vault CE version to install
default: "2.0.4"
push:
branches: [main]
paths:
- vault-node/**
- cmd/**
- internal/**
- go.mod
- go.sum

permissions:
id-token: write
contents: read

concurrency: build-ami

jobs:
bake:
runs-on: ubuntu-latest
env:
AWS_REGION: ${{ vars.AWS_REGION || 'us-east-1' }}
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version: "1.26.x"

- name: Build vault-utils
run: GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o vault-node/vault-utils ./cmd/vault-utils

- uses: hashicorp/setup-packer@v3

- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}

- name: Bake
working-directory: vault-node
env:
AMI_REGIONS: ${{ vars.AMI_REGIONS }}
VAULT_VERSION: ${{ inputs.vault_version || '2.0.4' }}
run: |
regions=$(printf '%s\n' "$AMI_REGIONS" | jq -Rc 'split(",") | map(select(length > 0))')
packer init .
packer validate -var region="$AWS_REGION" -var vault_version="$VAULT_VERSION" -var "ami_regions=$regions" vault.pkr.hcl
packer build -color=false -var region="$AWS_REGION" -var vault_version="$VAULT_VERSION" -var "ami_regions=$regions" vault.pkr.hcl
38 changes: 38 additions & 0 deletions .github/workflows/test-local.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: test-local

on:
push:
branches: [main, master, develop]
pull_request:

permissions:
contents: read

jobs:
go:
name: go tests (docker)
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version: "1.26.x"

- name: Isolation and credentials
run: go test ./internal/vaultcluster -count=1 -timeout 15m

stack:
name: compose runtime conformance
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version: "1.26.x"

- name: Local compose runtime test
run: go test ./local -count=1 -timeout 15m -v
150 changes: 150 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Static validation.
name: validate

on:
push:
branches: [main, master, develop]
pull_request:

permissions:
contents: read

jobs:
go:
name: go unit tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version: "1.26.x"

- name: Unit tests (no Docker)
run: go test -short ./...

architecture:
name: runtime separation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: HTTP-only trees must not reference any runtime
run: |
set -euo pipefail
fail=0
check() {
local pattern="$1" label="$2"
if grep -rInE "$pattern" \
internal/vaultcluster/policies \
--include='*.sh' --include='*.tpl' --include='*.hcl' \
| grep -vE '^\s*[^:]+:[0-9]+:\s*#' ; then
echo "FAIL: HTTP-only tree references $label"
fail=1
fi
}
check '\bdocker\b' 'docker'
check '\bdocker[- ]compose\b' 'docker compose'
check 'vault-cluster-(vault|postgres)' 'container names'
check '/vault/(file|logs|config)' 'container filesystem paths'
exit "$fail"

security:
name: secret scan and CE guard
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: No bootstrap material is committed
run: |
set -euo pipefail
fail=0
while IFS= read -r f; do
echo "FAIL: bootstrap material committed: $f"; fail=1
done < <(find . -path ./.git -prune -o \
\( -name 'vault-init.json' -o -name '*.snap' \
-o -name '*.token' -o -name 'unseal*' \) -print)

if grep -rInE 'hvs\.[A-Za-z0-9_-]{20,}|"root_token"\s*:\s*"hvs' . \
--exclude-dir=.git --exclude=CHANGELOG.md; then
echo "FAIL: what looks like a real Vault token is committed"; fail=1
fi
exit "$fail"

- name: Fake credentials are labelled as fake
run: |
set -euo pipefail
if grep -rInE '(password|secret|api_key)\s*[:=]\s*"[A-Za-z0-9]{16,}"' \
local internal 2>/dev/null \
| grep -viE 'FAKE-|local-dev-|\{\{|\$\{|not-a-real'; then
echo "FAIL: an unlabelled credential-shaped literal was found"
exit 1
fi

- name: No Vault Enterprise features are used
run: |
set -euo pipefail
fail=0
if grep -rInE 'sys/namespaces|X-Vault-Namespace|sys/replication|sys/control-group|sentinel' \
local/ cmd/ internal/ --include='*.sh' --include='*.hcl' --include='*.tpl' --include='*.go' \
| grep -vE ':\s*#' | grep -v 'VAULT_NAMESPACE'; then
echo "FAIL: Vault Enterprise-only feature referenced (Community Edition only)"
fail=1
fi
if grep -rIn 'hashicorp/vault-enterprise' local cmd internal --exclude-dir=.git; then
echo "FAIL: Enterprise image referenced"; fail=1
fi
exit "$fail"

- name: Images are pinned by digest
run: |
set -euo pipefail
fail=0
if grep -nE '^\s*image:' local/compose.yml | grep -v '@sha256:'; then
echo "FAIL: an image is not digest-pinned"; fail=1
fi
if grep -rn ':latest' local/ --include='*.yml' --include='.env.example'; then
echo "FAIL: ':latest' found - the environment could change without a commit"; fail=1
fi
exit "$fail"

compose:
name: compose config
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Compose file is valid with a one-shot bootstrap
run: |
set -euo pipefail
cd local
cp .env.example .env

docker compose --env-file .env config --quiet

default_svcs="$(docker compose --env-file .env config --services)"
printf 'default services:\n%s\n' "${default_svcs}"
if printf '%s\n' "${default_svcs}" | grep -qx 'unseal'; then
echo "FAIL: long-running unseal sidecar should not be a default service"
exit 1
fi
if ! printf '%s\n' "${default_svcs}" | grep -qx 'bootstrap'; then
echo "FAIL: bootstrap one-shot missing from default compose services"
exit 1
fi
if printf '%s\n' "${default_svcs}" | grep -qx 'postgres'; then
echo "FAIL: the local stack must not run PostgreSQL"
exit 1
fi

docs:
name: documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Required documents exist
run: |
set -euo pipefail
[ -f README.md ] || { echo "missing: README.md"; exit 1; }
[ -f CHANGELOG.md ] || { echo "missing: CHANGELOG.md"; exit 1; }
100 changes: 66 additions & 34 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,44 +1,76 @@
# Local .terraform directories
.terraform/
# Published module surface.
#
# Shipped: runtime code, CI, /README.md, /CHANGELOG.md
# Local-only (kept on disk, not published): ADRs, design docs, runbooks, examples,
# and any other markdown.

# .tfstate files
*.tfstate
*.tfstate.*
*.md
!/README.md
!/CHANGELOG.md
docs/
runbooks/
examples/
adrs/
adapters/

# Crash log files
crash.log
crash.*.log
# Unseal keys, tokens, snapshots. Never commit.
.bootstrap/
**/.bootstrap/
*.unseal
*.unseal-keys
vault-init.json
.vault-init
.vault-token
*.token
*.snap
*.snapshot
audit.log
audit*.log
local/data/
local/audit/
local/backups/

# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
*.tfvars
*.tfvars.json
# Generated policies.
local/.rendered-policies/
*.rendered.hcl

# Ignore override files as they are usually used to override resources locally and so
# are not checked in
# Secrets and local env.
.env
.env.*
!.env.example
*.pem
*.key
!**/testdata/**/*.key
*.crt
!**/testdata/**/*.crt
*.p12
*.pfx
role-id
secret-id

# OpenTofu / Terraform state (none yet).
**/.terraform/*
*.tfstate
*.tfstate.*
*.tfplan
crash.log
override.tf
override.tf.json
*_override.tf
*_override.tf.json

# Ignore transient lock info files created by terraform apply
.terraform.tfstate.lock.info

# Include override files you do wish to add to version control using negated pattern
# !example_override.tf

# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*

# Ignore CLI configuration files
.terraformrc
terraform.rc

# Optional: ignore graph output files generated by `terraform graph`
# *.dot
# Go build artifacts.
/vault-utils
coverage.out

# Optional: ignore plan files saved before destroying Terraform configuration
# Uncomment the line below if you want to ignore planout files.
# planout
# Test/editor noise.
test-results/
*.tap
.coverage
.DS_Store
**/.DS_Store
.idea/
.vscode/
.cursor/
*.swp
*~
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# 0.1.0 (Unreleased)
* Initial release
11 changes: 11 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM golang:1.26-alpine AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY cmd/ cmd/
COPY internal/ internal/
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /vault-utils ./cmd/vault-utils

FROM alpine:3.21
COPY --from=build /vault-utils /usr/local/bin/vault-utils
ENTRYPOINT ["/usr/local/bin/vault-utils"]
Loading
Loading