Skip to content
Back to skills

3060 Secrets Management 2954c4a6

ASecurity

Secrets management patterns including vaults, environment variables, key rotation, and secure credential handling for applications

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
securitypythongobashsqlawsgcpgitapidatabaseci/cd

Works with

  • cli
  • api

Security analysis

A100/100

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 3060-secrets-management_2954c4a6 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 3060 Secrets Management 2954c4a6?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 3060 Secrets Management 2954c4a6
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-3060-secrets-management-2954c4a6/badge)](https://www.skillsdirectory.com/skills/tools-only-3060-secrets-management-2954c4a6)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
---
name: security-secrets-management
description: Secrets management patterns including vaults, environment variables, key rotation, and secure credential handling for applications
---

# Security: Secrets Management

**Scope**: Secrets vaults, credential management, key rotation, secure storage
**Lines**: ~380
**Last Updated**: 2025-10-27

## When to Use This Skill

Activate this skill when:
- Storing API keys, passwords, or credentials
- Implementing secrets management for applications
- Integrating with HashiCorp Vault, AWS Secrets Manager, or similar
- Rotating encryption keys or credentials
- Managing secrets in CI/CD pipelines
- Preventing credential exposure in code or logs
- Designing multi-environment secret strategies

## Secrets Management Fundamentals

### What Are Secrets?

```python
"""
Secrets are sensitive data that must be protected:
- API keys and tokens
- Database passwords
- Encryption keys
- TLS/SSL certificates
- OAuth client secrets
- Service account credentials
- Signing keys
"""

# ❌ NEVER DO THIS - Hardcoded secrets (example only, never in production)
API_KEY = "[REDACTED]"  # Example of what NOT to do
DB_PASSWORD = "MyP@ssw0rd123"  # Example of what NOT to do

# ✅ DO THIS - Environment variables or vault
import os
API_KEY = os.environ.get('API_KEY')
DB_PASSWORD = vault.get_secret('database/password')
```

## Environment Variables

### Using Environment Variables

```python
import os
from typing import Optional

# Basic usage
DATABASE_URL = os.environ['DATABASE_URL']  # Raises KeyError if missing
API_KEY = os.environ.get('API_KEY')  # Returns None if missing
API_KEY = os.environ.get('API_KEY', 'default-value')  # With default

# Validation helper
def get_required_env(key: str) -> str:
    """Get required environment variable or raise error"""
    value = os.environ.get(key)
    if not value:
        raise ValueError(f"Missing required environment variable: {key}")
    return value

# Type conversion
PORT = int(os.environ.get('PORT', '8000'))
DEBUG = os.environ.get('DEBUG', 'false').lower() == 'true'

# Complex secrets (JSON)
import json
AWS_CREDENTIALS = json.loads(os.environ.get('AWS_CREDENTIALS', '{}'))
```

### .env Files (Development Only)

```python
# .env file (NEVER commit to git)
DATABASE_URL=postgresql://user:password@localhost/db
API_KEY=sk_test_abc123
STRIPE_SECRET=sk_test_def456
DEBUG=true

# Load with python-dotenv
from dotenv import load_dotenv
import os

# Load .env file in development
if os.environ.get('ENV') != 'production':
    load_dotenv()

DATABASE_URL = os.environ['DATABASE_URL']
```

**.gitignore**:
```gitignore
# Never commit secrets
.env
.env.local
.env.*.local
secrets.json
credentials.json
*.pem
*.key
```

## HashiCorp Vault

### Vault Integration (Python)

```python
import hvac
from typing import Dict, Any

class VaultClient:
    """HashiCorp Vault client wrapper"""

    def __init__(self, url: str = None, token: str = None):
        self.url = url or os.environ['VAULT_ADDR']
        self.token = token or os.environ['VAULT_TOKEN']

        self.client = hvac.Client(url=self.url, token=self.token)

        if not self.client.is_authenticated():
            raise Exception("Vault authentication failed")

    def get_secret(self, path: str) -> Dict[str, Any]:
        """Read secret from Vault"""
        response = self.client.secrets.kv.v2.read_secret_version(
            path=path,
            mount_point='secret'
        )
        return response['data']['data']

    def write_secret(self, path: str, data: Dict[str, Any]):
        """Write secret to Vault"""
        self.client.secrets.kv.v2.create_or_update_secret(
            path=path,
            secret=data,
            mount_point='secret'
        )

    def delete_secret(self, path: str):
        """Delete secret from Vault"""
        self.client.secrets.kv.v2.delete_metadata_and_all_versions(
            path=path,
            mount_point='secret'
        )

# Usage
vault = VaultClient()

# Read database credentials
db_creds = vault.get_secret('database/postgres')
DATABASE_URL = (
    f"postgresql://{db_creds['username']}:{db_creds['password']}"
    f"@{db_creds['host']}:{db_creds['port']}/{db_creds['database']}"
)

# Write API key
vault.write_secret('api/stripe', {
    'secret_key': 'sk_live_abc123',
    'publishable_key': 'pk_live_def456'
})

# Read API key
stripe_keys = vault.get_secret('api/stripe')
STRIPE_SECRET = stripe_keys['secret_key']
```

### Vault AppRole Authentication

```python
class VaultAppRoleClient:
    """Vault client using AppRole authentication"""

    def __init__(self, role_id: str, secret_id: str, url: str = None):
        self.url = url or os.environ['VAULT_ADDR']
        self.client = hvac.Client(url=self.url)

        # Authenticate with AppRole
        response = self.client.auth.approle.login(
            role_id=role_id,
            secret_id=secret_id
        )

        self.client.token = response['auth']['client_token']

        # Renew token periodically
        self._start_token_renewal()

    def _start_token_renewal(self):
        """Automatically renew token before expiration"""
        import threading

        def renew():
            while True:
                time.sleep(3600)  # Renew every hour
                try:
                    self.client.auth.token.renew_self()
                except Exception as e:
                    logger.error(f"Token renewal failed: {e}")

        thread = threading.Thread(target=renew, daemon=True)
        thread.start()

# Usage (secure - no token in env)
vault = VaultAppRoleClient(
    role_id=os.environ['VAULT_ROLE_ID'],
    secret_id=os.environ['VAULT_SECRET_ID']
)
```

### Dynamic Database Credentials

```python
def get_dynamic_db_credentials(vault_client, role: str):
    """Get short-lived database credentials from Vault"""

    # Vault generates credentials on-demand
    response = vault_client.client.secrets.database.generate_credentials(
        name=role,
        mount_point='database'
    )

    return {
        'username': response['data']['username'],
        'password': response['data']['password'],
        'ttl': response['lease_duration']  # Credentials expire
    }

# Usage
creds = get_dynamic_db_credentials(vault, 'readonly-role')

# Connect with temporary credentials
conn = psycopg2.connect(
    host='localhost',
    database='mydb',
    user=creds['username'],
    password=creds['password']
)

# Credentials automatically expire after TTL
```

## AWS Secrets Manager

### AWS Secrets Manager Integration

```python
import boto3
import json
from botocore.exceptions import ClientError

class AWSSecretsManager:
    """AWS Secrets Manager client"""

    def __init__(self, region_name: str = 'us-east-1'):
        self.client = boto3.client(
            'secretsmanager',
            region_name=region_name
        )

    def get_secret(self, secret_name: str) -> dict:
        """Retrieve secret from AWS Secrets Manager"""
        try:
            response = self.client.get_secret_value(SecretId=secret_name)

            # Secret can be string or binary
            if 'SecretString' in response:
                return json.loads(response['SecretString'])
            else:
                import base64
                return base64.b64decode(response['SecretBinary'])

        except ClientError as e:
            if e.response['Error']['Code'] == 'ResourceNotFoundException':
                raise ValueError(f"Secret not found: {secret_name}")
            raise

    def create_secret(self, secret_name: str, secret_value: dict):
        """Create new secret"""
        self.client.create_secret(
            Name=secret_name,
            SecretString=json.dumps(secret_value)
        )

    def update_secret(self, secret_name: str, secret_value: dict):
        """Update existing secret"""
        self.client.update_secret(
            SecretId=secret_name,
            SecretString=json.dumps(secret_value)
        )

    def rotate_secret(self, secret_name: str, lambda_arn: str):
        """Enable automatic secret rotation"""
        self.client.rotate_secret(
            SecretId=secret_name,
            RotationLambdaARN=lambda_arn,
            RotationRules={'AutomaticallyAfterDays': 30}
        )

# Usage
secrets = AWSSecretsManager(region_name='us-west-2')

# Get database credentials
db_secret = secrets.get_secret('prod/database/postgres')
DATABASE_URL = (
    f"postgresql://{db_secret['username']}:{db_secret['password']}"
    f"@{db_secret['host']}/{db_secret['dbname']}"
)

# Create API key secret
secrets.create_secret('prod/api/stripe', {
    'secret_key': 'sk_live_abc123',
    'publishable_key': 'pk_live_def456'
})
```

### Caching Secrets

```python
from functools import lru_cache
import time

class CachedSecretsManager:
    """Cache secrets to reduce API calls"""

    def __init__(self, secrets_manager):
        self.secrets_manager = secrets_manager
        self.cache = {}
        self.cache_ttl = 300  # 5 minutes

    def get_secret(self, secret_name: str) -> dict:
        """Get secret with caching"""
        now = time.time()

        # Check cache
        if secret_name in self.cache:
            cached_value, cached_time = self.cache[secret_name]
            if now - cached_time < self.cache_ttl:
                return cached_value

        # Fetch from source
        value = self.secrets_manager.get_secret(secret_name)

        # Update cache
        self.cache[secret_name] = (value, now)

        return value

    def invalidate_cache(self, secret_name: str = None):
        """Clear cache for secret or all secrets"""
        if secret_name:
            self.cache.pop(secret_name, None)
        else:
            self.cache.clear()
```

## GCP Secret Manager

### Google Cloud Secret Manager

```python
from google.cloud import secretmanager

class GCPSecretsManager:
    """Google Cloud Secret Manager client"""

    def __init__(self, project_id: str):
        self.project_id = project_id
        self.client = secretmanager.SecretManagerServiceClient()

    def get_secret(self, secret_id: str, version: str = "latest") -> str:
        """Access secret version"""
        name = f"projects/{self.project_id}/secrets/{secret_id}/versions/{version}"

        response = self.client.access_secret_version(request={"name": name})

        return response.payload.data.decode('UTF-8')

    def create_secret(self, secret_id: str, secret_value: str):
        """Create new secret"""
        parent = f"projects/{self.project_id}"

        # Create secret
        secret = self.client.create_secret(
            request={
                "parent": parent,
                "secret_id": secret_id,
                "secret": {"replication": {"automatic": {}}},
            }
        )

        # Add secret version
        self.client.add_secret_version(
            request={
                "parent": secret.name,
                "payload": {"data": secret_value.encode('UTF-8')},
            }
        )

    def update_secret(self, secret_id: str, secret_value: str):
        """Add new secret version (updates secret)"""
        parent = f"projects/{self.project_id}/secrets/{secret_id}"

        self.client.add_secret_version(
            request={
                "parent": parent,
                "payload": {"data": secret_value.encode('UTF-8')},
            }
        )

# Usage
secrets = GCPSecretsManager(project_id='my-project')

# Get secret
api_key = secrets.get_secret('stripe-api-key')

# Create secret
secrets.create_secret('database-password', 'secure-password-123')
```

## Key Rotation

### Automated Key Rotation

```python
from datetime import datetime, timedelta
from typing import Callable

class KeyRotationService:
    """Automated key rotation service"""

    def __init__(self, secrets_manager, notification_service=None):
        self.secrets_manager = secrets_manager
        self.notification_service = notification_service

    def rotate_key(self, secret_name: str, generator: Callable[[], str]):
        """Rotate a secret key"""

        # Generate new key
        new_key = generator()

        # Store old key for rollback
        old_key = self.secrets_manager.get_secret(secret_name)

        try:
            # Update with new key
            self.secrets_manager.update_secret(secret_name, {'value': new_key})

            # Log rotation
            logger.info(f"Rotated secret: {secret_name}")

            # Notify team
            if self.notification_service:
                self.notification_service.send_alert(
                    f"Secret rotated: {secret_name}"
                )

            return new_key

        except Exception as e:
            # Rollback on failure
            logger.error(f"Key rotation failed: {e}")
            self.secrets_manager.update_secret(secret_name, old_key)
            raise

    def check_expiration(self, secret_name: str, max_age_days: int = 90):
        """Check if secret needs rotation"""

        metadata = self.secrets_manager.get_secret_metadata(secret_name)
        last_rotated = metadata['last_rotated']

        age = datetime.now() - last_rotated

        if age > timedelta(days=max_age_days):
            logger.warning(f"Secret needs rotation: {secret_name} (age: {age.days} days)")
            return True

        return False

# Usage
rotation_service = KeyRotationService(secrets_manager)

# Generate new API key
import secrets
def generate_api_key():
    return secrets.token_urlsafe(32)

# Rotate key
new_key = rotation_service.rotate_key('api/external-service', generate_api_key)

# Check if rotation needed
if rotation_service.check_expiration('database/password', max_age_days=30):
    rotation_service.rotate_key('database/password', generate_strong_password)
```

### Zero-Downtime Rotation

```python
class ZeroDowntimeRotation:
    """Rotate secrets without downtime"""

    def __init__(self, secrets_manager):
        self.secrets_manager = secrets_manager

    def rotate_with_overlap(self, secret_name: str, new_value: str):
        """
        Dual-key rotation:
        1. Add new key (both keys valid)
        2. Update services to use new key
        3. Remove old key after grace period
        """

        # Get current secret
        current = self.secrets_manager.get_secret(secret_name)

        # Store both old and new
        dual_secret = {
            'primary': new_value,
            'secondary': current['value'],  # Old key still valid
            'rotated_at': datetime.utcnow().isoformat()
        }

        self.secrets_manager.update_secret(secret_name, dual_secret)

        logger.info(f"New key active, old key valid for grace period")

    def complete_rotation(self, secret_name: str):
        """Remove old key after grace period"""

        secret = self.secrets_manager.get_secret(secret_name)

        # Keep only primary (new) key
        updated = {'value': secret['primary']}

        self.secrets_manager.update_secret(secret_name, updated)

        logger.info(f"Rotation completed, old key removed")

# Usage
rotator = ZeroDowntimeRotation(secrets_manager)

# Phase 1: Add new key
rotator.rotate_with_overlap('database/password', 'new-password')

# Update applications to use new key (manual or automated)
# Wait for all services to migrate (e.g., 24 hours)

# Phase 2: Remove old key
rotator.complete_rotation('database/password')
```

## CI/CD Secrets Management

### GitHub Actions Secrets

```yaml
# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2

      - name: Deploy to production
        env:
          # Access GitHub secrets
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
          API_KEY: ${{ secrets.API_KEY }}
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        run: |
          ./deploy.sh
```

### Vault Integration in CI/CD

```yaml
# .github/workflows/deploy.yml
jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2

      - name: Import Secrets from Vault
        uses: hashicorp/vault-action@v2
        with:
          url: https://vault.example.com
          token: ${{ secrets.VAULT_TOKEN }}
          secrets: |
            secret/data/database username | DATABASE_USER ;
            secret/data/database password | DATABASE_PASSWORD ;
            secret/data/api stripe_key | STRIPE_KEY

      - name: Deploy
        run: |
          echo "Using secrets from Vault"
          ./deploy.sh
```

## Security Best Practices

### Secrets Management Checklist

**Storage**:
- [ ] Never commit secrets to version control
- [ ] Use secrets manager (Vault, AWS, GCP)
- [ ] Encrypt secrets at rest and in transit
- [ ] Use separate secrets per environment
- [ ] Implement access control (least privilege)

**Rotation**:
- [ ] Rotate secrets regularly (30-90 days)
- [ ] Automate rotation where possible
- [ ] Support zero-downtime rotation
- [ ] Audit rotation events
- [ ] Have rollback plan

**Access**:
- [ ] Use short-lived credentials when possible
- [ ] Implement audit logging
- [ ] Restrict secret access by role
- [ ] Monitor secret access patterns
- [ ] Alert on unauthorized access

**Development**:
- [ ] Use different secrets for dev/staging/prod
- [ ] Never log secrets
- [ ] Sanitize secrets in error messages
- [ ] Use .gitignore for local secret files
- [ ] Document secret requirements

**Detection**:
- [ ] Scan commits for secrets (git-secrets, truffleHog)
- [ ] Monitor for exposed secrets (GitHub, public repos)
- [ ] Alert on secret usage anomalies
- [ ] Regular security audits

## Common Vulnerabilities

### Secrets in Code

```python
# ❌ VULNERABLE - Hardcoded secrets
API_KEY = "[REDACTED]"

# ❌ VULNERABLE - Secrets in comments
# Production API key: [REDACTED]

# ❌ VULNERABLE - Secrets in error messages (example of what NOT to do)
try:
    connect(password="secret123")  # Example only - never hardcode passwords
except Exception as e:
    logger.error(f"Connection failed with password: {password}")

# ✅ SECURE - Environment variables
API_KEY = os.environ['API_KEY']

# ✅ SECURE - Vault integration
API_KEY = vault.get_secret('api/stripe')['key']

# ✅ SECURE - Sanitized errors
try:
    connect(password=password)
except Exception as e:
    logger.error("Connection failed (credentials redacted)")
```

### Secrets Detection Tools

```bash
# git-secrets (prevent committing secrets)
git secrets --install
git secrets --register-aws
git secrets --scan

# truffleHog (scan repository history)
trufflehog git https://github.com/user/repo

# detect-secrets (baseline scanning)
detect-secrets scan > .secrets.baseline
detect-secrets audit .secrets.baseline

# gitleaks (fast secrets scanner)
gitleaks detect --source . --verbose
```

## Related Skills

- `security-authentication.md` - Password and credential management
- `security-authorization.md` - Access control for secrets
- `cryptography-encryption.md` - Encrypting secrets
- `cicd-github-actions.md` - CI/CD secrets management

---

**Last Updated**: 2025-10-27
**Format Version**: 1.0 (Atomic)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.