| name | secrets-management |
| description | Never commit secrets, manage credentials securely using environment variables, vaults, and Hack23 ISMS key management policy |
| license | Apache-2.0 |
Secrets Management Skill
Purpose
This skill ensures secure handling of sensitive credentials, API keys, database passwords, and cryptographic keys throughout the CIA platform's development and deployment lifecycle. It enforces zero-tolerance for hardcoded secrets and mandates proper secrets management practices.
When to Use This Skill
Apply this skill when:
- ✅ Adding new external API integrations (Riksdagen, World Bank)
- ✅ Configuring database connections
- ✅ Implementing authentication mechanisms
- ✅ Setting up CI/CD pipelines
- ✅ Deploying to new environments
- ✅ Rotating credentials after security incidents
- ✅ Reviewing code that handles configuration
Do NOT skip for:
- ❌ Development/testing credentials (still use proper secrets management)
- ❌ "Temporary" hardcoded values (they become permanent)
- ❌ Internal-only APIs (still require proper secrets management)
Golden Rules of Secrets Management
Rule #1: Never Commit Secrets to Git
ABSOLUTELY FORBIDDEN:
public class DatabaseConfig {
private static final String DB_URL = "jdbc:postgresql://prod-db.example.com:5432/cia";
private static final String DB_USERNAME = "admin";
private static final String DB_PASSWORD = "SuperSecret123!";
}
public class RiksdagenClient {
private static final String API_KEY = "sk_live_abc123def456";
}
SECURE ALTERNATIVES:
@Configuration
public class DatabaseConfig {
@Value("${spring.datasource.url}")
private String dbUrl;
@Value("${spring.datasource.username}")
private String dbUsername;
@Value("${spring.datasource.password}")
private String dbPassword;
@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(dbUrl);
config.setUsername(dbUsername);
config.setPassword(dbPassword);
return new HikariDataSource(config);
}
}
@Service
public class RiksdagenClient {
private final String apiKey;
public RiksdagenClient(@Value("${riksdagen.api.key}") String apiKey) {
this.apiKey = apiKey;
}
}
Rule #2: Use Environment-Specific Configuration
Application Properties Structure:
src/main/resources/
├── application.yml # Defaults, no secrets
├── application-dev.yml # Development config
├── application-test.yml # Test config
└── application-production.yml # Production config (secrets from env vars)
application.yml (Safe to commit):
spring:
application:
name: citizen-intelligence-agency
datasource:
url: ${DATABASE_URL:jdbc:postgresql://localhost:5432/cia_dev}
username: ${DATABASE_USERNAME:cia_user}
password: ${DATABASE_PASSWORD}
jpa:
hibernate:
ddl-auto: validate
show-sql: false
riksdagen:
api:
base-url: https://data.riksdagen.se/api
key: ${RIKSDAGEN_API_KEY}
worldbank:
api:
base-url: https://api.worldbank.org/v2
key: ${WORLDBANK_API_KEY:}
security:
jwt:
secret: ${JWT_SECRET}
expiration: 86400
application-production.yml (Also safe):
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
logging:
level:
root: WARN
com.hack23.cia: INFO
file:
name: /var/log/cia/application.log
Rule #3: Environment Variables in Deployment
Docker Compose (Development):
version: '3.8'
services:
cia-app:
image: hack23/cia:latest
environment:
DATABASE_URL: ${DATABASE_URL}
DATABASE_USERNAME: ${DATABASE_USERNAME}
DATABASE_PASSWORD: ${DATABASE_PASSWORD}
RIKSDAGEN_API_KEY: ${RIKSDAGEN_API_KEY}
JWT_SECRET: ${JWT_SECRET}
env_file:
- .env
.env.example (Safe to commit as template):
DATABASE_URL=jdbc:postgresql://localhost:5432/cia
DATABASE_USERNAME=cia_user
DATABASE_PASSWORD=CHANGE_ME
RIKSDAGEN_API_KEY=your_api_key_here
WORLDBANK_API_KEY=your_api_key_here
JWT_SECRET=generate_with_openssl_rand_base64_64
.gitignore (MUST include):
# Secrets and credentials
.env
.env.local
.env.production
*.key
*.pem
*.p12
*.jks
secrets/
credentials/
encrypt.properties
# IDE secrets
.idea/dataSources.xml
.vscode/settings.json
Rule #4: Secrets Rotation Strategy
Quarterly Rotation Schedule:
Q1: Rotate database passwords
Q2: Rotate API keys
Q3: Rotate JWT secrets
Q4: Rotate encryption keys
Rotation Process:
NEW_DB_PASSWORD=$(openssl rand -base64 32)
kubectl set env deployment/cia-app DATABASE_PASSWORD=$NEW_DB_PASSWORD
psql -h db.example.com -U admin -c "ALTER USER cia_user PASSWORD '$NEW_DB_PASSWORD';"
kubectl rollout status deployment/cia-app
echo "$(date): Rotated database password" >> /var/log/secrets-rotation.log
Secrets Detection and Prevention
Pre-Commit Hooks
Install git-secrets:
brew install git-secrets
sudo apt-get install git-secrets
cd /path/to/cia
git secrets --install
git secrets --register-aws
git secrets --add 'password\s*=\s*["\'][^"\']{8,}["\']'
git secrets --add 'apikey\s*=\s*["\'][^"\']{16,}["\']'
git secrets --add 'secret\s*=\s*["\'][^"\']{16,}["\']'
Custom Pre-Commit Hook (.git/hooks/pre-commit):
#!/bin/bash
echo "Scanning for secrets..."
if git diff --cached | grep -iE '(password|secret|api_?key|token)\s*[:=]\s*["\047][^"\047]{8,}["\047]'; then
echo "❌ ERROR: Potential secret detected in staged files!"
echo "Please remove hardcoded secrets and use environment variables."
exit 1
fi
if git diff --cached --name-only | grep -E '\.(key|pem|p12|jks)$'; then
echo "❌ ERROR: Attempting to commit key/certificate file!"
exit 1
fi
if git diff --cached --name-only | grep -E '^\.env(\.|$)'; then
echo "❌ ERROR: Attempting to commit .env file!"
exit 1
fi
echo "✅ No secrets detected"
exit 0
GitHub Actions Secret Scanning
.github/workflows/secret-scan.yml:
name: Secret Scanning
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
gitleaks:
name: Gitleaks Secret Scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}
- name: Upload SARIF report
if: failure()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
trufflehog:
name:
Secure Secrets Storage Solutions
AWS Secrets Manager (Recommended for Production)
Java Integration:
@Configuration
public class SecretsManagerConfig {
@Bean
public SecretsManagerClient secretsManagerClient() {
return SecretsManagerClient.builder()
.region(Region.EU_WEST_1)
.build();
}
}
@Service
public class SecretsService {
@Autowired
private SecretsManagerClient secretsManager;
private final Map<String, String> secretsCache = new ConcurrentHashMap<>();
public String getSecret(String secretName) {
return secretsCache.computeIfAbsent(secretName, this::fetchSecret);
}
private String fetchSecret(String secretName) {
GetSecretValueRequest request = GetSecretValueRequest.builder()
.secretId(secretName)
.build();
GetSecretValueResponse response = secretsManager.getSecretValue(request);
return response.secretString();
}
@Scheduled(fixedRate = 3600000)
public void refreshSecrets() {
secretsCache.clear();
}
}
@Configuration
public class DatabaseConfigWithSecretsManager {
SecretsService secretsService;
DataSource {
secretsService.getSecret();
(dbSecret);
secretJson.getString();
secretJson.getString();
secretJson.getString();
();
config.setJdbcUrl( + host + );
config.setUsername(username);
config.setPassword(password);
(config);
}
}
Create Secret in AWS:
aws secretsmanager create-secret \
--name cia/production/database \
--description "CIA Production Database Credentials" \
--secret-string '{
"username": "cia_prod_user",
"password": "GeneratedSecurePassword123!",
"host": "cia-prod-db.xyz.eu-west-1.rds.amazonaws.com",
"port": "5432",
"database": "cia_production"
}'
aws secretsmanager put-resource-policy \
--secret-id cia/production/database \
--resource-policy '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::123456789:role/cia-app-role"},
"Action": "secretsmanager:GetSecretValue",
"Resource": "*"
}]
}'
HashiCorp Vault (Alternative)
Java Integration:
@Configuration
public class VaultConfig {
@Bean
public VaultTemplate vaultTemplate() {
VaultEndpoint endpoint = VaultEndpoint.create("vault.example.com", 8200);
TokenAuthentication authentication = new TokenAuthentication(
System.getenv("VAULT_TOKEN")
);
SslConfiguration ssl = SslConfiguration.forTrustStore(
KeyStore.getInstance("PKCS12"),
"changeit".toCharArray()
);
return new VaultTemplate(endpoint,
new ClientHttpRequestFactoryFactory().create(
new ClientOptions(), ssl),
authentication);
}
}
@Service
public class VaultSecretsService {
@Autowired
private VaultTemplate vaultTemplate;
public String getDatabasePassword() {
VaultResponse response = vaultTemplate
.read("secret/data/cia/production/database");
(String) response.getData().get();
}
Map<String, String> {
vaultTemplate
.read();
response.getData();
}
}
Spring Cloud Config Server (Encrypted Properties)
Config Server Setup:
spring:
cloud:
config:
server:
git:
uri: https://github.com/Hack23/cia-config
search-paths: '{application}'
encrypt:
enabled: true
encrypt:
key: ${CONFIG_SERVER_ENCRYPTION_KEY}
Encrypted Properties:
spring:
datasource:
password: '{cipher}AQICAHhwKp7VkJJJJ...'
riksdagen:
api:
key: '{cipher}AQICAHhwKp7VkJJJJ...'
Encrypt secrets:
curl -X POST http://config-server:8888/encrypt \
-H "Content-Type: text/plain" \
--data-binary "MySecretPassword123"
Cryptographic Key Management
JWT Signing Keys
Key Generation:
openssl genrsa -out jwt_private.pem 4096
openssl rsa -in jwt_private.pem -pubout -out jwt_public.pem
aws secretsmanager create-secret \
--name cia/jwt/private-key \
--secret-binary fileb://jwt_private.pem
cp jwt_public.pem src/main/resources/jwt-public.pem
shred -u jwt_private.pem
JWT Configuration:
@Configuration
public class JwtConfig {
@Autowired
private SecretsService secretsService;
@Bean
public PrivateKey jwtPrivateKey() throws Exception {
String privateKeyPEM = secretsService.getSecret("cia/jwt/private-key");
privateKeyPEM = privateKeyPEM
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s", "");
byte[] encoded = Base64.getDecoder().decode(privateKeyPEM);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(keySpec);
}
@Bean
public PublicKey jwtPublicKey() throws Exception {
Resource resource = new ClassPathResource("jwt-public.pem");
String publicKeyPEM IOUtils.toString(resource.getInputStream(), StandardCharsets.UTF_8);
publicKeyPEM = publicKeyPEM
.replace(, )
.replace(, )
.replaceAll(, );
[] encoded = Base64.getDecoder().decode(publicKeyPEM);
(encoded);
KeyFactory.getInstance();
keyFactory.generatePublic(keySpec);
}
}
Database Encryption Keys
PostgreSQL TDE (Transparent Data Encryption):
openssl rand -base64 32 > /secure/location/database-encryption-key
echo "ssl = on" >> postgresql.conf
echo "ssl_cert_file = '/etc/ssl/certs/server.crt'" >> postgresql.conf
echo "ssl_key_file = '/secure/location/server.key'" >> postgresql.conf
psql -d cia -c "CREATE EXTENSION IF NOT EXISTS pgcrypto;"
Application-Level Encryption:
@Configuration
public class EncryptionConfig {
@Bean
public BytesEncryptor fieldEncryptor() {
String encryptionKey = System.getenv("FIELD_ENCRYPTION_KEY");
String salt = System.getenv("FIELD_ENCRYPTION_SALT");
return Encryptors.standard(encryptionKey, salt);
}
}
@Entity
public class Politician {
@Id
private String id;
private String firstName;
private String lastName;
@Column(name = "personal_id_encrypted")
private byte[] personalIdEncrypted;
@Autowired
@Transient
private BytesEncryptor encryptor;
@Transient
public String getPersonalId() {
if (personalIdEncrypted == null) return null;
return new String(encryptor.decrypt(personalIdEncrypted));
}
public void setPersonalId(String personalId) {
if (personalId == ) {
.personalIdEncrypted = ;
} {
.personalIdEncrypted = encryptor.encrypt(personalId.getBytes());
}
}
}
Secrets Incident Response
If Secret Compromised:
Immediate Actions (Within 1 Hour):
- ✅ Rotate the compromised secret immediately
- ✅ Revoke old secret/key from all systems
- ✅ Review access logs for unauthorized access
- ✅ Notify security team and stakeholders
- ✅ Document incident in security log
Investigation (Within 24 Hours):
- ✅ Determine how secret was exposed
- ✅ Identify all systems that used the secret
- ✅ Check for signs of unauthorized access
- ✅ Review code repository history
- ✅ Update detection mechanisms
Remediation (Within 1 Week):
- ✅ Implement additional controls to prevent recurrence
- ✅ Update security documentation
- ✅ Conduct team training on secrets management
- ✅ Add monitoring/alerting for similar incidents
- ✅ Complete incident report
Incident Response Script:
#!/bin/bash
SECRET_TYPE=$1
INCIDENT_ID=$(date +%Y%m%d-%H%M%S)
echo "=== Secrets Incident Response ==="
echo "Incident ID: $INCIDENT_ID"
echo "Secret Type: $SECRET_TYPE"
echo "Started: $(date)"
echo "Generating new secret..."
NEW_SECRET=$(openssl rand -base64 32)
echo "Updating Secrets Manager..."
aws secretsmanager update-secret \
--secret-id "cia/production/$SECRET_TYPE" \
--secret-string "$NEW_SECRET"
echo "Rotating in application..."
kubectl set env deployment/cia-app "${SECRET_TYPE^^}"="$NEW_SECRET"
echo "Verifying application health..."
kubectl wait --for=condition=available --timeout=300s deployment/cia-app
echo "Logging incident..."
echo "[$INCIDENT_ID] Rotated due to compromise" >> /var/log/security-incidents.log
ISMS Compliance Mapping
ISO 27001:2022 Controls
- A.8.4 - Access to Source Code: Secrets not in source code
- A.8.11 - Data Masking: Secrets masked in logs
- A.8.24 - Use of Cryptography: Keys managed securely
- A.5.17 - Authentication Information: Credentials protected
NIST Cybersecurity Framework
- PR.AC-1: Credentials managed and protected
- PR.DS-5: Protections against data leaks
- PR.MA-2: Remote maintenance authenticated
CIS Controls v8
- Control 3.3: Protect recovery data
- Control 3.11: Encrypt sensitive data at rest
- Control 4.7: Manage credentials
Hack23 ISMS Policy References
Secrets & Key Management Framework:
All Hack23 ISMS Policies: https://github.com/Hack23/ISMS-PUBLIC
CIA Platform Architecture References
References
Standards & Guidelines
Tools & Services