agentic-community / agentic-community/mcp-gateway-registry
feat(terraform): replace Keycloak DB password with RDS IAM authentication
- Langage dominant
- Python
- Étoiles
- 911
- Forks
- 234
- Merge moyen
- 1 j 11 h
- PR mergées (30 j)
- 62
Description
## Summary
Replace the static Keycloak↔Aurora MySQL password (rotated by Lambda from Secrets Manager) with RDS IAM database authentication via the AWS Advanced JDBC Wrapper. Eliminates static DB credentials for Keycloak entirely; the rotation Lambda remains for DocumentDB.
## Motivation
- Issue #1026 exposed rotation desync as a cause of Keycloak crashes when the password in Secrets Manager drifts from Aurora.
- Static DB passwords + 30-day rotation is more moving parts than IAM auth, which mints short-lived tokens per connection.
- `terraform/aws-ecs/keycloak-database.tf:43` currently suppresses Checkov `CKV_AWS_162` ("RDS cluster has IAM authentication enabled") with the justification that Keycloak uses password auth — this issue removes the suppression.
## Scope
In scope:
- Keycloak ECS task → Aurora MySQL connection in `terraform/aws-ecs/`
- Custom Keycloak Docker image with AWS Advanced JDBC Wrapper
Out of scope (separate issues if needed):
- DocumentDB credentials (DocumentDB does not support IAM auth)
- Registry / mcpgw application secrets
- Helm chart and Docker Compose deployments
- CDK stack in `infra/`
## Plan
### P1 — Custom Keycloak image with AWS JDBC Wrapper
Add the AWS Advanced JDBC Wrapper jar (and MySQL Connector/J as runtime dep) to `/opt/keycloak/providers/` *before* `kc.sh build` so `--optimized` mode bakes the driver in.
Files: `docker/keycloak/Dockerfile`
Pinned versions (bumped via dependabot, not `latest`):
- `aws-advanced-jdbc-wrapper` 2.3.9
- `mysql-connector-j` 8.4.0
```dockerfile
FROM quay.io/keycloak/keycloak:25.0 as builder
ENV KC_HEALTH_ENABLED=true
ENV KC_METRICS_ENABLED=true
ENV KC_FEATURES=token-exchange
ENV KC_DB=mysql
ENV KC_DB_DRIVER=software.amazon.jdbc.Driver
WORKDIR /opt/keycloak
ARG AWS_JDBC_WRAPPER_VERSION=2.3.9
ARG MYSQL_CONNECTOR_VERSION=8.4.0
ADD --chown=keycloak:keycloak \
https://github.com/awslabs/aws-advanced-jdbc-wrapper/releases/download/${AWS_JDBC_WRAPPER_VERSION}/aws-advanced-jdbc-wrapper-${AWS_JDBC_WRAPPER_VERSION}.jar \
/opt/keycloak/providers/
ADD --chown=keycloak:keycloak \
https://repo1.maven.org/maven2/com/mysql/mysql-connector-j/${MYSQL_CONNECTOR_VERSION}/mysql-connector-j-${MYSQL_CONNECTOR_VERSION}.jar \
/opt/keycloak/providers/
RUN keytool -genkeypair -storepass password -storetype PKCS12 -keyalg RSA -keysize 2048 -dname "CN=server" -alias server -ext "SAN:c=DNS:localhost,IP:127.0.0.1" -keystore conf/server.keystore
RUN /opt/keycloak/bin/kc.sh build
FROM quay.io/keycloak/keycloak:25.0
COPY --from=builder /opt/keycloak/ /opt/keycloak/
WORKDIR /opt/keycloak
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8080/health/ready || exit 1
USER keycloak
ENTRYPOINT ["/opt/keycloak/bin/kc.sh", "start", "--optimized"]
```
### P2 — Aurora cluster: enable IAM auth
Files: `terraform/aws-ecs/keycloak-database.tf`
- Set `iam_database_authentication_enabled = true`
- Remove the `CKV_AWS_162` skip comment at line 43
### P3 — Bootstrap IAM DB user
One-time SQL run via short-lived ECS task (or maintenance Lambda) using the AWS-managed master credentials:
```sql
CREATE USER 'keycloak_iam' IDENTIFIED WITH AWSAuthenticationPlugin AS 'RDS';
GRANT ALL PRIVILEGES ON keycloak.* TO 'keycloak_iam';
FLUSH PRIVILEGES;
```
### P4 — ECS task role: grant `rds-db:connect`
Files: `terraform/aws-ecs/keycloak-ecs.tf`
Resource ARN format: `arn:aws:rds-db:::dbuser:/keycloak_iam`
```hcl
resource "aws_iam_role_policy" "keycloak_task_rds_iam_auth" {
name = "keycloak-rds-iam-auth"
role = aws_iam_role.keycloak_task_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = ["rds-db:connect"]
Resource = "arn:aws:rds-db:${var.aws_region}:${data.aws_caller_identity.current.account_id}:dbuser:${aws_rds_cluster.keycloak.cluster_resource_id}/keycloak_iam"
}]
})
}
```
### P5 — Switch ECS task definition
Files: `terraform/aws-ecs/keycloak-ecs.tf` (lines 97-104)
- `KC_DB_URL` → `jdbc:aws-wrapper:mysql:///keycloak?wrapperPlugins=iam&wrapperDialect=aurora-mysql`
- `KC_DB_USERNAME` → `keycloak_iam` (plain env var, no Secrets Manager lookup)
- `KC_DB_DRIVER` → `software.amazon.jdbc.Driver`
- Add `AWS_REGION` env (wrapper needs it for token signing)
- Remove `KC_DB_PASSWORD` entry
### P6 — Delete obsolete infra
Files: `terraform/aws-ecs/{secret-rotation-config.tf, variables.tf, keycloak-database.tf, terraform.tfvars.example, README.md}`
- `aws_secretsmanager_secret.keycloak_db_secret`
- `aws_secretsmanager_secret_rotation.keycloak_db_secret` (secret-rotation-config.tf:35)
- `var.keycloak_database_password` (variables.tf:97)
- Replace `master_password = var.keycloak_database_password` (keycloak-database.tf:54) with `manage_master_user_password = true` (kept as break-glass admin only)
- README references at lines 261, 323
- `terraform.tfvars.example:96`
**Verify**: if only Keycloak used `rotate-rds` Lambda, delete `terraform/aws-ecs/lambda/rotate-rds/` and related resources too. Otherwise keep.
## Risks and mitigations
| Risk | Mitigation |
|------|------------|
| Wrapper bug / outage blocks all DB connections | Keep AWS-managed master user as break-glass; document recovery path |
| 15-min IAM token TTL exhausts on long-lived pool conns | Wrapper refreshes proactively; load-test before merge |
| Driver swap (`KC_DB_DRIVER`) requires full pod replacement | Coordinated cutover, not a rolling deploy |
| Wrapper missing AWS SDK transitive deps on classpath | Add `software.amazon.awssdk:rds` and `:auth` jars only if `NoClassDefFoundError` shows up at runtime |
## Acceptance criteria
- [ ] `docker build docker/keycloak` produces an image with both `aws-advanced-jdbc-wrapper-*.jar` and `mysql-connector-j-*.jar` under `/opt/keycloak/providers/`
- [ ] `terraform apply` succeeds with `iam_database_authentication_enabled = true` and no `CKV_AWS_162` skip
- [ ] Keycloak ECS task starts, reaches `/health/ready`, persists realms across task replacement
- [ ] `aws secretsmanager list-secrets` no longer returns `keycloak_db_secret`
- [ ] Keycloak survives a forced 20-minute idle period without connection errors (token-refresh smoke test)
- [ ] Checkov scan in CI passes without the `CKV_AWS_162` suppression
## Out of scope / follow-ups
- Mirror the change in `infra/` (CDK) — file separately
- Helm chart equivalent using IRSA — file separately if requested
- Apply same pattern to any future RDS-backed services
Guide de contribution
Ouvrir le guide de contribution
Évaluation
Cette issue n'a pas encore été évaluée.