A Rust CLI for automatic secret rotation across multiple backends and target systems.
| Backend | Config value | Notes |
|---|---|---|
| HashiCorp Vault KV v2 | vault |
Always included |
| AWS Secrets Manager | aws |
Always included |
| Local file | file |
Dev/testing; flat key:value files |
| Azure Key Vault | azure |
Requires --features azure |
| GCP Secret Manager | gcp |
Requires --features gcp |
| OpenShift / Kubernetes | ocp |
Requires --features ocp |
Linux / macOS:
curl -fsSL https://raw.githubusercontent.com/kelleyblackmore/Automatic-Secret-Rotation/main/install.sh | bashWindows (PowerShell):
irm https://raw.githubusercontent.com/kelleyblackmore/Automatic-Secret-Rotation/main/install.ps1 | iexFrom source:
cargo install --git https://github.com/kelleyblackmore/Automatic-Secret-Rotation
# With optional backends/features:
cargo install --git https://github.com/kelleyblackmore/Automatic-Secret-Rotation --features azure,gcp,ocp,mysqlmacOS note: The system tool
/usr/sbin/asr(Apple Software Restore) conflicts with this binary name. The installer defaults tosecret-rotatoron macOS. Override:ASR_BINARY_NAME=asr ./install.sh.
asr init # Create a config file
asr gen-password myapp/database # Generate and store a password
asr flag myapp/database --period 3 # Rotate every 3 months
asr scan # Check which secrets are due
asr auto --dry-run # Preview what would be rotated
asr auto # Rotate all due secretsasr loads config from a TOML file (-c config.toml) or environment variables.
backend = "vault" # vault | aws | file | azure | gcp | ocp
[vault]
address = "http://127.0.0.1:8200"
token = "hvs.your-token"
mount = "secret"
[aws]
region = "us-east-1"
[file]
directory = "~/.asr/secrets"
[azure]
vault_url = "https://my-vault.vault.azure.net"
[gcp]
project_id = "my-gcp-project"
[ocp]
namespace = "my-app"
[rotation]
period_months = 6
secret_length = 32
# Append-only JSONL audit log
[audit]
log_file = "/var/log/asr/audit.jsonl"
stdout = false
# Webhook / Slack notifications
[notification]
webhook_url = "https://hooks.slack.com/services/..."
events = ["rotate", "flag", "scan"]Single target (backward-compatible):
[targets.postgres]
host = "localhost"
database = "mydb"
username = "admin"
password_path = "myapp/db-admin" # secret path for admin creds
ssl_mode = "verify-full" # TLS required + verified; only "disable" is plaintext
[targets.api]
base_url = "https://api.example.com"
endpoint = "/users/{username}/password"
method = "POST"
auth_header = "Bearer token"Multiple targets (new array form):
[[targets]]
type = "postgres"
host = "primary.db.internal"
database = "app"
username = "admin"
password_path = "myapp/db-admin"
[[targets]]
type = "postgres"
host = "replica.db.internal"
database = "app"
username = "admin"
password_path = "myapp/db-admin"
[[targets]]
type = "mysql" # requires --features mysql
host = "mysql.internal"
database = "app"
username = "admin"
password_path = "myapp/mysql-admin"| Variable | Description |
|---|---|
SECRET_BACKEND |
Backend name |
VAULT_ADDR / VAULT_TOKEN / VAULT_MOUNT |
Vault |
AWS_REGION |
AWS |
AZURE_VAULT_URL |
Azure Key Vault |
GCP_PROJECT_ID |
GCP |
OCP_NAMESPACE |
Kubernetes namespace |
ROTATION_PERIOD_MONTHS |
Default rotation period |
SECRET_LENGTH |
Password length (default: 32) |
ASR_AUDIT_LOG |
JSONL audit log path |
ASR_AUDIT_STDOUT |
Write audit events to stdout (1/true) |
ASR_WEBHOOK_URL |
Webhook URL for notifications |
ASR_ALLOW_INSECURE_HTTP |
1 allows plain http:// endpoints off localhost (warns on every use) |
asr init Create a sample config file
asr flag <path> [--period N] Flag a secret for rotation
asr scan [path] List secrets due for rotation
asr rotate <path> Rotate one secret
--update-target Also update configured target systems
--target-username <USER> Username to update in target
asr auto [path] Rotate all due secrets
--dry-run Preview without making changes
--update-env Sync new values to shell config files
--update-target Update target systems
asr read <path> Display a secret (WARNING: shows values)
asr list [path] List secrets at a path
asr gen-password <path> Generate and store a random password
asr update-env <path> Sync a secret to a shell env var
asr update-keychain <path> Store in macOS Keychain (macOS + --features keychain)
name: Weekly Secret Rotation
on:
schedule:
- cron: '0 0 * * 0'
workflow_dispatch:
jobs:
rotate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install asr
run: curl -fsSL https://raw.githubusercontent.com/kelleyblackmore/Automatic-Secret-Rotation/main/install.sh | bash
- name: Rotate secrets
env:
SECRET_BACKEND: vault
VAULT_ADDR: ${{ secrets.VAULT_ADDR }}
VAULT_TOKEN: ${{ secrets.VAULT_TOKEN }}
run: |
asr auto --dry-run
asr autovault secrets enable -version=2 -path=secret kv
cat > rotator-policy.hcl <<EOF
path "secret/data/*" { capabilities = ["create","read","update"] }
path "secret/metadata/*" { capabilities = ["create","read","update","list"] }
EOF
vault policy write rotator rotator-policy.hcl
vault token create -policy=rotator{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue", "secretsmanager:PutSecretValue",
"secretsmanager:CreateSecret", "secretsmanager:UpdateSecret",
"secretsmanager:DescribeSecret", "secretsmanager:ListSecrets",
"secretsmanager:TagResource"
],
"Resource": "*"
}See examples/ocp-rbac.yaml for a complete Role, RoleBinding, and weekly CronJob manifest.
- Store backend credentials in CI secrets or Vault — never commit them
- Endpoints that carry credentials or secrets (Vault, Azure Key Vault, the API, GitLab and GitHub targets, the webhook) must use
https://. Plainhttp://is accepted only forlocalhost/127.0.0.0/8/::1, unlessASR_ALLOW_INSECURE_HTTP=1is set for a trusted private network - The
filebackend keeps every secret inside itsdirectory: absolute paths,..and symlinks that lead outside it are refused (symlinks that stay inside still work), and files are written atomically with mode 0600 rotateandreadcommands display secret values — use only in secure environments and clear terminal history afterwardsautonever displays secret values
cargo build # Debug build
cargo test # Unit tests
# Integration tests (requires Docker Compose)
docker compose -f tests/integration/docker-compose.yml up -d
cargo test --features integration -- --ignored
docker compose -f tests/integration/docker-compose.yml down
cargo build --release # Release buildBump version in Cargo.toml, run cargo update -p secret-rotator so
Cargo.lock records the new version (release builds use --locked), and merge
both to main. The Tag Release workflow tags the commit v<version> and
starts the Release and Integration Tests workflows. Release builds the
binaries, publishes SHA256SUMS and the SBOMs, attests provenance and the SBOM,
and then installs the published release on Linux, macOS and Windows to check it.
Pushing a v* tag by hand still works too.
Apache License — see LICENSE.