Browse OpenBao/HashiCorp Vault secrets without exposing values. Request secret updates via Slack approval workflow.
Let your team see secret KEYS without exposing VALUES. Update secrets via approval workflow.
OpenBao/Vault has a major UX issue: There's no way to show users which secrets exist (keys) without also showing the secret values.
This creates a dilemma:
- β Option 1: Give team access to Vault β They see ALL secret values (security risk!)
- β Option 2: Lock down Vault β Team has no visibility, constantly asks "what secrets exist?"
- β Option 3: Maintain a separate documentation β Gets out of sync instantly
Real-world scenario:
- Developer: "Does the
prod/api/path have aDATABASE_URLsecret?" - DevOps: Has to manually check Vault and tell them
- Developer: "Can you add a
REDIS_URLsecret?" - DevOps: Manually adds it
This doesn't scale.
Vault Clone solves this with:
- π Read-Only Mirror: Auto-syncs secret paths and keys from OpenBao/Vault every 5 minutes
- π Redacted Values: Shows secret keys but redacts all values (shows only first/last char hints)
- π Searchable UI: Team can browse, search, filter all secrets without security risk
- β Self-Serve Updates: Request new secrets via web form β Triggers Slack approval β Auto-executes
- π Google OAuth SSO: Only authorized domain can access
- π― Zero Secrets Stored: Values are redacted at ingest, never stored in memory or disk
# Values are redacted BEFORE storage - original values NEVER touch this app
def redact_value(value: str) -> str:
"""Redact secret, showing only first/last character hints."""
if len(value) <= 4:
return "***"
return f"{value[0]}***{value[-1]}" # e.g., "p***d" for "password123"Even if someone hacks this app's database, they get nothing.
OpenBao Vault (read-only token)
β
Sync Service (list + read secrets)
β
Redaction Engine (values β "p***d")
β
Local Database (keys + redacted values)
User β Google SSO Login β Browse UI
β
Search: "DATABASE_URL"
β
Results show:
β
Path: prod/api/config
β
Key: DATABASE_URL
β
Value: p***d (redacted)
β
Last Updated: 2026-06-20
Team can now:
- See which secrets exist
- Know the exact secret key names
- Know where secrets are located
- Search across all secrets
- WITHOUT seeing actual values!
User clicks "Add Secret" β Fill form:
- Path: prod/api/config
- Key: REDIS_URL
- Value: redis://...
- Reason: "New caching layer for API"
β
Form submits to Approval Handler (Slack integration)
β
Approver gets Slack message with [Approve] [Reject]
β
On approval β Argo Workflow executes β Adds secret to OpenBao
β
Next sync (5 min) β New secret appears in Vault Clone
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β OpenBao/Vault (Production) β
β Read-only token: Can list + read all secrets β
ββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β (every 5 min sync)
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Vault Clone (This App) β
β ββββββββββββββββ βββββββββββββββββ ββββββββββββββββ β
β β Sync Service ββ β Redaction ββ β Database β β
β β (httpx async)β β Engine β β (in-mem) β β
β ββββββββββββββββ βββββββββββββββββ ββββββββββββββββ β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Web UI (FastAPI + Jinja2 templates) β β
β β - Browse/search secrets β β
β β - View redacted values β β
β β - Add Secret form β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β (Add Secret request)
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Approval Handler (Slack Integration) β
β Sends approval request to Slack β Approvers decide β
ββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β (On approval)
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Argo Workflow (Auto-Execution) β
β Executes: vault kv put secret/path key=value β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# BEFORE storage - values never exist in raw form
value = vault.read("secret/prod/db")["password"] # "SuperSecret123"
redacted = redact_value(value) # "S***3"
db.store(key="password", value=redacted) # Only "S***3" storedOriginal value never touches:
- Application memory (beyond read β redact)
- Database
- Logs
- Response payloads
- Only users from
ALLOWED_EMAIL_DOMAINcan log in - Session-based authentication (7-day expiry)
- Signed cookies (no database needed)
The app uses a read-only Vault token:
- β Can list secret paths
- β Can read secret values (for sync only)
- β Cannot write/update/delete secrets
- β Cannot create policies
- β Cannot modify ACLs
All writes go through Approval Handler β Argo Workflow.
logger.info(f"Synced secret: {path}/{key}") # β
OK - no value
logger.info(f"Value: {value}") # β NEVER - would leakUpdates require:
- Slack approval from authorized approvers
- Optional ticket requirement (Jira/Linear)
- Multi-level approvals for production paths
- Full audit trail (who requested, who approved, when executed)
- β Auto-Sync: Mirrors all secret paths/keys every 5 minutes
- β Redacted Values: Shows "p***d" instead of "password123"
- β Search & Filter: Find secrets across all paths
- β Path Hierarchy: Browse secrets by folder structure
- β Self-Serve Add Secret: Request form β Slack approval β Auto-execution
- β Google OAuth SSO: Domain-restricted access
- β Responsive UI: Modern web interface (Jinja2 templates)
- β
Health Checks:
/healthendpoint for monitoring
For each secret, shows:
- π Key Name: Exact secret key
- π Path: Where secret is located
- ποΈ Redacted Value: First/last char hints (e.g., "A***k" for base64)
- π Last Sync: When this secret was last updated from Vault
- π·οΈ Version: Secret version number (if supported)
Users can request:
- Path:
prod/api/config - Key:
NEW_API_KEY - Value: (actual secret - sent only to approver via Slack DM)
- Description: Why this secret is needed
- Ticket ID: Optional Jira/Linear ticket
- Python 3.11+
- OpenBao/HashiCorp Vault instance
- Read-only Vault token
- Google OAuth credentials (for SSO)
- Approval Handler deployed (optional, for self-serve updates)
git clone https://github.com/code-rajeshdeb/vault-clone.git
cd vault-clonepip install -r requirements.txt# Vault Configuration
export VAULT_ADDR=https://vault.example.com
export VAULT_TOKEN_FILE=/path/to/token # OR set VAULT_TOKEN env var
export VAULT_MOUNT=secret # Vault KV mount point
export SYNC_INTERVAL=300 # Sync every 5 minutes
# Google OAuth (for SSO)
export GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
export GOOGLE_CLIENT_SECRET=your-secret
export GOOGLE_REDIRECT_URI=https://vault-clone.example.com/auth/google/callback
export ALLOWED_EMAIL_DOMAIN=example.com # Only @example.com emails allowed
# Approval Handler (for Add Secret feature)
export APPROVAL_HANDLER_URL=http://approval-handler:8080
# Session Secret (generate with: openssl rand -hex 32)
export SESSION_SECRET_KEY=your-session-secret-key# Development
uvicorn main:app --reload --host 0.0.0.0 --port 8000
# Production
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4- Open
http://localhost:8000 - Click "Login with Google"
- Authenticate with your
@example.comemail - Browse secrets!
docker build -t vault-clone:latest .docker run -d \
-p 8000:8000 \
-e VAULT_ADDR=https://vault.example.com \
-e VAULT_TOKEN=your-readonly-token \
-e GOOGLE_CLIENT_ID=your-client-id \
-e GOOGLE_CLIENT_SECRET=your-secret \
-e ALLOWED_EMAIL_DOMAIN=example.com \
vault-clone:latestManifests are in k8s/:
# Update k8s/external-secret-*.yaml with your secret manager paths
# Update k8s/deployment.yaml with your config
kubectl apply -f k8s/Included:
deployment.yaml- Main app deploymentservice.yaml- ClusterIP serviceexternal-secret-openbao-readonly-token.yaml- Read-only Vault tokenexternal-secret-google-oauth.yaml- Google OAuth credentials
Browse Secrets:
- Login with Google SSO
- Browse secret paths in sidebar
- Search for specific keys
- View redacted values (e.g., "A***k")
- Know which secrets exist without seeing values!
Request New Secret:
- Click "Add Secret" button
- Fill form:
- Path:
prod/api/config - Key:
NEW_SECRET_KEY - Value: (your secret value)
- Reason: Why you need this
- Path:
- Submit β Goes to Slack for approval
- Wait for approval β Secret auto-added to Vault
- Next sync (5 min) β Appears in Vault Clone
- Receive Slack message: "New secret request from @john.doe"
- See details:
- Path, key, actual value (in Slack DM only!)
- Requester, reason, ticket
- Click [Approve] or [Reject]
- On approve β Argo Workflow executes β Secret added
Create a read-only policy:
# vault-clone-readonly.hcl
path "secret/*" {
capabilities = ["read", "list"]
}
path "secret/metadata/*" {
capabilities = ["read", "list"]
}Apply:
vault policy write vault-clone-readonly vault-clone-readonly.hcl
vault token create -policy=vault-clone-readonly -period=720h- Go to Google Cloud Console
- Create OAuth 2.0 credentials
- Add authorized redirect URI:
https://your-domain.com/auth/google/callback - Copy Client ID and Secret
- Set as environment variables
Adjust via SYNC_INTERVAL (seconds):
300(5 min) - Default, good balance60(1 min) - More frequent updates (higher Vault load)600(10 min) - Less frequent (lower load)
pytest tests/ -vcurl http://localhost:8000/health
# {"status":"healthy","vault_connected":true,"last_sync":"2026-06-28T10:30:00Z"}- β Use read-only Vault token - Never give write access
- β Rotate token regularly - Set token TTL/period
- β Domain-restrict SSO - Only allow company email domain
- β HTTPS only - Always use TLS for production
- β Network policies - Restrict pod egress to Vault + Google only
- β Audit logs - Monitor who accesses which secrets
- β Secret rotation - Use Vault's TTL for automatic expiry
Contributions welcome!
- Fork the repository
- Create feature branch (
git checkout -b feature/amazing-feature) - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open Pull Request
MIT License - see LICENSE file.
If you find this useful:
- β Star this repo
- π Report bugs via Issues
- π‘ Suggest features
- π° Sponsor via GitHub Sponsors
- Approval Handler - Slack approval workflow (used for Add Secret feature)
- OpenBao - Open-source Vault fork
- HashiCorp Vault - Secrets management
- v2.0: Support for multiple Vault backends
- v2.1: AWS Secrets Manager integration
- v2.2: Secret diff view (track changes over time)
- v2.3: Role-based access control (RBAC)
- v2.4: Bulk secret operations
- v2.5: Secret usage analytics
- Issues: https://github.com/code-rajeshdeb/vault-clone/issues
- Discussions: https://github.com/code-rajeshdeb/vault-clone/discussions
Made with β€οΈ for DevOps/SRE teams who need secret visibility without security risks.
β Star this repo if it solves your Vault UX problem!