KeycloakFactory.java
package com.cloudforgeci.api.security;
import com.cloudforgeci.api.core.annotation.BaseFactory;
import com.cloudforge.core.annotation.DeploymentContext;
import com.cloudforge.core.annotation.SystemContext;
import com.cloudforge.core.enums.AuthMode;
import software.amazon.awscdk.services.ec2.IVpc;
import software.amazon.awscdk.services.ec2.SecurityGroup;
import software.amazon.awscdk.services.ecs.FargateService;
import software.amazon.awscdk.services.ecs.ICluster;
import software.amazon.awscdk.services.rds.IDatabaseInstance;
import software.amazon.awscdk.services.secretsmanager.ISecret;
import software.constructs.Construct;
import java.util.logging.Logger;
/**
* Keycloak SAML Bridge Factory.
*
* <p>Deploys Keycloak as a SAML Identity Provider bridge between AWS Cognito (OIDC)
* and applications that require SAML authentication (e.g., Mattermost with group sync).</p>
*
* <p><strong>Architecture:</strong></p>
* <pre>
* Mattermost ←SAML→ Keycloak ←OIDC→ Cognito User Pool
* (groups) (federation) (users)
* </pre>
*
* <p><strong>When to Use:</strong></p>
* <ul>
* <li>Application requires SAML (not OIDC)</li>
* <li>Need group synchronization / team management</li>
* <li>Want to use Cognito but app doesn't support OIDC directly</li>
* </ul>
*
* <p><strong>Configuration:</strong></p>
* <pre>
* {
* "oidcProvider": "cognito-saml",
* "authMode": "application-oidc",
* "cognitoAutoProvision": true
* }
* </pre>
*
* <p><strong>What Gets Created:</strong></p>
* <ul>
* <li>Keycloak ECS Fargate service</li>
* <li>Keycloak schema in existing PostgreSQL database</li>
* <li>ALB target group for Keycloak (auth.{domain})</li>
* <li>Cognito OIDC federation in Keycloak</li>
* <li>SAML client configuration for Mattermost</li>
* </ul>
*
* <p><strong>What Gets Reused:</strong></p>
* <ul>
* <li>Existing Cognito User Pool (created by CognitoAuthenticationFactory)</li>
* <li>Existing RDS PostgreSQL database</li>
* <li>Existing VPC, subnets, security groups</li>
* <li>Existing ECS cluster</li>
* <li>Existing ALB</li>
* </ul>
*
* <p><strong>Security:</strong></p>
* <ul>
* <li>Follows security profile settings (DEV/STAGING/PRODUCTION)</li>
* <li>Database credentials in Secrets Manager</li>
* <li>Encrypted database connections</li>
* <li>HTTPS only</li>
* </ul>
*/
public class KeycloakFactory extends BaseFactory {
private static final Logger LOG = Logger.getLogger(KeycloakFactory.class.getName());
@DeploymentContext("oidcProvider")
private String oidcProvider;
@DeploymentContext("authMode")
private AuthMode authMode;
@DeploymentContext("stackName")
private String stackName;
@DeploymentContext("domain")
private String domain;
@DeploymentContext("subdomain")
private String subdomain;
@DeploymentContext("region")
private String region;
// Reuse existing infrastructure
@SystemContext("vpc")
private IVpc vpc;
@SystemContext("cluster")
private ICluster cluster;
@SystemContext("databaseInstance")
private IDatabaseInstance database;
@SystemContext("databaseCredentialsSecret")
private ISecret databaseSecret;
@SystemContext("applicationSecurityGroup")
private SecurityGroup applicationSecurityGroup;
// Cognito endpoints (set by CognitoAuthenticationFactory)
@SystemContext("cognitoIssuer")
private String cognitoIssuer;
@SystemContext("cognitoClientId")
private String cognitoClientId;
@SystemContext("cognitoClientSecretName")
private String cognitoClientSecretName;
public KeycloakFactory(Construct scope, String id) {
super(scope, id);
}
@Override
public void create() {
// Only deploy Keycloak when oidcProvider is "cognito-saml"
if (!"cognito-saml".equals(oidcProvider)) {
LOG.info("Keycloak not needed - oidcProvider is '" + oidcProvider + "' (expected 'cognito-saml')");
return;
}
// Only deploy for application-level authentication
if (authMode != AuthMode.APPLICATION_OIDC) {
LOG.info("Keycloak not needed - authMode is '" + authMode + "' (expected 'application-oidc')");
return;
}
LOG.info("Deploying Keycloak SAML bridge for Cognito federation");
LOG.info(" Provider: cognito-saml");
LOG.info(" Auth Mode: application-oidc");
LOG.info(" Stack: " + stackName);
// Validate required infrastructure exists
validateInfrastructure();
// Deploy Keycloak
deployKeycloak();
}
private void validateInfrastructure() {
if (vpc == null) {
throw new IllegalStateException("VPC not found - ensure NetworkFactory runs before KeycloakFactory");
}
if (cluster == null) {
throw new IllegalStateException("ECS Cluster not found - ensure ComputeFactory runs before KeycloakFactory");
}
if (database == null) {
throw new IllegalStateException("Database not found - ensure DatabaseFactory runs before KeycloakFactory");
}
if (cognitoIssuer == null) {
throw new IllegalStateException("Cognito not configured - ensure CognitoAuthenticationFactory runs before KeycloakFactory");
}
LOG.info("Infrastructure validation passed:");
LOG.info(" ✓ VPC: " + vpc.getVpcId());
LOG.info(" ✓ ECS Cluster: " + cluster.getClusterName());
LOG.info(" ✓ Database: " + database.getInstanceIdentifier());
LOG.info(" ✓ Cognito Issuer: " + cognitoIssuer);
}
private void deployKeycloak() {
LOG.info("Deploying Keycloak ECS service...");
// TODO: Implementation in next step
// 1. Create Keycloak database schema
// 2. Create ECS task definition with Keycloak container
// 3. Configure environment variables for:
// - Database connection (reuse existing RDS)
// - Cognito OIDC federation
// - SAML client for Mattermost
// 4. Create Fargate service
// 5. Create ALB target group for auth.{domain}
// 6. Export SAML endpoints to SystemContext
LOG.info("Keycloak deployment placeholder - implementation continues in next iteration");
}
}