Secrets Management
Last Updated: 2025-10-30
Status: Production
Implementation: src/secrets.py
Overview
The blog_data pipeline uses a centralized secrets management system that provides a consistent interface for all credential and secret management across the codebase.
Design Principles
- Environment Variables First - For local development convenience
- AWS Secrets Manager Fallback - For production security
- Fail Fast with Clear Errors - No silent failures
- Centralized Logic - Single source of truth in
src/secrets.py - Type Safety - Pydantic models for validation in
src/config.py - Caching - Avoid repeated API calls
- Testability - Easy to mock and test
Architecture
Components
src/
├── secrets.py # Centralized secrets management (NEW)
├── config.py # Configuration using Pydantic (UPDATED)
└── [other modules] # Use secrets.py for credentials
How It Works
# Priority order for fetching secrets:
1. Environment variable (e.g., NEO4J_URI)
2. AWS Secrets Manager (e.g., blog-data/neo4j/credentials)
3. Default value (if provided)
4. Raise error (if required=True)
Usage
Basic Usage
from src.secrets import get_secrets_manager
# Get secrets manager instance
sm = get_secrets_manager()
# Get a single secret
secret = sm.get_secret(
env_var_name="NEO4J_URI",
secret_name="blog-data/neo4j/credentials",
required=True
)
print(f"Value: {secret.value}")
print(f"Source: {secret.source}") # ENVIRONMENT or SECRETS_MANAGER
Convenience Functions
For common credentials, use the convenience functions:
from src.secrets import get_neo4j_credentials, get_cloudinary_credentials
# Get Neo4j credentials
neo4j_creds = get_neo4j_credentials()
# Returns: {"uri": "...", "username": "...", "password": "..."}
# Get Cloudinary credentials
cloudinary_creds = get_cloudinary_credentials()
# Returns: {"cloud_name": "...", "api_key": "...", "api_secret": "..."}
Using with Config
The src/config.py module uses secrets.py internally:
from src.config import get_config
config = get_config()
# Neo4j credentials automatically loaded
print(config.neo4j.uri)
print(config.neo4j.username)
# Cloudinary credentials automatically loaded
print(config.cloudinary.cloud_name)
Configuration
Local Development (.env.local)
Create a .env.local file in the project root:
# Neo4j
NEO4J_URI=neo4j+s://xxxxx.databases.neo4j.io
NEO4J_USER=neo4j
NEO4J_PASSWORD=your-password
# Cloudinary
CLOUDINARY_CLOUD_NAME=your-cloud-name
CLOUDINARY_API_KEY=your-api-key
CLOUDINARY_API_SECRET=your-api-secret
# AWS (optional for local development)
AWS_REGION=eu-west-2
AWS_PROFILE=default
Production (AWS Secrets Manager)
Secrets are stored in AWS Secrets Manager with the following structure:
Neo4j Credentials
Secret Name: blog-data/neo4j/credentials
{
"uri": "neo4j+s://xxxxx.databases.neo4j.io",
"username": "neo4j",
"password": "your-password"
}
Cloudinary Credentials
Secret Name: blog-data/cloudinary/credentials
{
"cloud_name": "your-cloud-name",
"api_key": "your-api-key",
"api_secret": "your-api-secret"
}
ECS Task Execution
In ECS, the task execution role automatically has permissions to access these secrets. No additional configuration needed.
API Reference
SecretsManager Class
class SecretsManager:
def __init__(self, region: str = "eu-west-2", profile: Optional[str] = None)
def get_secret(
self,
env_var_name: str,
secret_name: Optional[str] = None,
required: bool = True,
default: Optional[str] = None,
) -> SecretValue
def get_secret_dict(
self,
secret_name: str,
env_var_mapping: Optional[Dict[str, str]] = None,
) -> Dict[str, Any]
Helper Functions
def get_secrets_manager() -> SecretsManager
def get_neo4j_credentials() -> Dict[str, str]
def get_cloudinary_credentials() -> Dict[str, str]
Migration Guide
From Direct Environment Variables
Before:
import os
neo4j_uri = os.getenv("NEO4J_URI")
if not neo4j_uri:
raise ValueError("NEO4J_URI not set")
After:
from src.secrets import get_secrets_manager
sm = get_secrets_manager()
neo4j_uri = sm.get_secret("NEO4J_URI", "blog-data/neo4j/credentials").value
From Config Module
Before:
from src.config import get_config
config = get_config()
neo4j_uri = config.neo4j.uri # Still works!
After:
# No change needed! Config module uses secrets.py internally
from src.config import get_config
config = get_config()
neo4j_uri = config.neo4j.uri
Testing
Mocking Secrets
from unittest.mock import patch
from src.secrets import SecretValue, SecretSource
def test_with_mocked_secrets():
with patch('src.secrets.get_secrets_manager') as mock_sm:
mock_sm.return_value.get_secret.return_value = SecretValue(
value="test-value",
source=SecretSource.ENVIRONMENT
)
# Your test code here
Using Environment Variables in Tests
import os
import pytest
@pytest.fixture
def neo4j_env():
os.environ["NEO4J_URI"] = "neo4j://localhost:7687"
os.environ["NEO4J_USER"] = "neo4j"
os.environ["NEO4J_PASSWORD"] = "test"
yield
del os.environ["NEO4J_URI"]
del os.environ["NEO4J_USER"]
del os.environ["NEO4J_PASSWORD"]
def test_with_env_vars(neo4j_env):
from src.secrets import get_neo4j_credentials
creds = get_neo4j_credentials()
assert creds["uri"] == "neo4j://localhost:7687"
Troubleshooting
Secret Not Found
Error: ValueError: NEO4J_URI not found in environment or Secrets Manager
Solutions:
- Check
.env.localfile exists and contains the variable - Verify AWS credentials are configured (
aws configure) - Check IAM permissions for Secrets Manager access
- Verify secret exists in AWS Secrets Manager console
Access Denied
Error: Access denied to secret: blog-data/neo4j/credentials
Solutions:
- Check IAM role/user has
secretsmanager:GetSecretValuepermission - Verify the secret ARN in IAM policy matches the secret name
- Check if secret is in the correct AWS region
Wrong Region
Error: Secret not found but exists in AWS console
Solutions:
- Set
AWS_REGIONenvironment variable - Pass
regionparameter toSecretsManager(region="eu-west-2") - Check AWS CLI default region:
aws configure get region
Security Best Practices
- Never commit
.env.local- Already in.gitignore - Use IAM roles in ECS - No hardcoded credentials
- Rotate secrets regularly - Use AWS Secrets Manager rotation
- Limit secret access - Use least-privilege IAM policies
- Audit secret access - Enable CloudTrail logging
See Also
- Configuration Guide - Overall configuration
- Deployment Guide - Production deployment
- AWS Secrets Manager - AWS documentation