CLI Secrets Management
Learn how to inject secrets at runtime using ReAPI CLI for maximum security. This guide covers secrets file format, CI/CD integration, and best practices for production environments.
Prerequisites: Familiarity with CLI Usage and Secrets Management concepts.
Why CLI Secrets Injection?
When you package secrets in a deployment, they’re encrypted and stored in ReAPI’s cloud. While secure, some organizations require secrets to never leave their infrastructure.
CLI secrets injection solves this by:
- Zero-knowledge deployment: Deployments contain no secrets at all
- Centralized secret management: Use your existing vault (AWS Secrets Manager, HashiCorp Vault, Azure Key Vault)
- Instant rotation: Update secrets in your vault without redeploying tests
- Compliance: Meet requirements for secrets to stay within your infrastructure
- Audit trail: Leverage your vault’s access logging and rotation policies
How It Works
┌─────────────────────────────────────────────────────┐
│ 1. Store Secrets in Your Vault │
│ (AWS Secrets Manager, Vault, K8s Secrets, etc.) │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 2. CI/CD Pipeline Retrieves Secrets │
│ - Saves to temporary file (secrets.json) │
│ - Sets restrictive permissions (0600) │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 3. ReAPI CLI Runs Tests │
│ - Injects secrets via --secrets flag │
│ - Secrets available as {{$secrets.KEY}} │
│ - No secrets sent to cloud │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 4. Cleanup │
│ - Delete secrets file immediately │
│ - Secrets never persisted to disk long-term │
└─────────────────────────────────────────────────────┘Basic Usage
Command Syntax
# Inject secrets from JSON file
npx @reapi/cli -y test run ./deployment.json --secrets ./secrets.json
# Inject secrets from YAML file
npx @reapi/cli -y test run ./deployment.json --secrets ./secrets.yaml
# Short form
npx @reapi/cli -y test run ./deployment.json -s ./secrets.json
# With remote deployment
npx @reapi/cli -y test run remote://deployment-id \
--api-key "$REAPI_API_KEY" \
--secrets ./secrets.jsonSecrets File Format
JSON format (secrets.json):
{
"API_KEY": "sk_live_abc123xyz789",
"DB_PASSWORD": "super-secret-password",
"AUTH_TOKEN": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"STRIPE_SECRET": "sk_test_51A...",
"ADMIN_PASSWORD": "complex-password-here"
}YAML format (secrets.yaml):
API_KEY: sk_live_abc123xyz789
DB_PASSWORD: super-secret-password
AUTH_TOKEN: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
STRIPE_SECRET: sk_test_51A...
ADMIN_PASSWORD: complex-password-hereUsing Secrets in Tests
In your test flows, access secrets using the {{$secrets.KEY}} syntax:
API Node - Request Headers:
Authorization: Bearer {{$secrets.API_KEY}}
X-API-Secret: { { $secrets.STRIPE_SECRET } }API Node - Request Body:
{
"username": "admin",
"password": "{{$secrets.ADMIN_PASSWORD}}",
"database": "{{$secrets.DB_NAME}}"
}JSONata Expressions:
// In assertions
response.status = 200 and response.key = $secrets.EXPECTED_KEY
// In context operations
{
"connectionString": "postgresql://user:" & $secrets.DB_PASSWORD & "@localhost/db"
}Merge Priority
When secrets are provided from multiple sources, they merge with the following priority (highest to lowest):
┌────────────────────────────────────┐
│ 1. CLI --secrets (Highest) │ ← Overrides everything
└────────────────────────────────────┘
↓
┌────────────────────────────────────┐
│ 2. Packaged Deployment Secrets │ ← From variable groups
└────────────────────────────────────┘
↓
┌────────────────────────────────────┐
│ 3. Mock/Default Values (Lowest) │ ← Test placeholders
└────────────────────────────────────┘Example:
Deployment has:
{
"API_KEY": "dev-key-123",
"DB_HOST": "dev.example.com"
}CLI provides (--secrets secrets.json):
{
"API_KEY": "prod-key-789",
"DB_PASSWORD": "secret-pass"
}Final merged secrets:
{
"API_KEY": "prod-key-789", // ← CLI override
"DB_HOST": "dev.example.com", // ← From deployment
"DB_PASSWORD": "secret-pass" // ← CLI addition
}Deployment Configuration
To use CLI secrets injection, you must disable packaging in your variable groups.
Disable Secrets Packaging
- Open your variable group in ReAPI Studio
- Navigate to Secured Variable Group settings
- Disable the
packageSecretsInDeploymenttoggle - Save the variable group
- Create/update deployment
Result: Deployment contains test logic but no secrets. Secrets must be provided via CLI.
Validation
When creating a deployment with packaging disabled:
# This will fail - no secrets provided
npx @reapi/cli -y test run ./deployment.json
# ❌ Error: Secrets required but not provided
# This works - secrets injected
npx @reapi/cli -y test run ./deployment.json --secrets ./secrets.json
# ✅ Tests run successfullyCI/CD Integration
AWS Secrets Manager
GitHub Actions example:
# .github/workflows/api-tests.yml
name: API Tests
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
permissions:
id-token: write # Required for AWS OIDC
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GithubActionsRole
aws-region: us-east-1
- name: Retrieve secrets from AWS Secrets Manager
run: |
aws secretsmanager get-secret-value \
--secret-id reapi/production/api-secrets \
--query SecretString \
--output text > secrets.json
# Set restrictive permissions
chmod 600 secrets.json
- name: Run API tests
env:
REAPI_API_KEY: ${{ secrets.REAPI_API_KEY }}
run: |
npx @reapi/cli -y test run \
remote://your-deployment-id \
--secrets secrets.json \
--output ./test-results \
--throw-on-failure
- name: Cleanup secrets
if: always()
run: |
rm -f secrets.json
echo "Secrets file cleaned up"
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: test-results/HashiCorp Vault
GitLab CI example:
# .gitlab-ci.yml
api-tests:
stage: test
image: node:20
before_script:
# Install Vault CLI
- apt-get update && apt-get install -y vault
# Authenticate with Vault
- export VAULT_ADDR="https://vault.example.com"
- export VAULT_TOKEN="$VAULT_CI_TOKEN"
script:
# Retrieve secrets from Vault
- |
vault kv get -format=json secret/reapi/production | \
jq '.data.data' > secrets.json
# Set restrictive permissions
- chmod 600 secrets.json
# Run tests
- |
npx @reapi/cli -y test run \
remote://$DEPLOYMENT_ID \
--api-key "$REAPI_API_KEY" \
--secrets secrets.json \
--output ./test-results \
--report junit \
--throw-on-failure
after_script:
# Always cleanup
- rm -f secrets.json
artifacts:
when: always
reports:
junit: test-results/*.xmlAzure Key Vault
Azure DevOps Pipeline:
# azure-pipelines.yml
trigger:
- main
pool:
vmImage: "ubuntu-latest"
steps:
- task: AzureCLI@2
displayName: "Retrieve secrets from Azure Key Vault"
inputs:
azureSubscription: "Azure-Subscription"
scriptType: "bash"
scriptLocation: "inlineScript"
inlineScript: |
# Get secrets from Key Vault
API_KEY=$(az keyvault secret show --vault-name my-keyvault --name api-key --query value -o tsv)
DB_PASS=$(az keyvault secret show --vault-name my-keyvault --name db-password --query value -o tsv)
# Create secrets JSON
cat > secrets.json <<EOF
{
"API_KEY": "$API_KEY",
"DB_PASSWORD": "$DB_PASS"
}
EOF
chmod 600 secrets.json
- script: |
npx @reapi/cli -y test run \
remote://$(DEPLOYMENT_ID) \
--api-key "$(REAPI_API_KEY)" \
--secrets secrets.json \
--output ./test-results \
--throw-on-failure
displayName: "Run API Tests"
- script: rm -f secrets.json
displayName: "Cleanup secrets"
condition: always()
- task: PublishTestResults@2
displayName: "Publish test results"
condition: always()
inputs:
testResultsFormat: "JUnit"
testResultsFiles: "test-results/*.xml"Kubernetes Secrets
Jenkins Pipeline with K8s:
// Jenkinsfile
pipeline {
agent {
kubernetes {
yaml """
apiVersion: v1
kind: Pod
spec:
containers:
- name: node
image: node:20
command: ['sleep']
args: ['infinity']
volumeMounts:
- name: secrets
mountPath: /secrets
readOnly: true
volumes:
- name: secrets
secret:
secretName: reapi-test-secrets
"""
}
}
stages {
stage('Run API Tests') {
steps {
container('node') {
script {
// Convert K8s secret to JSON format
sh '''
echo '{' > secrets.json
echo " \\"API_KEY\\": \\"$(cat /secrets/api_key)\\"," >> secrets.json
echo " \\"DB_PASSWORD\\": \\"$(cat /secrets/db_password)\\"" >> secrets.json
echo '}' >> secrets.json
chmod 600 secrets.json
'''
// Run tests
sh '''
npx @reapi/cli -y test run \
remote://${DEPLOYMENT_ID} \
--api-key "${REAPI_API_KEY}" \
--secrets secrets.json \
--output ./test-results \
--throw-on-failure
'''
}
}
}
post {
always {
container('node') {
sh 'rm -f secrets.json'
}
}
}
}
}
}Configuration File
You can specify secrets file in your reapi.yaml configuration:
# reapi.yaml
target: remote://deployment-id
secrets: ./secrets.json # Path to secrets file
output: ./test-results
report: junit,html
throwOnFailure: trueUsage:
# Secrets path loaded from config
npx @reapi/cli -y test run --config reapi.yaml
# Override config with command-line parameter
npx @reapi/cli -y test run --config reapi.yaml --secrets ./prod-secrets.jsonSecurity Best Practices
File Permissions
Always set restrictive permissions on secrets files:
# Linux/macOS - owner read/write only
chmod 600 secrets.json
# Windows PowerShell - remove inheritance and grant only current user
icacls secrets.json /inheritance:r
icacls secrets.json /grant:r "%USERNAME%:(R,W)"Immediate Cleanup
Delete secrets files as soon as tests complete:
# Bash with trap for cleanup on exit
cleanup() {
rm -f secrets.json
echo "Secrets cleaned up"
}
trap cleanup EXIT
# Retrieve and run tests
aws secretsmanager get-secret-value ... > secrets.json
npx @reapi/cli -y test run --secrets secrets.json
# cleanup() runs automatically even if script failsPython example:
import subprocess
import os
import json
try:
# Retrieve secrets
secrets = retrieve_from_vault()
# Write to temporary file
with open('secrets.json', 'w') as f:
json.dump(secrets, f)
# Set restrictive permissions
os.chmod('secrets.json', 0o600)
# Run tests
subprocess.run([
'npx', '@reapi/cli', '-y', 'test', 'run',
'remote://deployment-id',
'--secrets', 'secrets.json'
], check=True)
finally:
# Always cleanup
if os.path.exists('secrets.json'):
os.remove('secrets.json')
print("Secrets cleaned up")Never Commit Secrets Files
.gitignore:
# Secrets files
secrets.json
secrets.yaml
*-secrets.json
*-secrets.yaml
*.secrets.*
# Environment files with secrets
prod-*.json
production-*.json
.env.production
.env.prodUse Temporary Directories
For extra security, write secrets to a temporary directory:
#!/bin/bash
set -e
# Create temporary directory
TEMP_DIR=$(mktemp -d)
SECRETS_FILE="$TEMP_DIR/secrets.json"
# Cleanup function
cleanup() {
rm -rf "$TEMP_DIR"
echo "Temporary directory cleaned up"
}
trap cleanup EXIT
# Retrieve secrets
aws secretsmanager get-secret-value \
--secret-id reapi/production \
--query SecretString \
--output text > "$SECRETS_FILE"
# Set restrictive permissions
chmod 600 "$SECRETS_FILE"
# Run tests
npx @reapi/cli -y test run \
remote://deployment-id \
--secrets "$SECRETS_FILE" \
--throw-on-failure
# cleanup() runs automaticallyRotate Secrets Regularly
Establish a rotation schedule:
# Example rotation workflow
# 1. Generate new secrets in your vault
aws secretsmanager rotate-secret --secret-id reapi/production
# 2. Update secrets in tests (no code changes needed!)
# Tests automatically pick up new secrets on next run
# 3. Run tests to verify new secrets work
npx @reapi/cli -y test run remote://id --secrets <(
aws secretsmanager get-secret-value \
--secret-id reapi/production \
--query SecretString \
--output text
)
# 4. If tests pass, rotation successful!Recommended rotation schedule:
| Secret Type | Rotation Frequency | Reason |
|---|---|---|
| Production API keys | Every 90 days | Compliance requirement |
| Database passwords | Every 60 days | High-risk credential |
| Service tokens | Every 30 days | Frequently accessed |
| Test environment | Every 180 days | Lower risk tolerance |
Audit Access
Enable audit logging in your secret management system:
AWS Secrets Manager CloudTrail:
{
"eventName": "GetSecretValue",
"requestParameters": {
"secretId": "reapi/production/api-secrets"
},
"userIdentity": {
"principalId": "AIDAJ45Q7YJFXYZ123456",
"arn": "arn:aws:iam::123456789012:user/ci-user"
},
"eventTime": "2024-01-15T10:30:00Z"
}Review audit logs regularly:
- Who accessed secrets?
- From which IP addresses?
- At what times?
- Were there any failed access attempts?
Troubleshooting
Secrets File Not Found
Error:
❌ Error: Secrets file not found: ./secrets.jsonSolutions:
- Verify file path is correct (relative or absolute)
- Check file was created successfully
- Ensure current directory is correct
# Debug: print absolute path
echo "Secrets file: $(pwd)/secrets.json"
ls -la secrets.json
# Use absolute path
npx @reapi/cli -y test run --secrets "$(pwd)/secrets.json"Invalid Secrets Format
Error:
❌ Error: Invalid secrets file formatSolutions:
- Validate JSON syntax
# Validate JSON
cat secrets.json | jq .
# If error, JSON is malformed- Check YAML syntax
# Validate YAML (requires yq)
cat secrets.yaml | yq .- Ensure file is UTF-8 encoded
# Check encoding
file -I secrets.json
# Should show: charset=utf-8Secrets Not Applied
Problem: Tests run but don’t use injected secrets
Solutions:
- Verify syntax in tests:
# ✅ Correct
Authorization: Bearer {{$secrets.API_KEY}}
# ❌ Wrong - missing $secrets prefix
Authorization: Bearer {{API_KEY}}- Check secret key names match:
// secrets.json
{
"API_KEY": "value" // ← Key name
}# Test must use exact key name
Authorization: Bearer {{$secrets.API_KEY}}- Enable debug logging:
# Set log level to debug
export LOG_LEVEL=debug
npx @reapi/cli -y test run --secrets secrets.jsonPermission Denied
Error:
❌ Error: Permission denied reading secrets fileSolutions:
# Check file permissions
ls -la secrets.json
# Grant read permission
chmod 600 secrets.json
# Verify you own the file
stat secrets.json
# Run as file owner
sudo -u owner npx @reapi/cli -y test run --secrets secrets.jsonSecrets Leak in CI Logs
Problem: Secrets visible in CI/CD logs
Solutions:
- Never echo secrets directly:
# ❌ BAD - secret visible in logs
echo "Using API key: $API_KEY"
# ✅ GOOD - masked output
echo "API key loaded successfully"- Use CI secret masking:
# GitHub Actions - secrets are automatically masked
- name: Run tests
env:
SECRET_KEY: ${{ secrets.SECRET_KEY }}
run: echo "Secret is: $SECRET_KEY" # Will show: Secret is: ***- Disable debug mode in CI:
# Don't use debug logging in CI
# ❌ export LOG_LEVEL=debug
# ✅ Use default log level
npx @reapi/cli -y test run --secrets secrets.jsonAdvanced Patterns
Dynamic Secret Selection
Choose different secrets based on environment:
#!/bin/bash
ENVIRONMENT=${1:-staging}
case $ENVIRONMENT in
production)
SECRET_ID="reapi/production"
DEPLOYMENT_ID="prod-deployment-id"
;;
staging)
SECRET_ID="reapi/staging"
DEPLOYMENT_ID="staging-deployment-id"
;;
development)
SECRET_ID="reapi/development"
DEPLOYMENT_ID="dev-deployment-id"
;;
*)
echo "Unknown environment: $ENVIRONMENT"
exit 1
;;
esac
# Retrieve appropriate secrets
aws secretsmanager get-secret-value \
--secret-id "$SECRET_ID" \
--query SecretString \
--output text > secrets.json
# Run tests for environment
npx @reapi/cli -y test run \
"remote://$DEPLOYMENT_ID" \
--secrets secrets.json \
--throw-on-failureMultiple Secret Sources
Merge secrets from multiple vaults:
#!/bin/bash
set -e
# Retrieve from multiple sources
aws secretsmanager get-secret-value \
--secret-id reapi/common \
--query SecretString \
--output text > common.json
aws secretsmanager get-secret-value \
--secret-id reapi/production \
--query SecretString \
--output text > prod.json
# Merge JSON files (prod overrides common)
jq -s '.[0] * .[1]' common.json prod.json > secrets.json
# Cleanup intermediate files
rm -f common.json prod.json
# Run tests
npx @reapi/cli -y test run --secrets secrets.json
# Cleanup final secrets
rm -f secrets.jsonSecrets with Environment Variables
Combine secrets file with environment variables:
# reapi.yaml
target: remote://deployment-id
secrets: ./secrets.json
env: ./runtime-env.json # Non-secret environment variablesUse case: Secrets contain credentials, environment file contains configuration:
// secrets.json (from vault)
{
"API_KEY": "sk_prod_abc123",
"DB_PASSWORD": "secret-pass"
}// runtime-env.json (from config/git)
{
"API_BASE_URL": "https://api.example.com",
"TIMEOUT": 30000,
"RETRY_COUNT": 3
}Parallel Test Runs
Run multiple test suites in parallel with different secrets:
#!/bin/bash
# Run staging and production tests in parallel
(
aws secretsmanager get-secret-value \
--secret-id reapi/staging \
--query SecretString \
--output text > staging-secrets.json
npx @reapi/cli -y test run \
remote://staging-deployment \
--secrets staging-secrets.json \
--output ./staging-results
rm -f staging-secrets.json
) &
(
aws secretsmanager get-secret-value \
--secret-id reapi/production \
--query SecretString \
--output text > prod-secrets.json
npx @reapi/cli -y test run \
remote://prod-deployment \
--secrets prod-secrets.json \
--output ./prod-results
rm -f prod-secrets.json
) &
# Wait for both to complete
wait
echo "All test suites completed"Comparison: Packaged vs Injected
| Aspect | Packaged Secrets | CLI Injected Secrets |
|---|---|---|
| Security | Encrypted in cloud | Never leave your infrastructure |
| Rotation | Requires redeployment | Instant (update vault) |
| Setup | Simple (UI workflow) | More complex (vault + CI) |
| Convenience | High (one-time setup) | Medium (script required) |
| Compliance | Good for most cases | Best for strict requirements |
| Secret Management | ReAPI handles | Your vault handles |
| Audit Trail | ReAPI logs | Your vault’s audit logs |
| Cost | Included | Depends on vault provider |
| Use Case | Internal testing, staging | Production, compliance-heavy |
Best Practices Summary
DO ✅
- Use CLI injection for production environments
- Store secrets in a dedicated vault (AWS, Vault, Azure)
- Set restrictive file permissions (600)
- Delete secrets files immediately after use
- Add secrets files to
.gitignore - Use temporary directories for secrets
- Rotate secrets regularly (60-90 days)
- Enable audit logging in your vault
- Test secret rotation process
- Document secret rotation procedures
DON’T ❌
- Commit secrets files to version control
- Echo secrets in CI logs
- Leave secrets files on disk
- Use weak file permissions (644, 755)
- Share secrets files via email/chat
- Store secrets in plain text config files
- Skip cleanup steps in error cases
- Use same secrets for all environments
- Forget to test after secret rotation
- Ignore audit logs
Next Steps
- CLI Usage Guide - Learn CLI basics and commands
- Secrets Management - Understand secrets concepts
- CI/CD Integration - Set up automated testing
- Security & Secrets - Security architecture deep dive
Questions? Contact us at support@reapi.com or join our Discord community