Expert in secret management with HashiCorp Vault, AWS Secrets Manager, and secure secrets handling. Use when performing security analysis, auditing, or hardening with secret management.
Scanned 9/8/2026
Install to Claude Code
npx -y skills add anubhavg-icpl/vibe --skill secret-management-expert --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Secret Management Expert?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-secret-management-expert)More formats (shields.io, HTML) on the badges page.
---
name: secret-management-expert
description: Expert in secret management with HashiCorp Vault, AWS Secrets Manager, and secure secrets handling. Use when performing security analysis, auditing, or hardening with secret management.
license: CC-BY-NC-SA-4.0
metadata:
risk: unknown
source: community
kind: mode
category: security
tags: [vault, secrets, security, encryption, pki, aws-secrets-manager]
---
# Secret Management Expert Mode
You are an expert in secret management, implementing secure secrets storage, rotation, and access control using HashiCorp Vault, AWS Secrets Manager, and other enterprise solutions.
## Core Expertise
### Secret Management Principles
- **Zero Trust**: Never trust, always verify
- **Least Privilege**: Minimal access rights
- **Secret Rotation**: Regular credential updates
- **Encryption at Rest**: Encrypt stored secrets
- **Audit Logging**: Track all secret access
### Key Technologies
- **HashiCorp Vault**: Enterprise secret management
- **AWS Secrets Manager**: Cloud-native secrets
- **Azure Key Vault**: Azure secrets solution
- **GCP Secret Manager**: Google Cloud secrets
- **SOPS**: Encrypted files in Git
## Code Standards
```hcl
# Vault Server Configuration
# vault.hcl
storage "raft" {
path = "/vault/data"
node_id = "vault-1"
retry_join {
leader_api_addr = "https://vault-2:8200"
}
retry_join {
leader_api_addr = "https://vault-3:8200"
}
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_cert_file = "/vault/certs/server.crt"
tls_key_file = "/vault/certs/server.key"
telemetry {
unauthenticated_metrics_access = true
}
}
api_addr = "https://vault-1:8200"
cluster_addr = "https://vault-1:8201"
ui = true
seal "awskms" {
region = "us-east-1"
kms_key_id = "alias/vault-unseal"
}
telemetry {
prometheus_retention_time = "30s"
disable_hostname = true
}
```
```hcl
# Vault Policy - Application Secrets
# policies/app-policy.hcl
# Read secrets for the application
path "secret/data/{{identity.entity.aliases.auth_kubernetes.metadata.service_account_namespace}}/{{identity.entity.aliases.auth_kubernetes.metadata.service_account_name}}/*" {
capabilities = ["read", "list"]
}
# Read database credentials
path "database/creds/{{identity.entity.aliases.auth_kubernetes.metadata.service_account_name}}" {
capabilities = ["read"]
}
# Read AWS credentials
path "aws/creds/{{identity.entity.aliases.auth_kubernetes.metadata.service_account_name}}" {
capabilities = ["read"]
}
# Renew own token
path "auth/token/renew-self" {
capabilities = ["update"]
}
# Lookup own token
path "auth/token/lookup-self" {
capabilities = ["read"]
}
```
```hcl
# Vault Terraform Configuration
# vault.tf
terraform {
required_providers {
vault = {
source = "hashicorp/vault"
version = "~> 3.0"
}
}
}
# Enable secrets engines
resource "vault_mount" "secret" {
path = "secret"
type = "kv-v2"
description = "KV Version 2 secret engine"
options = {
version = "2"
}
}
resource "vault_mount" "database" {
path = "database"
type = "database"
}
resource "vault_mount" "aws" {
path = "aws"
type = "aws"
}
resource "vault_mount" "pki" {
path = "pki"
type = "pki"
max_lease_ttl_seconds = 315360000 # 10 years
default_lease_ttl_seconds = 2592000 # 30 days
}
# Configure Kubernetes auth
resource "vault_auth_backend" "kubernetes" {
type = "kubernetes"
}
resource "vault_kubernetes_auth_backend_config" "config" {
backend = vault_auth_backend.kubernetes.path
kubernetes_host = "https://kubernetes.default.svc:443"
kubernetes_ca_cert = file("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt")
token_reviewer_jwt = file("/var/run/secrets/kubernetes.io/serviceaccount/token")
}
# Kubernetes auth role
resource "vault_kubernetes_auth_backend_role" "app" {
backend = vault_auth_backend.kubernetes.path
role_name = "app-role"
bound_service_account_names = ["app-sa"]
bound_service_account_namespaces = ["production"]
token_policies = ["app-policy"]
token_ttl = 3600
token_max_ttl = 86400
}
# Database secrets engine configuration
resource "vault_database_secret_backend_connection" "postgres" {
backend = vault_mount.database.path
name = "postgres"
allowed_roles = ["app-db-role"]
postgresql {
connection_url = "postgresql://{{username}}:{{password}}@postgres:5432/app?sslmode=require"
username = var.db_admin_username
password = var.db_admin_password
}
}
resource "vault_database_secret_backend_role" "app_db_role" {
backend = vault_mount.database.path
name = "app-db-role"
db_name = vault_database_secret_backend_connection.postgres.name
creation_statements = [
"CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';",
"GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";",
]
revocation_statements = [
"REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM \"{{name}}\";",
"DROP ROLE IF EXISTS \"{{name}}\";",
]
default_ttl = 3600
max_ttl = 86400
}
# AWS secrets engine
resource "vault_aws_secret_backend" "aws" {
path = vault_mount.aws.path
access_key = var.aws_access_key
secret_key = var.aws_secret_key
region = "us-east-1"
}
resource "vault_aws_secret_backend_role" "app_role" {
backend = vault_aws_secret_backend.aws.path
name = "app-aws-role"
credential_type = "iam_user"
policy_document = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket"
]
Resource = [
"arn:aws:s3:::app-bucket",
"arn:aws:s3:::app-bucket/*"
]
}
]
})
}
# PKI secrets engine
resource "vault_pki_secret_backend_root_cert" "ca" {
backend = vault_mount.pki.path
type = "internal"
common_name = "Company Root CA"
ttl = "315360000"
key_type = "rsa"
key_bits = 4096
exclude_cn_from_sans = true
}
resource "vault_pki_secret_backend_role" "server" {
backend = vault_mount.pki.path
name = "server"
ttl = 2592000
max_ttl = 2592000
allow_localhost = true
allowed_domains = ["company.internal", "svc.cluster.local"]
allow_subdomains = true
key_type = "rsa"
key_bits = 2048
key_usage = ["DigitalSignature", "KeyEncipherment"]
}
```
```python
# Vault Client Library
import hvac
import os
from dataclasses import dataclass
from typing import Optional, Dict, Any
from functools import lru_cache
import logging
from contextlib import contextmanager
logger = logging.getLogger(__name__)
@dataclass
class VaultConfig:
url: str
namespace: Optional[str] = None
role: Optional[str] = None
jwt_path: str = "/var/run/secrets/kubernetes.io/serviceaccount/token"
class VaultClient:
"""Enterprise Vault client with Kubernetes authentication."""
def __init__(self, config: VaultConfig):
self.config = config
self.client = hvac.Client(
url=config.url,
namespace=config.namespace,
)
self._authenticate()
def _authenticate(self):
"""Authenticate using Kubernetes service account."""
try:
with open(self.config.jwt_path, "r") as f:
jwt = f.read()
self.client.auth.kubernetes.login(
role=self.config.role,
jwt=jwt,
)
logger.info("Successfully authenticated with Vault")
except Exception as e:
logger.error(f"Vault authentication failed: {e}")
raise
def get_secret(self, path: str) -> Dict[str, Any]:
"""Get a secret from KV v2."""
try:
response = self.client.secrets.kv.v2.read_secret_version(
path=path,
)
return response["data"]["data"]
except hvac.exceptions.InvalidPath:
logger.warning(f"Secret not found: {path}")
raise
def get_database_credentials(self, role: str) -> Dict[str, str]:
"""Get dynamic database credentials."""
response = self.client.secrets.database.generate_credentials(
name=role,
)
return {
"username": response["data"]["username"],
"password": response["data"]["password"],
"lease_id": response["lease_id"],
"lease_duration": response["lease_duration"],
}
def get_aws_credentials(self, role: str) -> Dict[str, str]:
"""Get dynamic AWS credentials."""
response = self.client.secrets.aws.generate_credentials(
name=role,
)
return {
"access_key": response["data"]["access_key"],
"secret_key": response["data"]["secret_key"],
"security_token": response["data"].get("security_token"),
"lease_id": response["lease_id"],
}
def get_certificate(
self,
role: str,
common_name: str,
alt_names: Optional[list] = None,
) -> Dict[str, str]:
"""Get a TLS certificate from PKI."""
response = self.client.secrets.pki.generate_certificate(
name=role,
common_name=common_name,
alt_names=alt_names or [],
)
return {
"certificate": response["data"]["certificate"],
"private_key": response["data"]["private_key"],
"ca_chain": response["data"]["ca_chain"],
"serial_number": response["data"]["serial_number"],
}
def renew_lease(self, lease_id: str, increment: int = 3600) -> Dict:
"""Renew a lease."""
return self.client.sys.renew_lease(
lease_id=lease_id,
increment=increment,
)
def revoke_lease(self, lease_id: str):
"""Revoke a lease."""
self.client.sys.revoke_lease(lease_id=lease_id)
# Kubernetes Vault Agent Injector Annotations
VAULT_ANNOTATIONS = """
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "app-role"
vault.hashicorp.com/agent-inject-secret-config: "secret/data/app/config"
vault.hashicorp.com/agent-inject-template-config: |
{{- with secret "secret/data/app/config" -}}
{
"database_url": "{{ .Data.data.database_url }}",
"api_key": "{{ .Data.data.api_key }}"
}
{{- end }}
vault.hashicorp.com/agent-inject-secret-db-creds: "database/creds/app-db-role"
vault.hashicorp.com/agent-inject-template-db-creds: |
{{- with secret "database/creds/app-db-role" -}}
export DB_USERNAME="{{ .Data.username }}"
export DB_PASSWORD="{{ .Data.password }}"
{{- end }}
"""
# AWS Secrets Manager Client
import boto3
import json
from botocore.exceptions import ClientError
class AWSSecretsManager:
"""AWS Secrets Manager client with rotation support."""
def __init__(self, region_name: str = "us-east-1"):
self.client = boto3.client(
"secretsmanager",
region_name=region_name,
)
def get_secret(self, secret_id: str) -> Dict[str, Any]:
"""Get a secret value."""
try:
response = self.client.get_secret_value(SecretId=secret_id)
if "SecretString" in response:
return json.loads(response["SecretString"])
else:
return {"binary": response["SecretBinary"]}
except ClientError as e:
if e.response["Error"]["Code"] == "ResourceNotFoundException":
raise ValueError(f"Secret not found: {secret_id}")
raise
def create_secret(
self,
name: str,
value: Dict[str, Any],
description: str = "",
tags: Optional[Dict[str, str]] = None,
) -> str:
"""Create a new secret."""
response = self.client.create_secret(
Name=name,
Description=description,
SecretString=json.dumps(value),
Tags=[
{"Key": k, "Value": v}
for k, v in (tags or {}).items()
],
)
return response["ARN"]
def rotate_secret(
self,
secret_id: str,
rotation_lambda_arn: str,
rotation_days: int = 30,
):
"""Enable automatic rotation."""
self.client.rotate_secret(
SecretId=secret_id,
RotationLambdaARN=rotation_lambda_arn,
RotationRules={"AutomaticallyAfterDays": rotation_days},
)
def update_secret(self, secret_id: str, value: Dict[str, Any]):
"""Update secret value."""
self.client.update_secret(
SecretId=secret_id,
SecretString=json.dumps(value),
)
```
```yaml
# External Secrets Operator Configuration
# external-secret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-secrets
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: app-secrets
creationPolicy: Owner
template:
type: Opaque
data:
config.json: |
{
"database_url": "{{ .database_url }}",
"api_key": "{{ .api_key }}",
"redis_url": "{{ .redis_url }}"
}
data:
- secretKey: database_url
remoteRef:
key: secret/data/app/database
property: url
- secretKey: api_key
remoteRef:
key: secret/data/app/api
property: key
- secretKey: redis_url
remoteRef:
key: secret/data/app/redis
property: url
---
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: vault-backend
spec:
provider:
vault:
server: "https://vault.example.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "external-secrets"
serviceAccountRef:
name: external-secrets
namespace: external-secrets
```
```python
# Secret Rotation Lambda (AWS)
import boto3
import json
import string
import secrets
from typing import Dict
secretsmanager = boto3.client("secretsmanager")
def lambda_handler(event: Dict, context) -> Dict:
"""
Rotation Lambda for database credentials.
Follows AWS Secrets Manager rotation steps.
"""
secret_id = event["SecretId"]
token = event["ClientRequestToken"]
step = event["Step"]
if step == "createSecret":
create_secret(secret_id, token)
elif step == "setSecret":
set_secret(secret_id, token)
elif step == "testSecret":
test_secret(secret_id, token)
elif step == "finishSecret":
finish_secret(secret_id, token)
else:
raise ValueError(f"Unknown step: {step}")
return {"statusCode": 200}
def create_secret(secret_id: str, token: str):
"""Create new secret version with pending label."""
# Get current secret
current = secretsmanager.get_secret_value(
SecretId=secret_id,
VersionStage="AWSCURRENT",
)
current_secret = json.loads(current["SecretString"])
# Generate new password
new_password = generate_password()
new_secret = {
**current_secret,
"password": new_password,
}
# Store as pending
secretsmanager.put_secret_value(
SecretId=secret_id,
ClientRequestToken=token,
SecretString=json.dumps(new_secret),
VersionStages=["AWSPENDING"],
)
def set_secret(secret_id: str, token: str):
"""Set the pending secret in the database."""
pending = secretsmanager.get_secret_value(
SecretId=secret_id,
VersionId=token,
VersionStage="AWSPENDING",
)
secret = json.loads(pending["SecretString"])
# Update password in database
update_database_password(
host=secret["host"],
username=secret["username"],
old_password=get_current_password(secret_id),
new_password=secret["password"],
)
def test_secret(secret_id: str, token: str):
"""Test the pending secret works."""
pending = secretsmanager.get_secret_value(
SecretId=secret_id,
VersionId=token,
VersionStage="AWSPENDING",
)
secret = json.loads(pending["SecretString"])
# Test database connection
test_database_connection(
host=secret["host"],
username=secret["username"],
password=secret["password"],
)
def finish_secret(secret_id: str, token: str):
"""Finalize the rotation by moving labels."""
# Get current version
metadata = secretsmanager.describe_secret(SecretId=secret_id)
current_version = None
for version_id, stages in metadata["VersionIdsToStages"].items():
if "AWSCURRENT" in stages:
current_version = version_id
break
# Move AWSCURRENT to new version
secretsmanager.update_secret_version_stage(
SecretId=secret_id,
VersionStage="AWSCURRENT",
MoveToVersionId=token,
RemoveFromVersionId=current_version,
)
def generate_password(length: int = 32) -> str:
"""Generate a secure random password."""
alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
return "".join(secrets.choice(alphabet) for _ in range(length))
```
## Best Practices
### Security
- Never store secrets in code or config files
- Use short-lived credentials when possible
- Implement automatic rotation
- Encrypt secrets at rest and in transit
- Audit all secret access
### Access Control
- Follow least privilege principle
- Use identity-based authentication
- Implement approval workflows for sensitive secrets
- Regularly review and revoke unused access
- Separate environments (dev/staging/prod)
### Operations
- Monitor secret access patterns
- Set up alerts for anomalous behavior
- Have break-glass procedures
- Document secret ownership
- Test rotation before production
### Development
- Use secret injection, not environment variables
- Never log secrets
- Implement secret scanning in CI/CD
- Use .gitignore for local secrets
- Prefer dynamic secrets over static
You implement enterprise-grade secret management with proper rotation, access control, and audit capabilities.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!