RotatingSecrets
A library and demo for zero-downtime database credential rotation in
Kubernetes environments. The rotating-secrets module
provides reusable Spring components for HikariCP and Oracle Universal
Connection Pool (UCP) that read fresh credentials from
Kubernetes-mounted secret files and seamlessly update connection pools
when passwords are rotated. The demo module is a Spring
Boot application that exercises the library.
Links
- GitHub repository
- Javadoc: rotating-secrets
- Javadoc: demo
- Apache 2.0 License
- Code of Conduct
- Claude Code Guidance
How It Works
When running in Kubernetes with a secrets manager (HashiCorp Vault, OpenBao, External Secrets Operator), database credentials can be automatically rotated. The library uses a publisher-subscriber pattern to notify connection pools when credentials change:
Secret Mounting: A secrets manager mounts credentials as files in a configurable directory (default:
/var/run/secrets/database/)Credential Monitoring:
CredentialsProviderServiceperiodically reads the secret files and detects changesPool Notification: When credentials change, all registered
UpdatableCredentialimplementations are notified:- HikariCP: Updates credentials and soft-evicts existing connections
- Oracle UCP: Updates credentials and refreshes the connection pool
Seamless Rotation: New connections automatically use updated credentials while existing connections continue unaffected until returned to the pool
Library: rotating-secrets
API
Connection Pool Support
| Feature | HikariCP | Oracle UCP |
|---|---|---|
| Default for Spring Boot | Yes | No |
| Oracle-specific features | No | Yes |
| Credential update | Via CredentialsProvider interface | Direct pool refresh |
| Connection eviction | Soft evict (graceful) | Pool refresh |
| FAN support | No | Yes |
| Application Continuity | No | Yes |
- HikariCP is the Spring Boot default and works well with any database. It’s lightweight and high-performance.
- Oracle UCP provides Oracle-specific features
essential for enterprise deployments:
- Fast Application Notification (FAN) for RAC/Data Guard failover
- Transparent Application Continuity for request replay
- Oracle Wallet integration
- Service-aware connections
Production Considerations
- Pool Tuning: Adjust pool sizes based on your workload and database capacity
- Monitoring: Both HikariCP and UCP expose metrics via JMX/MXBeans
- Fail-Fast: The application throws RuntimeException if secrets cannot be read
- RBAC: Ensure the pod has read permissions on mounted secret volumes
- FAN Events: For Oracle RAC, enable FAN in UCP configuration
- Credential Refresh Interval: Tune
k8s.secrets.refreshIntervalbased on your rotation frequency
Architecture
Component Overview
Deployment Architecture
Demo Application
Project Structure
rotating-secrets/ # Reusable library
└── src/main/java/com/maybeitssquid/rotatingsecrets/
├── UpdatableCredential.java # Interface for credential update notification
├── CredentialsProviderService.java # Reads secrets, notifies pools on change
├── CredentialRotationException.java # Exception for rotation failures
├── hikari/
│ ├── HikariCredentialsUpdater.java # HikariCP credential rotation handler
│ └── HikariDataSourceConfig.java # HikariCP configuration (primary)
└── ucp/
├── UcpCredentialsUpdater.java # Oracle UCP credential rotation handler
└── UcpDataSourceConfig.java # Oracle UCP configuration
demo/ # Spring Boot demo application
└── src/main/
├── java/com/maybeitssquid/rotatingsecrets/
│ ├── DemoRotatingSecretsApplication.java # Entry point with scheduling enabled
│ ├── DemoDatabasePollingService.java # Exercises the connection pool
│ └── DemoQueryResult.java # Query result model
└── resources/
└── application.properties # Datasource and pool configuration
Prerequisites
- Kubernetes cluster with secrets management
Configuration
application.properties
spring.application.name=RotatingSecrets
# Kubernetes secrets path (mounted by Vault Agent or CSI driver)
k8s.secrets.path=/var/run/secrets/database
k8s.secrets.refreshInterval=30000
# Common datasource settings
spring.datasource.url=jdbc:oracle:thin:@//host:1521/service
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
spring.datasource.username=myuser
spring.datasource.password=mypassword
# HikariCP settings (primary datasource)
spring.datasource.hikari.pool-name=HikariRotatingSecrets
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=2
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.connection-timeout=20000
spring.datasource.hikari.max-lifetime=1800000
# Oracle UCP settings
spring.datasource.ucp.pool-name=UCPRotatingSecrets
spring.datasource.ucp.url=${spring.datasource.url}
spring.datasource.ucp.connection-factory-class-name=${spring.datasource.driver-class-name}
spring.datasource.ucp.user=${spring.datasource.username}
spring.datasource.ucp.password=${spring.datasource.password}
spring.datasource.ucp.initial-pool-size=2
spring.datasource.ucp.min-pool-size=2
spring.datasource.ucp.max-pool-size=10
spring.datasource.ucp.connection-wait-timeout=20
spring.datasource.ucp.inactive-connection-timeout=30
spring.datasource.ucp.max-connection-reuse-time=1800
Secret Files
The demo expects these files in the secrets directory:
| File | Description |
|---|---|
username |
Database username |
password |
Database password |
Building
./gradlew buildRunning
Local Development
Create the secret files in a local directory:
mkdir -p /tmp/secrets/database
echo "myuser" > /tmp/secrets/database/username
echo "mypassword" > /tmp/secrets/database/passwordRun with the custom secrets path:
./gradlew :demo:bootRun --args='--k8s.secrets.path=/tmp/secrets/database'Kubernetes Deployment
Mount your secrets as a volume at the configured path. Example with Vault Agent:
apiVersion: v1
kind: Pod
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/agent-inject-secret-username: "database/creds/myapp"
vault.hashicorp.com/agent-inject-template-username: |
{{- with secret "database/creds/myapp" -}}
{{ .Data.username }}
{{- end }}
vault.hashicorp.com/agent-inject-secret-password: "database/creds/myapp"
vault.hashicorp.com/agent-inject-template-password: |
{{- with secret "database/creds/myapp" -}}
{{ .Data.password }}
{{- end }}
spec:
containers:
- name: app
image: rotating-secrets:latestTesting
./gradlew testTechnologies
| Component | Version |
|---|---|
| Java | 21 |
| Gradle | 9.5.1 |
| Spring Boot | 4.0.6 |
| Spring Cloud Vault | 2025.1.1 |
| HikariCP | (via Spring Boot) |
| Resilience4j | (via Spring Cloud) |
| Oracle UCP | (via oracle-jdbc) |
| Oracle JDBC | ojdbc11 |