-
-
Notifications
You must be signed in to change notification settings - Fork 7
Open
Labels
documentationImprovements or additions to documentationImprovements or additions to documentation
Description
Describe the bug
We need to ensure that the CloudFormation template used to provision the EKS cluster for Microcks and Keycloak is valid, complete, and deploys correctly.
Tasks:
- Validate the CloudFormation YAML syntax using cfn-lint or AWS Console.
- Ensure all required parameters (VPC ID, Subnet IDs, IAM Roles) are well-documented or defaulted.
- Check IAM roles and policies for minimal required permissions.
- Confirm that EKS cluster and node group provisioning succeeds without manual intervention.
- Test output values (e.g., ClusterName, NodeGroupName) and ensure they’re accurate.
- Identify any potential missing dependencies (e.g., OIDC provider, IAM roles for service accounts).
- Document steps to validate the stack for future automation.
Note: This is hard-coded for validation. External variables will be added later for production.
Attach any resources that can help us understand the issue.
- Microcks-cloudformation.yaml
---
AWSTemplateFormatVersion: '2010-09-09'
Description: Deploy Microcks on Amazon EKS with Aurora PostgreSQL and DocumentDB, using Helm via Lambda.
Parameters:
ClusterName:
Type: String
Default: microcks-cluster
Description: Name of the EKS cluster
KubernetesVersion:
Type: String
Default: '1.32'
Description: Kubernetes version for EKS (e.g., 1.30, 1.31). Verify supported versions with AWS EKS documentation.
NodeInstanceType:
Type: String
Default: t3.medium
Description: Instance type for EKS node group
AllowedValues: [t3.medium, t3.large, m5.large, m5.xlarge]
NodeGroupName:
Type: String
Default: microcks-nodes
Description: Name of the EKS node group
DesiredCapacity:
Type: Number
Default: 2
Description: Desired number of nodes
MinSize:
Type: Number
Default: 1
Description: Minimum number of nodes
MaxSize:
Type: Number
Default: 3
Description: Maximum number of nodes
SubnetIds:
Type: List<AWS::EC2::Subnet::Id>
Description: Subnet IDs for EKS and Aurora. Must have internet access (public or NAT Gateway) for Lambda.
DocDBSubnetIds:
Type: List<AWS::EC2::Subnet::Id>
Description: Subnet IDs for DocumentDB
AuroraEngineVersion:
Type: String
Default: '15.5'
Description: Aurora PostgreSQL engine version (e.g., 15.5). Verify with AWS RDS documentation.
DocDBEngineVersion:
Type: String
Default: '5.0'
Description: DocumentDB engine version (e.g., 5.0). Verify with AWS DocumentDB documentation.
VpcSecurityGroupId:
Type: String
Description: Security group ID for the VPC, allowing 5432 (Aurora), 27017 (DocumentDB), and 80/443 (EKS)
AuroraMasterUsername:
Type: String
Default: microcks
Description: Master username for Aurora PostgreSQL
AuroraMasterPassword:
Type: String
Default: microcks123
NoEcho: true
Description: Master password for Aurora PostgreSQL
DocDBMasterUsername:
Type: String
Default: microcks
Description: Master username for DocumentDB
DocDBMasterPassword:
Type: String
Default: microcks123
NoEcho: true
Description: Master password for DocumentDB
MicrocksDomain:
Type: String
Default: microcks.example.com
Description: Domain for Microcks (e.g., microcks.your-domain.com or microcks.<INGRESS_IP>.nip.io)
Resources:
# EKS Cluster Role
EKSClusterRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: eks.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonEKSClusterPolicy
# EKS Cluster
EKSCluster:
Type: AWS::EKS::Cluster
Properties:
Name: !Ref ClusterName
Version: !Ref KubernetesVersion
RoleArn: !GetAtt EKSClusterRole.Arn
ResourcesVpcConfig:
SubnetIds: !Ref SubnetIds
SecurityGroupIds:
- !Ref VpcSecurityGroupId
# Node Instance Role
NodeInstanceRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
- arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
- arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
# EKS Node Group
NodeGroup:
Type: AWS::EKS::Nodegroup
DependsOn: EKSCluster
Properties:
NodegroupName: !Ref NodeGroupName
ClusterName: !Ref ClusterName
NodeRole: !GetAtt NodeInstanceRole.Arn
Subnets: !Ref SubnetIds
ScalingConfig:
DesiredSize: !Ref DesiredCapacity
MinSize: !Ref MinSize
MaxSize: !Ref MaxSize
InstanceTypes:
- !Ref NodeInstanceType
AmiType: AL2_x86_64
DiskSize: 20
# Aurora Subnet Group
MicrocksDBSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: Subnet group for Microcks Aurora
SubnetIds: !Ref SubnetIds
# Aurora PostgreSQL Cluster
MicrocksDBCluster:
Type: AWS::RDS::DBCluster
Properties:
DBClusterIdentifier: microcks-db-cluster
Engine: aurora-postgresql
EngineVersion: !Ref AuroraEngineVersion
Port: 5432
MasterUsername: !Ref AuroraMasterUsername
MasterUserPassword: !Ref AuroraMasterPassword
DBSubnetGroupName: !Ref MicrocksDBSubnetGroup
VpcSecurityGroupIds:
- !Ref VpcSecurityGroupId
ServerlessV2ScalingConfiguration:
MinCapacity: 0.5
MaxCapacity: 2
BackupRetentionPeriod: 7
EnableHttpEndpoint: true
DeletionProtection: true
# Aurora PostgreSQL Instance
MicrocksDBInstance:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceIdentifier: microcks-db-instance
DBClusterIdentifier: !Ref MicrocksDBCluster
Engine: aurora-postgresql
DBInstanceClass: db.serverless
# IAM Role for Aurora DB Setup Lambda
AuroraDBSetupRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
- arn:aws:iam::aws:policy/AmazonRDSFullAccess
- arn:aws:iam::aws:policy/SecretsManagerReadWrite
- arn:aws:iam::aws:policy/AWSLambdaVPCAccessExecutionRole
# Lambda Function to Create Microcks Database
AuroraDBSetupLambda:
Type: AWS::Lambda::Function
Properties:
FunctionName: CreateMicrocksDB
Handler: index.handler
Runtime: python3.9
Role: !GetAtt AuroraDBSetupRole.Arn
Timeout: 300
VpcConfig:
SubnetIds: !Ref SubnetIds
SecurityGroupIds:
- !Ref VpcSecurityGroupId
Environment:
Variables:
DB_HOST: !GetAtt MicrocksDBCluster.Endpoint.Address
DB_USER: !Ref AuroraMasterUsername
DB_PASS: !Ref AuroraMasterPassword
Code:
ZipFile: |
import psycopg2
import os
import json
def handler(event, context):
try:
conn = psycopg2.connect(
host=os.environ['DB_HOST'],
port=5432,
user=os.environ['DB_USER'],
password=os.environ['DB_PASS'],
dbname='postgres'
)
conn.autocommit = True
cur = conn.cursor()
cur.execute("SELECT 1 FROM pg_database WHERE datname = 'microcks_db'")
if not cur.fetchone():
cur.execute("CREATE DATABASE microcks_db")
print("Database 'microcks_db' created.")
else:
print("Database 'microcks_db' already exists.")
cur.close()
conn.close()
return {"status": "Success"}
except Exception as e:
print(f"Error: {e}")
return {"status": "Failure", "error": str(e)}
# Trigger for Aurora DB Setup Lambda
AuroraDBSetupTrigger:
Type: Custom::CreateMicrocksDB
DependsOn: MicrocksDBInstance
Properties:
ServiceToken: !GetAtt AuroraDBSetupLambda.Arn
# DocumentDB Subnet Group
MicrocksDocDBSubnetGroup:
Type: AWS::DocDB::DBSubnetGroup
Properties:
DBSubnetGroupDescription: Subnet group for Microcks DocumentDB
SubnetIds: !Ref DocDBSubnetIds
# DocumentDB Cluster
MicrocksDocDBCluster:
Type: AWS::DocDB::DBCluster
Properties:
DBClusterIdentifier: microcks-docdb-cluster
EngineVersion: !Ref DocDBEngineVersion
MasterUsername: !Ref DocDBMasterUsername
MasterUserPassword: !Ref DocDBMasterPassword
DBSubnetGroupName: !Ref MicrocksDocDBSubnetGroup
VpcSecurityGroupIds:
- !Ref VpcSecurityGroupId
DeletionProtection: true
# DocumentDB Instance
MicrocksDocDBInstance:
Type: AWS::DocDB::DBInstance
DependsOn:
- MicrocksDocDBCluster
- MicrocksDocDBSubnetGroup
Properties:
DBInstanceIdentifier: microcks-docdb-instance
DBClusterIdentifier: !Ref MicrocksDocDBCluster
DBInstanceClass: db.t3.medium
# IAM Role for Microcks Deployment Lambda
MicrocksDeploymentRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonEKSClusterPolicy
- arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
- arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
- arn:aws:iam::aws:policy/AmazonRDSReadOnlyAccess
- arn:aws:iam::aws:policy/SecretsManagerReadWrite
- arn:aws:iam::aws:policy/CloudWatchLogsFullAccess
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
- arn:aws:iam::aws:policy/AWSLambdaVPCAccessExecutionRole
Policies:
- PolicyName: EKSKubeAccess
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- eks:DescribeCluster
- eks:AccessKubernetesApi
- eks:UpdateClusterConfig
Resource: !Sub arn:aws:eks:${AWS::Region}:${AWS::AccountId}:cluster/${ClusterName}
# Lambda Function to Deploy Microcks
MicrocksDeploymentLambda:
Type: AWS::Lambda::Function
Properties:
FunctionName: DeployMicrocks
Handler: index.handler
Runtime: python3.9
Timeout: 900
Role: !GetAtt MicrocksDeploymentRole.Arn
VpcConfig:
SubnetIds: !Ref SubnetIds
SecurityGroupIds:
- !Ref VpcSecurityGroupId
Environment:
Variables:
CLUSTER_NAME: !Ref ClusterName
REGION: !Ref AWS::Region
MONGODB_URI: !Sub mongodb://${DocDBMasterUsername}:${DocDBMasterPassword}@${MicrocksDocDBCluster.Endpoint}:27017/microcks
MICROCKS_DOMAIN: !Ref MicrocksDomain
Code:
ZipFile: |
import subprocess
import os
import json
def handler(event, context):
try:
cluster_name = os.environ["CLUSTER_NAME"]
region = os.environ["REGION"]
mongo_uri = os.environ["MONGODB_URI"]
microcks_domain = os.environ["MICROCKS_DOMAIN"]
# Update kubeconfig
subprocess.run(["aws", "eks", "update-kubeconfig", "--name", cluster_name, "--region", region], check=True)
# Create namespace
subprocess.run(["kubectl", "create", "namespace", "microcks"], check=True, capture_output=True)
# Install NGINX Ingress Controller
subprocess.run(["helm", "repo", "add", "ingress-nginx", "https://kubernetes.github.io/ingress-nginx"], check=True)
subprocess.run(["helm", "repo", "update"], check=True)
subprocess.run([
"helm", "install", "ingress-nginx", "ingress-nginx/ingress-nginx",
"--namespace", "ingress-nginx", "--create-namespace",
"--set", "controller.service.type=LoadBalancer",
"--set", "controller.config.proxy-buffer-size=128k"
], check=True)
# Install cert-manager
subprocess.run(["helm", "repo", "add", "jetstack", "https://charts.jetstack.io"], check=True)
subprocess.run(["helm", "repo", "update"], check=True)
subprocess.run([
"helm", "install", "cert-manager", "jetstack/cert-manager",
"--namespace", "cert-manager", "--create-namespace",
"--set", "installCRDs=true"
], check=True)
# Create ClusterIssuer for Let's Encrypt
cluster_issuer = f"""
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@{microcks_domain}
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginx
"""
with open("/tmp/cluster-issuer.yaml", "w") as f:
f.write(cluster_issuer)
subprocess.run(["kubectl", "apply", "-f", "/tmp/cluster-issuer.yaml"], check=True)
# Create Kubernetes secret for MongoDB
subprocess.run([
"kubectl", "create", "secret", "generic", "microcks-mongodb-connection",
"-n", "microcks",
"--from-literal=username=microcks",
"--from-literal=password=microcks123"
], check=True)
# Create Microcks Helm values file
microcks_values = f"""
appName: microcks
microcks:
url: https://{microcks_domain}
env:
- name: SPRING_DATA_MONGODB_URI
value: "{mongo_uri}"
keycloak:
enabled: true
install: false
url: https://keycloak.{microcks_domain}
privateUrl: https://keycloak.{microcks_domain}
realm: microcks
client:
id: microcks
secret: dummy-secret
mongodb:
install: false
database: microcks
secretRef:
secret: microcks-mongodb-connection
usernameKey: username
passwordKey: password
ingress:
enabled: true
ingressClassName: nginx
hostname: {microcks_domain}
annotations:
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
cert-manager.io/cluster-issuer: letsencrypt-prod
tls: true
"""
with open("/tmp/microcks.yaml", "w") as f:
f.write(microcks_values)
# Install Microcks
subprocess.run(["helm", "repo", "add", "microcks", "https://microcks.io/helm/"], check=True)
subprocess.run(["helm", "repo", "update"], check=True)
subprocess.run([
"helm", "install", "microcks", "microcks/microcks",
"-n", "microcks", "--create-namespace", "-f", "/tmp/microcks.yaml"
], check=True)
# Get Ingress IP
ingress_ip = subprocess.run([
"kubectl", "get", "svc", "-n", "ingress-nginx", "ingress-nginx-controller",
"-o", "jsonpath={.status.loadBalancer.ingress[0].hostname}"
], capture_output=True, text=True).stdout.strip()
if not ingress_ip:
raise Exception("Ingress IP not available")
return {
"status": "Microcks deployed",
"ingress_ip": ingress_ip,
"microcks_url": f"https://{microcks_domain}"
}
except Exception as e:
print(f"Error: {e}")
return {"status": "Failure", "error": str(e)}
# Trigger for Microcks Deployment Lambda
MicrocksDeploymentTrigger:
Type: Custom::MicrocksDeployment
DependsOn:
- MicrocksDeploymentLambda
- AuroraDBSetupTrigger
- NodeGroup
- MicrocksDocDBInstance
Properties:
ServiceToken: !GetAtt MicrocksDeploymentLambda.Arn
Outputs:
EKSClusterName:
Description: Name of the EKS cluster
Value: !Ref ClusterName
EKSClusterEndpoint:
Description: Endpoint of the EKS cluster
Value: !GetAtt EKSCluster.Endpoint
AuroraEndpoint:
Description: Endpoint of the Aurora PostgreSQL cluster
Value: !GetAtt MicrocksDBCluster.Endpoint.Address
DocumentDBEndpoint:
Description: Endpoint of the DocumentDB cluster
Value: !GetAtt MicrocksDocDBCluster.Endpoint
MicrocksURL:
Description: URL to access Microcks
Value: !Sub https://${MicrocksDomain}
---- Keycloak-cloudformation.yaml
---
AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFormation template to deploy Keycloak on Amazon EKS with Aurora PostgreSQL and Helm via Lambda
Parameters:
ClusterName:
Type: String
Default: keycloak-cluster
Description: Name of the EKS cluster
KubernetesVersion:
Type: String
Default: '1.30'
Description: Kubernetes version for EKS
NodeInstanceType:
Type: String
Default: t3.medium
Description: Instance type for EKS node group
NodeGroupName:
Type: String
Default: keycloak-nodes
Description: Name of the EKS node group
DesiredCapacity:
Type: Number
Default: 2
Description: Desired number of nodes
MinSize:
Type: Number
Default: 1
Description: Minimum number of nodes
MaxSize:
Type: Number
Default: 3
Description: Maximum number of nodes
SubnetIds:
Type: List<AWS::EC2::Subnet::Id>
Description: Comma-separated list of subnet IDs for EKS and Aurora
VpcSecurityGroupId:
Type: String
Description: Security group ID for the VPC
EngineVersion:
Type: String
Default: '15.5'
Description: Aurora PostgreSQL engine version
AuroraMasterUsername:
Type: String
Default: microcks
Description: Master username for Aurora PostgreSQL
AuroraMasterPassword:
Type: String
Default: microcks123
NoEcho: true
Description: Master password for Aurora PostgreSQL
KeycloakAdminUser:
Type: String
Default: admin
Description: Keycloak admin username
KeycloakAdminPassword:
Type: String
Default: microcks123
NoEcho: true
Description: Keycloak admin password
KeycloakDomain:
Type: String
Default: keycloak.example.com
Description: Custom domain for Keycloak (e.g., keycloak.your-domain.com)
Resources:
# EKS Cluster Role
EKSClusterRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: eks.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonEKSClusterPolicy
Policies:
- PolicyName: KeycloakEKSFullAccessPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- eks:*
- iam:CreateRole
- iam:AttachRolePolicy
- iam:PutRolePolicy
- iam:PassRole
- iam:GetOpenIDConnectProvider
- iam:CreateOpenIDConnectProvider
- iam:GetRole
- ecr:GetAuthorizationToken
- ecr:BatchCheckLayerAvailability
- ecr:GetDownloadUrlForLayer
- ecr:BatchGetImage
- rds:CreateDBCluster
- rds:CreateDBInstance
- rds:CreateDBSubnetGroup
- rds:DescribeDBClusters
- rds:DescribeDBInstances
- rds:ModifyDBCluster
- rds:DeleteDBCluster
- ec2:DescribeSubnets
- ec2:DescribeSecurityGroups
- ec2:DescribeVpcs
Resource: '*'
# EKS Cluster
EKSCluster:
Type: AWS::EKS::Cluster
Properties:
Name: !Ref ClusterName
Version: !Ref KubernetesVersion
RoleArn: !GetAtt EKSClusterRole.Arn
ResourcesVpcConfig:
SubnetIds: !Ref SubnetIds
SecurityGroupIds:
- !Ref VpcSecurityGroupId
# Node Instance Role
NodeInstanceRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
- arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
- arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
# EKS Node Group
NodeGroup:
Type: AWS::EKS::Nodegroup
DependsOn: EKSCluster
Properties:
NodegroupName: !Ref NodeGroupName
ClusterName: !Ref ClusterName
NodeRole: !GetAtt NodeInstanceRole.Arn
Subnets: !Ref SubnetIds
ScalingConfig:
DesiredSize: !Ref DesiredCapacity
MinSize: !Ref MinSize
MaxSize: !Ref MaxSize
InstanceTypes:
- !Ref NodeInstanceType
AmiType: AL2_x86_64
DiskSize: 20
# Aurora Subnet Group
KeycloakDBSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: Subnet group for Keycloak Aurora
SubnetIds: !Ref SubnetIds
# Aurora PostgreSQL Cluster
KeycloakDBCluster:
Type: AWS::RDS::DBCluster
Properties:
DBClusterIdentifier: keycloak-db-cluster
Engine: aurora-postgresql
EngineVersion: !Ref EngineVersion
Port: 5432
MasterUsername: !Ref AuroraMasterUsername
MasterUserPassword: !Ref AuroraMasterPassword
DBSubnetGroupName: !Ref KeycloakDBSubnetGroup
VpcSecurityGroupIds:
- !Ref VpcSecurityGroupId
ServerlessV2ScalingConfiguration:
MinCapacity: 0.5
MaxCapacity: 2
BackupRetentionPeriod: 7
EnableHttpEndpoint: true
# Aurora PostgreSQL Instance
KeycloakDBInstance:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceIdentifier: keycloak-db-instance
DBClusterIdentifier: !Ref KeycloakDBCluster
Engine: aurora-postgresql
DBInstanceClass: db.serverless
# IAM Role for Aurora DB Setup Lambda
AuroraDBSetupRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
- arn:aws:iam::aws:policy/AmazonRDSFullAccess
- arn:aws:iam::aws:policy/SecretsManagerReadWrite
- arn:aws:iam::aws:policy/AWSLambdaVPCAccessExecutionRole
# Lambda Function to Create Keycloak Database
AuroraDBSetupLambda:
Type: AWS::Lambda::Function
Properties:
FunctionName: CreateKeycloakDB
Handler: index.handler
Runtime: python3.9
Role: !GetAtt AuroraDBSetupRole.Arn
Timeout: 300
VpcConfig:
SubnetIds: !Ref SubnetIds
SecurityGroupIds:
- !Ref VpcSecurityGroupId
Environment:
Variables:
DB_HOST: !GetAtt KeycloakDBCluster.Endpoint.Address
DB_USER: !Ref AuroraMasterUsername
DB_PASS: !Ref AuroraMasterPassword
Code:
ZipFile: |
import psycopg2
import os
def handler(event, context):
try:
conn = psycopg2.connect(
host=os.environ['DB_HOST'],
port=5432,
user=os.environ['DB_USER'],
password=os.environ['DB_PASS'],
dbname='postgres'
)
conn.autocommit = True
cur = conn.cursor()
cur.execute("SELECT 1 FROM pg_database WHERE datname = 'keycloak_db'")
if not cur.fetchone():
cur.execute("CREATE DATABASE keycloak_db")
print("Database 'keycloak_db' created.")
else:
print("Database 'keycloak_db' already exists.")
cur.close()
conn.close()
return { "status": "Success" }
except Exception as e:
print(f"Error: {e}")
raise
# Trigger for Aurora DB Setup Lambda
AuroraDBSetupTrigger:
Type: Custom::CreateKeycloakDB
DependsOn: KeycloakDBInstance
Properties:
ServiceToken: !GetAtt AuroraDBSetupLambda.Arn
# IAM Role for Keycloak Deployment Lambda
KeycloakDeploymentRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonEKSClusterPolicy
- arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
- arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
- arn:aws:iam::aws:policy/AmazonRDSReadOnlyAccess
- arn:aws:iam::aws:policy/SecretsManagerReadWrite
- arn:aws:iam::aws:policy/CloudWatchLogsFullAccess
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
- arn:aws:iam::aws:policy/AWSLambdaVPCAccessExecutionRole
# Lambda Function to Deploy Keycloak
KeycloakDeploymentLambda:
Type: AWS::Lambda::Function
Properties:
FunctionName: DeployKeycloak
Handler: index.handler
Runtime: python3.9
Timeout: 900
Role: !GetAtt KeycloakDeploymentRole.Arn
VpcConfig:
SubnetIds: !Ref SubnetIds
SecurityGroupIds:
- !Ref VpcSecurityGroupId
Environment:
Variables:
CLUSTER_NAME: !Ref ClusterName
REGION: !Ref AWS::Region
DB_HOST: !GetAtt KeycloakDBCluster.Endpoint.Address
DB_USER: !Ref AuroraMasterUsername
DB_PASS: !Ref AuroraMasterPassword
ADMIN_USER: !Ref KeycloakAdminUser
ADMIN_PASS: !Ref KeycloakAdminPassword
KEYCLOAK_DOMAIN: !Ref KeycloakDomain
Code:
ZipFile: |
import subprocess
import os
import json
def handler(event, context):
try:
cluster_name = os.environ["CLUSTER_NAME"]
region = os.environ["REGION"]
db_host = os.environ["DB_HOST"]
db_user = os.environ["DB_USER"]
db_pass = os.environ["DB_PASS"]
admin_user = os.environ["ADMIN_USER"]
admin_pass = os.environ["ADMIN_PASS"]
keycloak_domain = os.environ["KEYCLOAK_DOMAIN"]
# Update kubeconfig
subprocess.run(["aws", "eks", "update-kubeconfig", "--name", cluster_name, "--region", region], check=True)
# Create namespace
subprocess.run(["kubectl", "create", "namespace", "microcks"], check=True, capture_output=True)
# Install NGINX Ingress Controller
subprocess.run(["helm", "repo", "add", "ingress-nginx", "https://kubernetes.github.io/ingress-nginx"], check=True)
subprocess.run(["helm", "repo", "update"], check=True)
subprocess.run([
"helm", "install", "ingress-nginx", "ingress-nginx/ingress-nginx",
"--namespace", "ingress-nginx", "--create-namespace",
"--set", "controller.service.type=LoadBalancer",
"--set", "controller.config.proxy-buffer-size=128k"
], check=True)
# Install cert-manager
subprocess.run(["helm", "repo", "add", "jetstack", "https://charts.jetstack.io"], check=True)
subprocess.run(["helm", "repo", "update"], check=True)
subprocess.run([
"helm", "install", "cert-manager", "jetstack/cert-manager",
"--namespace", "cert-manager", "--create-namespace",
"--set", "installCRDs=true"
], check=True)
# Create ClusterIssuer for Let's Encrypt
cluster_issuer = f"""
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@{keycloak_domain}
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginx
"""
with open("/tmp/cluster-issuer.yaml", "w") as f:
f.write(cluster_issuer)
subprocess.run(["kubectl", "apply", "-f", "/tmp/cluster-issuer.yaml"], check=True)
# Create Keycloak Helm values file
keycloak_values = f"""
auth:
adminUser: {admin_user}
adminPassword: "{admin_pass}"
postgresql:
enabled: false
externalDatabase:
host: "{db_host}"
port: 5432
database: "keycloak_db"
user: "{db_user}"
password: "{db_pass}"
scheme: "postgresql"
service:
type: ClusterIP
ports:
http: 80
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1"
memory: "1Gi"
persistence:
enabled: true
storageClass: "gp2"
accessModes:
- ReadWriteOnce
size: 8Gi
ingress:
enabled: true
ingressClassName: nginx
hostname: {keycloak_domain}
annotations:
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
cert-manager.io/cluster-issuer: letsencrypt-prod
tls: true
"""
with open("/tmp/keycloak.yaml", "w") as f:
f.write(keycloak_values)
# Install Keycloak
subprocess.run(["helm", "repo", "add", "bitnami", "https://charts.bitnami.com/bitnami"], check=True)
subprocess.run(["helm", "repo", "update"], check=True)
subprocess.run([
"helm", "install", "keycloak", "bitnami/keycloak",
"-n", "microcks", "-f", "/tmp/keycloak.yaml"
], check=True)
# Get Ingress IP
ingress_ip = subprocess.run([
"kubectl", "get", "svc", "-n", "ingress-nginx", "ingress-nginx-controller",
"-o", "jsonpath={.status.loadBalancer.ingress[0].hostname}"
], capture_output=True, text=True).stdout.strip()
if not ingress_ip:
raise Exception("Ingress IP not available")
return {
"status": "Keycloak deployed",
"ingress_ip": ingress_ip,
"keycloak_url": f"https://{keycloak_domain}"
}
except Exception as e:
print(f"Error: {e}")
raise
# Trigger for Keycloak Deployment Lambda
KeycloakDeploymentTrigger:
Type: Custom::KeycloakDeployment
DependsOn:
- AuroraDBSetupTrigger
- KeycloakDeploymentLambda
Properties:
ServiceToken: !GetAtt KeycloakDeploymentLambda.Arn
Outputs:
EKSClusterName:
Description: Name of the EKS cluster
Value: !Ref ClusterName
EKSClusterEndpoint:
Description: Endpoint of the EKS cluster
Value: !GetAtt EKSCluster.Endpoint
AuroraEndpoint:
Description: Endpoint of the Aurora PostgreSQL cluster
Value: !GetAtt KeycloakDBCluster.Endpoint.Address
KeycloakURL:
Description: URL to access Keycloak
Value: !Sub https://${KeycloakDomain}
---Metadata
Metadata
Assignees
Labels
documentationImprovements or additions to documentationImprovements or additions to documentation