Skip to content

Commit 4512870

Browse files
committed
feat: add keycloak integration with database provisioning
Introduces Keycloak authentication service as a containerized Fargate task with dedicated database setup and automated provisioning through a new Lambda function. Establishes secure database connections with proper user roles and permissions, updates network security groups to allow inter-service communication, and configures Route53 DNS routing for the auth subdomain.
1 parent 3742a33 commit 4512870

File tree

23 files changed

+1319
-18
lines changed

23 files changed

+1319
-18
lines changed

layers/iroco2-cur-analyzer/00-variables.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ variable "environment" {
2727
variable "project_name" {
2828
type = string
2929
description = "Project's name"
30-
default = "cur"
30+
default = "keycloak"
3131
}
3232

3333
variable "project_type" {

main.tf

Lines changed: 121 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,56 @@ module "data" {
6262
depends_on = [module.network]
6363
}
6464

65+
module "rds_provisioner" {
66+
source = "./modules/lambdas/iroco2-rds-db-provisioner"
67+
68+
lambda_function_name = "${var.namespace}-${var.environment}-rds-db-provisioner"
69+
lambda_subnet_ids = module.network.private_subnet_ids
70+
lambda_security_group_id = module.network.security_group_ids["iroco2-${var.environment}-rds-lambda-provisioner"]
71+
72+
lambda_force_invoke = false
73+
74+
rds_db_driver = "postgres"
75+
rds_default_db_name = "irocalc"
76+
rds_endpoint = module.data.rds_database.db_instance_address
77+
rds_db_port = 5432
78+
rds_db_user = "iroco2"
79+
80+
rds_secret_arn = module.data.rds_database_secret_arn
81+
82+
databases = [
83+
"keycloak"
84+
]
85+
schemas = [
86+
{ name = "keycloak", database = "keycloak", owner = "keycloak_readwrite" }
87+
]
88+
roles = [
89+
{ name = "keycloak_readwrite" }
90+
]
91+
users = [
92+
{ name = "keycloak", password_arn = module.data.rds_keycloak_secret_arn, roles = ["keycloak_readwrite"] }
93+
]
94+
95+
grants = [
96+
{
97+
object_type = "DATABASE",
98+
target = "keycloak",
99+
grant_to = "keycloak",
100+
database = "keycloak",
101+
privileges = ["CONNECT"]
102+
},
103+
{
104+
object_type = "SCHEMA",
105+
target = "keycloak",
106+
grant_to = "keycloak_readwrite",
107+
database = "keycloak",
108+
privileges = ["USAGE", "CREATE"]
109+
}
110+
]
111+
112+
depends_on = [module.data]
113+
}
114+
65115
module "lambda_cur" {
66116
source = "./modules/lambdas/iroco2-cur-analyzer"
67117

@@ -80,15 +130,14 @@ module "backend_api" {
80130
cur_s3_bucket_arn = module.lambda_cur.s3_cur_bucket_arn
81131

82132
# Network variables
83-
vpc_id = module.network.vpc_id
84-
private_subnet_ids = module.network.private_subnet_ids
85-
alb_dns_name = module.network.alb_dns_name
86-
alb_zone_id = module.network.alb_zone_id
87-
alb_arn_suffix = module.network.alb_arn_suffix
88-
alb_listener_arn = module.network.alb_listener_https_arn
89-
alb_security_group_id = module.network.security_group_ids["iroco2-${var.environment}-alb"]
90-
subdomain_name = var.subdomain_name
91-
zone_name = var.zone_name
133+
vpc_id = module.network.vpc_id
134+
private_subnet_ids = module.network.private_subnet_ids
135+
alb_dns_name = module.network.alb_dns_name
136+
alb_zone_id = module.network.alb_zone_id
137+
alb_arn_suffix = module.network.alb_arn_suffix
138+
alb_listener_arn = module.network.alb_listener_https_arn
139+
subdomain_name = var.subdomain_name
140+
zone_name = var.zone_name
92141

93142
# ECS variables
94143
cluster_name = module.services.cluster.name
@@ -103,7 +152,7 @@ module "backend_api" {
103152

104153
task_container_environment = {
105154
DATABASE_NAME = module.data.rds_database.db_instance_name
106-
IROCO2_CORS_ALLOWED_ORIGINS = var.cors_allowed_origins
155+
IROCO2_CORS_ALLOWED_ORIGINS = "https://${var.subdomain_name}.${var.zone_name},http://localhost:3000"
107156
IROCO2_AWS_ANALYZER_SQS_QUEUE_NAME = module.lambda_cur.analyzer_sqs_cur_name
108157
IROCO2_AWS_SCANNER_SQS_QUEUE_NAME = module.lambda_cur.scanner_sqs_cur_name
109158
IROCO2_AWS_SQS_QUEUE_ENDPOINT = trimsuffix(module.lambda_cur.analyzer_sqs_cur_url, module.lambda_cur.analyzer_sqs_cur_name)
@@ -114,8 +163,8 @@ module "backend_api" {
114163
IROCO2_CLERK_PUBLIC_KEY = module.services.ssm_parameters["clerk_public_key"].value
115164
IROCO2_KMS_IDENTITY_KEY_ID = module.data.iroco_identity_provider_key_id.id
116165
IROCO2_KMS_IDENTITY_PUBLIC_KEY = data.aws_kms_public_key.by_id.public_key
117-
JWT_ISSUER = var.zone_name
118-
JWT_AUDIENCE = var.zone_name
166+
JWT_ISSUER = module.services.ssm_parameters["clerk_issuer"].value
167+
JWT_AUDIENCE = module.services.ssm_parameters["clerk_audience"].value
119168
}
120169
task_container_secrets_arn = {}
121170
task_container_secrets_arn_with_key = {
@@ -141,3 +190,63 @@ module "backend_api" {
141190
}
142191
}
143192
}
193+
194+
module "keycloak" {
195+
source = "./modules/fargate-task-keycloak"
196+
197+
# Global variables
198+
aws_region = var.aws_region
199+
project_name = "keycloak"
200+
environment = var.environment
201+
202+
# Network variables
203+
vpc_id = module.network.vpc_id
204+
private_subnet_ids = module.network.private_subnet_ids
205+
alb_dns_name = module.network.alb_dns_name
206+
alb_zone_id = module.network.alb_zone_id
207+
alb_arn_suffix = module.network.alb_arn_suffix
208+
alb_listener_arn = module.network.alb_listener_https_arn
209+
subdomain_name = var.subdomain_name
210+
zone_name = var.zone_name
211+
212+
# ECS variables
213+
cluster_name = module.services.cluster.name
214+
cluster_id = module.services.cluster.id
215+
container_cpu = 512
216+
container_memory = 2048
217+
container_port = 8080
218+
container_image = "quay.io/keycloak/keycloak:24.0.1"
219+
container_desired_count = var.container_desired_count
220+
container_command = ["-c", "/opt/keycloak/bin/kc.sh build --health-enabled=true && /opt/keycloak/bin/kc.sh start --http-enabled=true --hostname=$HOSTNAME"]
221+
kms_identity_key_arn = data.aws_kms_key.signing_key.arn
222+
ecs_backend_security_group_id = module.network.security_group_ids["iroco2-${var.environment}-keycloak"]
223+
224+
task_container_environment = {
225+
HOSTNAME = "auth.${var.subdomain_name}.${var.zone_name}"
226+
KC_PROXY = "edge"
227+
KC_DB_URL = "jdbc:postgresql://${module.data.rds_database.db_instance_address}:5432/keycloak"
228+
KC_DB = "postgres"
229+
KC_DB_SCHEMA = "keycloak"
230+
KC_HEALTH_ENABLED = "true"
231+
KC_HEALTH_DB = "enabled"
232+
}
233+
task_container_secrets_arn = {}
234+
task_container_secrets_arn_with_key = {
235+
KEYCLOAK_ADMIN = {
236+
arn = module.data.rds_keycloak_admin_secret_arn
237+
key = "KEYCLOAK_ADMIN"
238+
}
239+
KEYCLOAK_ADMIN_PASSWORD = {
240+
arn = module.data.rds_keycloak_admin_secret_arn
241+
key = "KEYCLOAK_ADMIN_PASSWORD"
242+
}
243+
KC_DB_USERNAME = {
244+
arn = module.data.rds_keycloak_secret_arn
245+
key = "username"
246+
}
247+
KC_DB_PASSWORD = {
248+
arn = module.data.rds_keycloak_secret_arn
249+
key = "password"
250+
}
251+
}
252+
}

modules/data/00-outputs.tf

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ output "rds_database" {
2323
value = {
2424
db_instance_arn = module.rds.db_instance_arn
2525
db_instance_endpoint = module.rds.db_instance_endpoint
26+
db_instance_address = module.rds.db_instance_address
2627
db_instance_name = module.rds.db_instance_name
2728
db_instance_port = module.rds.db_instance_port
2829
}
@@ -33,3 +34,13 @@ output "rds_database_secret_arn" {
3334
value = aws_secretsmanager_secret.rds_master_pass.arn
3435
description = "The ARN of the secret containing the RDS master password"
3536
}
37+
38+
output "rds_keycloak_secret_arn" {
39+
value = aws_secretsmanager_secret.rds_keycloak_pass.arn
40+
description = "The ARN of the secret containing the RDS master password"
41+
}
42+
43+
output "rds_keycloak_admin_secret_arn" {
44+
value = aws_secretsmanager_secret.rds_keycloak_admin_pass.arn
45+
description = "The ARN of the secret containing the RDS master password"
46+
}

modules/data/10-rds-password.tf

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,73 @@ resource "aws_secretsmanager_secret_version" "rds_master_pass" {
5151
}
5252
)
5353
}
54+
55+
# Password for Keycloak DB
56+
resource "random_password" "rds_keycloak_pass" {
57+
length = 40
58+
special = true
59+
min_special = 5
60+
override_special = "!#$%^&*()-_=+[]{}<>:?"
61+
62+
lifecycle {
63+
ignore_changes = [
64+
override_special,
65+
min_special
66+
]
67+
}
68+
}
69+
70+
# The secret for Keycloak DB
71+
resource "aws_secretsmanager_secret" "rds_keycloak_pass" {
72+
name = "${var.namespace}/${var.environment}/rds/keycloak-db-secret"
73+
74+
tags = {
75+
project = var.project_name
76+
}
77+
}
78+
79+
# Initial version for Keycloak DB
80+
resource "aws_secretsmanager_secret_version" "rds_keycloak_pass" {
81+
secret_id = aws_secretsmanager_secret.rds_keycloak_pass.id
82+
secret_string = jsonencode(
83+
{
84+
username = "keycloak"
85+
password = random_password.rds_keycloak_pass.result
86+
}
87+
)
88+
}
89+
90+
# Password for Keycloak Admin
91+
resource "random_password" "rds_keycloak_admin_pass" {
92+
length = 40
93+
special = true
94+
min_special = 5
95+
override_special = "!#$%^&*()-_=+[]{}<>:?"
96+
97+
lifecycle {
98+
ignore_changes = [
99+
override_special,
100+
min_special
101+
]
102+
}
103+
}
104+
105+
# The secret for Keycloak Admin
106+
resource "aws_secretsmanager_secret" "rds_keycloak_admin_pass" {
107+
name = "${var.namespace}/${var.environment}/rds/keycloak-admin-secret"
108+
109+
tags = {
110+
project = var.project_name
111+
}
112+
}
113+
114+
# Initial version for Keycloak Admin
115+
resource "aws_secretsmanager_secret_version" "rds_keycloak_admin_pass" {
116+
secret_id = aws_secretsmanager_secret.rds_keycloak_admin_pass.id
117+
secret_string = jsonencode(
118+
{
119+
KEYCLOAK_ADMIN = "keycloak-admin"
120+
KEYCLOAK_ADMIN_PASSWORD = random_password.rds_keycloak_admin_pass.result
121+
}
122+
)
123+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Copyright 2025 Ippon Technologies
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
# SPDX-License-Identifier: Apache-2.0
16+
17+
data "aws_route53_zone" "main" {
18+
name = var.zone_name
19+
}
20+
21+
data "aws_kms_alias" "secrets_manager" {
22+
name = "alias/aws/secretsmanager"
23+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
locals {
2+
domain_name = "${var.subdomain_name}.${var.zone_name}"
3+
4+
task_environment = [
5+
for k, v in var.task_container_environment : {
6+
name = k
7+
value = v
8+
}
9+
]
10+
task_secrets_arn = [
11+
for k, v in var.task_container_secrets_arn : {
12+
name = k
13+
valueFrom = v
14+
}
15+
]
16+
task_secrets_arn_with_key = [
17+
for k, v in var.task_container_secrets_arn_with_key : {
18+
name = k
19+
valueFrom = "${v.arn}:${v.key}::"
20+
}
21+
]
22+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Copyright 2025 Ippon Technologies
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
# SPDX-License-Identifier: Apache-2.0
16+
17+
output "ecs_task_definition" {
18+
value = aws_ecs_task_definition.keycloak
19+
description = "ECS task definition"
20+
}
21+
22+
output "ecs_service" {
23+
value = aws_ecs_service.main
24+
description = "ECS service"
25+
}
26+
27+
output "alb_target_group" {
28+
value = aws_lb_target_group.keycloak
29+
description = "ALB target group"
30+
}
31+
32+
output "alb_listener_rule" {
33+
value = aws_lb_listener_rule.keycloak
34+
description = "ALB listener rule"
35+
}

0 commit comments

Comments
 (0)