This is an automated email from the ASF dual-hosted git repository.

lahirujayathilake pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-custos.git


The following commit(s) were added to refs/heads/master by this push:
     new 19b6f5e06 AWS Terraform configuration for network, keycloak and vault 
deployment
19b6f5e06 is described below

commit 19b6f5e06fb44007cdeaef2b1b2b2a6cd25ab1be
Author: lahiruj <[email protected]>
AuthorDate: Sun Nov 3 23:47:25 2024 -0500

    AWS Terraform configuration for network, keycloak and vault deployment
---
 deployment/terraform/aws/README.md                 |  27 ++
 deployment/terraform/aws/main.tf                   | 115 ++++++++
 deployment/terraform/aws/modules/keycloak/main.tf  | 282 +++++++++++++++++++
 .../terraform/aws/modules/keycloak/outputs.tf      |  35 +++
 .../aws/modules/keycloak/resources/Dockerfile      |  33 +++
 .../keycloak/resources/cache-ispn-jdbc-ping.xml    |  86 ++++++
 .../keycloak/resources/docker-entrypoint.sh        |  26 ++
 .../keycloak/templates/container_definition.json   | 113 ++++++++
 .../terraform/aws/modules/keycloak/variables.tf    | 231 ++++++++++++++++
 .../terraform/aws/modules/keycloak/versions.tf     |  10 +
 deployment/terraform/aws/modules/network/main.tf   |  88 ++++++
 .../terraform/aws/modules/network/outputs.tf       |  27 ++
 .../terraform/aws/modules/network/variables.tf     |  19 ++
 .../terraform/aws/modules/network/versions.tf      |  10 +
 deployment/terraform/aws/modules/vault/README.md   |   8 +
 deployment/terraform/aws/modules/vault/main.tf     | 250 +++++++++++++++++
 .../aws/modules/vault/resources/openssl-vault.cnf  |  21 ++
 .../vault/templates/install_vault_script.sh.tpl    |  75 +++++
 .../terraform/aws/modules/vault/variables.tf       | 124 +++++++++
 deployment/terraform/aws/modules/vault/versions.tf |  10 +
 deployment/terraform/aws/outputs.tf                |  52 ++++
 deployment/terraform/aws/terraform.tfvars          |  39 +++
 deployment/terraform/aws/variables.tf              | 302 +++++++++++++++++++++
 23 files changed, 1983 insertions(+)

diff --git a/deployment/terraform/aws/README.md 
b/deployment/terraform/aws/README.md
new file mode 100644
index 000000000..cea07ed81
--- /dev/null
+++ b/deployment/terraform/aws/README.md
@@ -0,0 +1,27 @@
+# Custos AWS Deployment
+
+### Create the Deployment
+
+Follow the instructions below to create the AWS deployment:
+
+1. Navigate to `airavata-custos/terraform/aws` and run the `terraform init` 
command to initialize the Terraform
+   workspace, which includes downloading the required Terraform modules and 
providers.
+2. Run `terraform plan` to preview the infrastructure changes that Terraform 
plans to make based on the configuration
+   files. This command allows you to review what will be created, updated, or 
destroyed.
+3. Run `terraform apply -auto-approve` to execute the proposed changes and 
create the resources, including the state
+   bucket and DynamoDB locking table. When this command is executed for the 
first time, the Terraform state will be
+   stored locally. To migrate the Terraform state to AWS, run `terraform init 
-force-copy` only once after the resources
+   are created.
+
+### Destroy the Deployment
+
+To safely destroy the deployment, follow these steps:
+
+1. Set the `force_destroy` parameter to `true` in the 
`terraform_state_backend` module within the `main.tf` file. This
+   step is necessary to ensure the S3 state bucket can be deleted even if it 
contains objects.
+2. Run `terraform apply -target module.terraform_state_backend -auto-approve` 
to apply this change, enabling the
+   deletion of the S3 state bucket.
+3. Run `terraform init -force-copy` to migrate the Terraform state from the S3 
bucket back to local storage. This step
+   prepares for a complete destruction of the infrastructure.
+4. Finally, run `terraform destroy -auto-approve` to remove all resources 
defined in the Terraform configuration. This
+   command deletes the deployment from AWS.
diff --git a/deployment/terraform/aws/main.tf b/deployment/terraform/aws/main.tf
new file mode 100644
index 000000000..352a1d74c
--- /dev/null
+++ b/deployment/terraform/aws/main.tf
@@ -0,0 +1,115 @@
+provider "aws" {
+  region = var.region
+}
+
+module "terraform_state_backend" {
+  source                             = "cloudposse/tfstate-backend/aws"
+  version                            = "1.4.1"
+  environment                        = var.environment
+  name                               = "tf-state"
+  namespace                          = var.namespace
+  tags                               = var.tags
+  terraform_backend_config_file_path = "./config"
+  terraform_backend_config_file_name = "backend.tf"
+  force_destroy                      = false
+}
+
+module "network" {
+  count        = var.enable_network ? 1 : 0
+  source       = "./modules/network"
+  private_cidr = var.private_cidr
+  public_cidr  = var.public_cidr
+  tags         = merge(
+    var.tags,
+    {
+      "Environment" = var.environment
+    }
+  )
+  vpc_cidr = var.vpc_cidr
+}
+
+data "aws_subnet" "selected" {
+  for_each = var.enable_network ? [] : toset(var.private_subnet_ids)
+  id       = each.value
+}
+
+locals {
+  private_subnet_ids   = var.enable_network ? 
module.network[0].private_subnet_ids : var.private_subnet_ids
+  private_subnet_cidrs = var.enable_network ? 
module.network[0].private_subnet_cidrs : [
+    for s in data.aws_subnet.selected : s.cidr_block
+  ]
+  public_subnet_ids = var.enable_network ? module.network[0].public_subnet_ids 
: var.public_subnet_ids
+  vpc_id            = var.enable_network ? module.network[0].vpc_id : 
var.vpc_id
+  rds_source_region = var.enable_network ? 
slice(module.network[0].availability_zones, 0, 1)[0] : var.rds_source_region
+}
+
+module "keycloak" {
+  source                             = "./modules/keycloak"
+  alb_certificate_arn                = var.keycloak_alb_certificate_arn
+  alb_destroy_log_bucket             = var.alb_destroy_log_bucket
+  container_cpu_units                = var.container_cpu_units
+  container_memory_limit             = var.container_memory_limit
+  container_memory_reserved          = var.container_memory_reserved
+  container_port                     = var.keycloak_container_port
+  db_backup_retention_days           = var.db_backup_retention_days
+  db_backup_window                   = var.db_backup_window
+  db_cluster_family                  = var.db_cluster_family
+  db_cluster_size                    = var.db_cluster_size
+  db_engine_version                  = var.db_engine_version
+  db_instance_type                   = var.db_instance_type
+  db_maintenance_window              = var.db_maintenance_window
+  deletion_protection                = var.deletion_protection
+  deployment_maximum_percent         = var.deployment_maximum_percent
+  deployment_minimum_healthy_percent = var.deployment_minimum_healthy_percent
+  desired_count                      = var.desired_count
+  dns_name                           = var.keycloak_dns_name
+  dns_zone_id                        = var.dns_zone_id
+  encryption_configuration           = var.encryption_configuration
+  environment                        = var.environment
+  http_redirect                      = var.http_redirect
+  http_ingress_cidr_blocks           = var.http_ingress_cidr_blocks
+  https_ingress_cidr_blocks          = var.https_ingress_cidr_blocks
+  jvm_heap_min                       = var.jvm_heap_min
+  jvm_heap_max                       = var.jvm_heap_max
+  jvm_meta_min                       = var.jvm_meta_min
+  jvm_meta_max                       = var.jvm_meta_max
+  internal                           = var.internal
+  log_retention_days                 = var.log_retention_days
+  name                               = "Keycloak"
+  namespace                          = var.namespace
+  private_subnet_ids                 = local.private_subnet_ids
+  private_subnet_cidrs               = local.private_subnet_cidrs
+  public_subnet_ids                  = local.public_subnet_ids
+  rds_source_region                  = local.rds_source_region
+  region                             = var.region
+  route_table_ids                    = var.route_table_ids
+  stickiness                         = var.stickiness
+  tags                               = var.tags
+  vpc_id                             = local.vpc_id
+}
+
+module "vault" {
+  source                    = "./modules/vault"
+  environment               = var.environment
+  region                    = var.region
+  instance_type             = var.vault_instance_type
+  vault_version             = var.vault_version
+  tags                      = var.tags
+  namespace                 = var.namespace
+  vpc_id                    = local.vpc_id
+  alb_destroy_log_bucket    = var.alb_destroy_log_bucket
+  alb_certificate_arn       = var.vault_alb_certificate_arn
+  deletion_protection       = var.deletion_protection
+  http_ingress_cidr_blocks  = var.http_ingress_cidr_blocks
+  http_redirect             = var.http_redirect
+  https_ingress_cidr_blocks = var.https_ingress_cidr_blocks
+  private_subnet_ids        = local.private_subnet_ids
+  container_port            = var.vault_container_port
+  stickiness                = var.stickiness
+  ubuntu_ami                = var.vault_ami
+  ssh_key_name              = var.ec2_ssh_key_name
+  leader_tls_servername     = var.vault_leader_tls_servername
+  secrets_manager_arn       = var.vault_secrets_manager_arn
+  min_nodes                 = var.vault_min_nodes
+  max_nodes                 = var.vault_max_nodes
+}
diff --git a/deployment/terraform/aws/modules/keycloak/main.tf 
b/deployment/terraform/aws/modules/keycloak/main.tf
new file mode 100644
index 000000000..4bd8b9f5c
--- /dev/null
+++ b/deployment/terraform/aws/modules/keycloak/main.tf
@@ -0,0 +1,282 @@
+module "label" {
+  source      = "cloudposse/label/null"
+  version     = "0.25.0"
+  environment = var.environment
+  label_order = ["namespace", "name", "environment"]
+  name        = var.name
+  namespace   = var.namespace
+  tags        = var.tags
+}
+
+resource "random_password" "db_password" {
+  length  = 30
+  special = false
+}
+
+resource "random_password" "keycloak_password" {
+  length  = 30
+  special = false
+}
+
+resource "aws_ssm_parameter" "db_password" {
+  name        = "/${var.name}/${var.environment}/DB_PASSWORD"
+  description = "RDS password for ${module.label.id}"
+  tags        = module.label.tags
+  type        = "SecureString"
+  value       = random_password.db_password.result
+}
+
+resource "aws_ssm_parameter" "keycloak_password" {
+  name        = "/${var.name}/${var.environment}/KEYCLOAK_PASSWORD"
+  description = "keycloak_admin password for ${module.label.id}"
+  #  overwrite   = true
+  tags        = module.label.tags
+  type        = "SecureString"
+  value       = random_password.keycloak_password.result
+}
+
+module "alb" {
+  source                                  = "cloudposse/alb/aws"
+  version                                 = "1.11.1"
+  alb_access_logs_s3_bucket_force_destroy = var.alb_destroy_log_bucket
+  attributes                              = ["alb"]
+  certificate_arn                         = var.alb_certificate_arn
+  deletion_protection_enabled             = var.deletion_protection
+  health_check_interval                   = 60
+  health_check_path                       = "/auth/health"
+  health_check_timeout                    = 10
+  http_ingress_cidr_blocks                = var.http_ingress_cidr_blocks
+  http_redirect                           = var.http_redirect
+  https_enabled                           = true
+  https_ingress_cidr_blocks               = var.https_ingress_cidr_blocks
+  internal                                = var.internal
+  lifecycle_rule_enabled                  = true
+  name                                    = module.label.id
+  subnet_ids                              = var.internal ? 
var.private_subnet_ids : var.public_subnet_ids
+  tags                                    = module.label.tags
+  target_group_name                       = module.label.id
+  target_group_port                       = var.container_port
+  target_group_target_type                = "ip"
+  vpc_id                                  = var.vpc_id
+  stickiness                              = var.stickiness
+}
+
+resource "aws_route53_record" "alb" {
+  zone_id = var.dns_zone_id
+  name    = var.dns_name
+  type    = "A"
+
+  alias {
+    name                   = module.alb.alb_dns_name
+    zone_id                = module.alb.alb_zone_id
+    evaluate_target_health = false
+  }
+}
+
+resource "aws_cloudwatch_log_group" "keycloak_log_group" {
+  name              = "/aws/ecs/cluster/${module.label.id}"
+  retention_in_days = var.log_retention_days
+  tags              = module.label.tags
+}
+
+resource "aws_ecs_cluster" "keycloak" {
+  name = module.label.id
+  tags = module.label.tags
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+}
+
+module "ecr" {
+  source                     = "cloudposse/ecr/aws"
+  version                    = "0.41.0"
+  encryption_configuration   = var.encryption_configuration
+  image_tag_mutability       = "MUTABLE"
+  max_image_count            = 3
+  name                       = "${var.name}-${var.environment}"
+  principals_readonly_access = [module.ecs.task_role_arn]
+  scan_images_on_push        = true
+  tags                       = module.label.tags
+}
+
+resource "aws_vpc_endpoint" "cloudwatch_logs" {
+  count               = var.internal ? 1 : 0
+  auto_accept         = true
+  private_dns_enabled = true
+  security_group_ids  = [aws_security_group.vpc_endpoints.id]
+  service_name        = "com.amazonaws.${var.region}.logs"
+  subnet_ids          = var.private_subnet_ids
+  tags                = module.label.tags
+  vpc_endpoint_type   = "Interface"
+  vpc_id              = var.vpc_id
+}
+
+resource "aws_vpc_endpoint" "ecr_api" {
+  count               = var.internal ? 1 : 0
+  auto_accept         = true
+  private_dns_enabled = true
+  security_group_ids  = [aws_security_group.vpc_endpoints.id]
+  service_name        = "com.amazonaws.${var.region}.ecr.api"
+  subnet_ids          = var.private_subnet_ids
+  tags                = module.label.tags
+  vpc_endpoint_type   = "Interface"
+  vpc_id              = var.vpc_id
+}
+
+resource "aws_vpc_endpoint" "ecr_dkr" {
+  count               = var.internal ? 1 : 0
+  auto_accept         = true
+  private_dns_enabled = true
+  security_group_ids  = [aws_security_group.vpc_endpoints.id]
+  service_name        = "com.amazonaws.${var.region}.ecr.dkr"
+  subnet_ids          = var.private_subnet_ids
+  tags                = module.label.tags
+  vpc_endpoint_type   = "Interface"
+  vpc_id              = var.vpc_id
+}
+
+resource "aws_vpc_endpoint" "s3" {
+  count           = var.internal ? 1 : 0
+  auto_accept     = true
+  route_table_ids = var.route_table_ids
+  service_name    = "com.amazonaws.${var.region}.s3"
+  tags            = module.label.tags
+  vpc_id          = var.vpc_id
+}
+
+resource "aws_vpc_endpoint" "ssm" {
+  count               = var.internal ? 1 : 0
+  auto_accept         = true
+  private_dns_enabled = true
+  security_group_ids  = [aws_security_group.vpc_endpoints.id]
+  service_name        = "com.amazonaws.${var.region}.ssm"
+  subnet_ids          = var.private_subnet_ids
+  tags                = module.label.tags
+  vpc_endpoint_type   = "Interface"
+  vpc_id              = var.vpc_id
+}
+
+resource "aws_vpc_endpoint" "ssm_messages" {
+  count               = var.internal ? 1 : 0
+  auto_accept         = true
+  private_dns_enabled = true
+  security_group_ids  = [aws_security_group.vpc_endpoints.id]
+  service_name        = "com.amazonaws.${var.region}.ssmmessages"
+  subnet_ids          = var.private_subnet_ids
+  tags                = module.label.tags
+  vpc_endpoint_type   = "Interface"
+  vpc_id              = var.vpc_id
+}
+
+resource "aws_security_group" "vpc_endpoints" {
+  name        = "vpc-endpoints"
+  description = "Allow traffic for PrivateLink endpoints"
+  vpc_id      = var.vpc_id
+
+  ingress {
+    description     = "TLS from VPC"
+    from_port       = 443
+    to_port         = 443
+    protocol        = "tcp"
+    security_groups = [module.ecs.service_security_group_id]
+  }
+
+  egress {
+    from_port   = 0
+    to_port     = 0
+    protocol    = "-1"
+    cidr_blocks = ["0.0.0.0/0"]
+  }
+
+  tags = module.label.tags
+}
+
+data "aws_caller_identity" "current" {}
+
+module "ecs" {
+  source                    = "cloudposse/ecs-alb-service-task/aws"
+  version                   = "0.74.0"
+  container_definition_json = 
templatefile("${path.module}/templates/container_definition.json", {
+    aws_account_id            = data.aws_caller_identity.current.account_id
+    container_cpu_units       = var.container_cpu_units
+    container_memory_limit    = var.container_memory_limit
+    container_memory_reserved = var.container_memory_reserved
+    db_addr                   = module.rds_cluster.endpoint
+    dns_name                  = module.alb.alb_dns_name
+    environment               = var.environment
+    image                     = 
"${module.ecr.repository_url}:${var.docker_image_tag}"
+    jvm_heap_min              = var.jvm_heap_min
+    jvm_heap_max              = var.jvm_heap_max
+    jvm_meta_min              = var.jvm_meta_min
+    jvm_meta_max              = var.jvm_meta_max
+    log_group                 = 
aws_cloudwatch_log_group.keycloak_log_group.name
+    name                      = var.name
+    region                    = var.region
+  })
+  alb_security_group                 = module.alb.security_group_id
+  container_port                     = var.container_port
+  deployment_maximum_percent         = var.deployment_maximum_percent
+  deployment_minimum_healthy_percent = var.deployment_minimum_healthy_percent
+  desired_count                      = var.desired_count
+  ecs_cluster_arn                    = aws_ecs_cluster.keycloak.arn
+  health_check_grace_period_seconds  = 600
+  ignore_changes_task_definition     = false
+  name                               = module.label.id
+  subnet_ids                         = var.private_subnet_ids
+  tags                               = module.label.tags
+  task_cpu                           = var.container_cpu_units
+  task_memory                        = var.container_memory_limit
+  use_alb_security_group             = true
+  vpc_id                             = var.vpc_id
+
+  ecs_load_balancers = [
+    {
+      container_name   = var.name
+      container_port   = var.container_port
+      elb_name         = null
+      target_group_arn = module.alb.default_target_group_arn
+    }
+  ]
+
+  depends_on = [module.alb]
+}
+
+resource "aws_security_group_rule" "jdbc_ping" {
+  type              = "ingress"
+  from_port         = 7800
+  to_port           = 7800
+  protocol          = "tcp"
+  cidr_blocks       = var.private_subnet_cidrs
+  security_group_id = module.ecs.service_security_group_id
+}
+
+
+module "rds_cluster" {
+  source                = "cloudposse/rds-cluster/aws"
+  version               = "1.9.0"
+  admin_password        = random_password.db_password.result
+  admin_user            = "keycloak"
+  allowed_cidr_blocks   = var.db_allowed_cidr_blocks
+  attributes            = ["rds"]
+  backup_window         = var.db_backup_window
+  cluster_family        = var.db_cluster_family
+  cluster_size          = var.db_cluster_size
+  copy_tags_to_snapshot = true
+  db_name               = "keycloak"
+  db_port               = 5432
+  deletion_protection   = var.deletion_protection
+  engine                = "aurora-postgresql"
+  engine_version        = var.db_engine_version
+  instance_type         = var.db_instance_type
+  maintenance_window    = var.db_maintenance_window
+  name                  = module.label.id
+  retention_period      = var.db_backup_retention_days
+  security_groups       = [module.ecs.service_security_group_id]
+  source_region         = var.rds_source_region
+  storage_encrypted     = true
+  subnets               = var.private_subnet_ids
+  tags                  = module.label.tags
+  vpc_id                = var.vpc_id
+}
diff --git a/deployment/terraform/aws/modules/keycloak/outputs.tf 
b/deployment/terraform/aws/modules/keycloak/outputs.tf
new file mode 100644
index 000000000..e1638fa1c
--- /dev/null
+++ b/deployment/terraform/aws/modules/keycloak/outputs.tf
@@ -0,0 +1,35 @@
+output "alb_dns_name" {
+  value = module.alb.alb_dns_name
+}
+
+output "alb_log_bucket" {
+  value = module.alb.access_logs_bucket_id
+}
+
+output "ecr_repo" {
+  value = module.ecr.repository_url
+}
+
+output "ecs_cluster" {
+  value = aws_ecs_cluster.keycloak.name
+}
+
+output "ecs_service" {
+  value = module.ecs.service_name
+}
+
+output "rds_cluster_endpoint" {
+  value = module.rds_cluster.endpoint
+}
+
+output "rds_cluster_reader_endpoint" {
+  value = module.rds_cluster.reader_endpoint
+}
+
+output "rds_cluster_database_name" {
+  value = module.rds_cluster.database_name
+}
+
+output "rds_cluster_master_username" {
+  value = module.rds_cluster.master_username
+}
diff --git a/deployment/terraform/aws/modules/keycloak/resources/Dockerfile 
b/deployment/terraform/aws/modules/keycloak/resources/Dockerfile
new file mode 100644
index 000000000..10b1c8308
--- /dev/null
+++ b/deployment/terraform/aws/modules/keycloak/resources/Dockerfile
@@ -0,0 +1,33 @@
+FROM quay.io/keycloak/keycloak:20.0.3 as builder
+
+ENV KC_METRICS_ENABLED=true
+ENV KC_HEALTH_ENABLED=true
+ENV KC_FEATURES=preview
+ENV KC_DB=postgres
+ENV KC_HTTP_RELATIVE_PATH=/auth
+
+COPY ./cache-ispn-jdbc-ping.xml /opt/keycloak/conf/cache-ispn-jdbc-ping.xml
+ENV KC_CACHE_CONFIG_FILE=cache-ispn-jdbc-ping.xml
+
+RUN /opt/keycloak/bin/kc.sh build
+
+FROM quay.io/keycloak/keycloak:20.0.3
+
+USER root
+RUN microdnf update -y && \
+    microdnf install -y jq && \
+    microdnf clean all
+
+USER keycloak
+COPY --from=builder /opt/keycloak /opt/keycloak
+
+WORKDIR /opt/keycloak
+
+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
+
+COPY docker-entrypoint.sh /docker-entrypoint.sh
+
+ENV FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS true
+
+EXPOSE 7800
+ENTRYPOINT ["/docker-entrypoint.sh"]
diff --git 
a/deployment/terraform/aws/modules/keycloak/resources/cache-ispn-jdbc-ping.xml 
b/deployment/terraform/aws/modules/keycloak/resources/cache-ispn-jdbc-ping.xml
new file mode 100644
index 000000000..fc2e28890
--- /dev/null
+++ 
b/deployment/terraform/aws/modules/keycloak/resources/cache-ispn-jdbc-ping.xml
@@ -0,0 +1,86 @@
+<infinispan
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+        xsi:schemaLocation="urn:infinispan:config:11.0 
http://www.infinispan.org/schemas/infinispan-config-11.0.xsd";
+        xmlns="urn:infinispan:config:11.0">
+
+    <jgroups>
+        <stack name="jdbc-ping-tcp" extends="tcp">
+
+            <JDBC_PING connection_driver="org.postgresql.Driver"
+                       connection_username="${env.KC_DB_USERNAME}" 
connection_password="${env.KC_DB_PASSWORD}"
+                       connection_url="${env.KC_DB_URL}"
+                       initialize_sql="CREATE TABLE IF NOT EXISTS JGROUPSPING 
(own_addr varchar(200) NOT NULL, bind_addr VARCHAR(200) NOT NULL, created 
timestamp NOT NULL, cluster_name varchar(200) NOT NULL, ping_data BYTEA, 
constraint PK_JGROUPSPING PRIMARY KEY (own_addr, cluster_name));"
+                       insert_single_sql="INSERT INTO JGROUPSPING (own_addr, 
bind_addr, created, cluster_name, ping_data) values 
(?,'${env.EXTERNAL_ADDR:127.0.0.1}',NOW(), ?, ?);"
+                       delete_single_sql="DELETE FROM JGROUPSPING WHERE 
own_addr=? AND cluster_name=?;"
+                       select_all_pingdata_sql="SELECT ping_data FROM 
JGROUPSPING WHERE cluster_name=?;"
+                       info_writer_sleep_time="5000"
+                       info_writer_max_writes_after_view="2"
+                       remove_all_data_on_view_change="true"
+                       stack.combine="REPLACE"
+                       stack.position="MPING"/>
+        </stack>
+    </jgroups>
+
+    <cache-container name="keycloak">
+        <transport lock-timeout="60000" stack="jdbc-ping-tcp"/>
+
+        <local-cache name="realms">
+            <encoding>
+                <key media-type="application/x-java-object"/>
+                <value media-type="application/x-java-object"/>
+            </encoding>
+            <memory max-count="10000"/>
+        </local-cache>
+        <local-cache name="users">
+            <encoding>
+                <key media-type="application/x-java-object"/>
+                <value media-type="application/x-java-object"/>
+            </encoding>
+            <memory max-count="10000"/>
+        </local-cache>
+        <distributed-cache name="sessions" owners="2">
+            <expiration lifespan="-1"/>
+        </distributed-cache>
+        <distributed-cache name="authenticationSessions" owners="2">
+            <expiration lifespan="-1"/>
+        </distributed-cache>
+        <distributed-cache name="offlineSessions" owners="2">
+            <expiration lifespan="-1"/>
+        </distributed-cache>
+        <distributed-cache name="clientSessions" owners="2">
+            <expiration lifespan="-1"/>
+        </distributed-cache>
+        <distributed-cache name="offlineClientSessions" owners="2">
+            <expiration lifespan="-1"/>
+        </distributed-cache>
+        <distributed-cache name="loginFailures" owners="2">
+            <expiration lifespan="-1"/>
+        </distributed-cache>
+        <local-cache name="authorization">
+            <encoding>
+                <key media-type="application/x-java-object"/>
+                <value media-type="application/x-java-object"/>
+            </encoding>
+            <memory max-count="10000"/>
+        </local-cache>
+        <replicated-cache name="work">
+            <expiration lifespan="-1"/>
+        </replicated-cache>
+        <local-cache name="keys">
+            <encoding>
+                <key media-type="application/x-java-object"/>
+                <value media-type="application/x-java-object"/>
+            </encoding>
+            <expiration max-idle="3600000"/>
+            <memory max-count="1000"/>
+        </local-cache>
+        <distributed-cache name="actionTokens" owners="2">
+            <encoding>
+                <key media-type="application/x-java-object"/>
+                <value media-type="application/x-java-object"/>
+            </encoding>
+            <expiration max-idle="-1" lifespan="-1" interval="300000"/>
+            <memory max-count="-1"/>
+        </distributed-cache>
+    </cache-container>
+</infinispan>
diff --git 
a/deployment/terraform/aws/modules/keycloak/resources/docker-entrypoint.sh 
b/deployment/terraform/aws/modules/keycloak/resources/docker-entrypoint.sh
new file mode 100755
index 000000000..c290a0a27
--- /dev/null
+++ b/deployment/terraform/aws/modules/keycloak/resources/docker-entrypoint.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+# ECS 1.3
+if [ -n "${ECS_CONTAINER_METADATA_URI}" ]; then
+  EXTERNAL_ADDR=$(curl -fs "${ECS_CONTAINER_METADATA_URI}" \
+    | jq -r '.Networks[0].IPv4Addresses[0]')
+fi
+
+# ECS 1.4
+if [ -n "${ECS_CONTAINER_METADATA_URI_V4}" ]; then
+  EXTERNAL_ADDR=$(curl -fs "${ECS_CONTAINER_METADATA_URI_V4}" \
+    | jq -r '.Networks[0].IPv4Addresses[0]')
+fi
+
+if [ -z "${EXTERNAL_ADDR}" ]; then
+  EXTERNAL_ADDR=127.0.0.1
+fi
+export EXTERNAL_ADDR
+
+
+if [ -z "${HOSTNAME}" ]; then
+  HOSTNAME="localhost"
+fi
+
+exec /opt/keycloak/bin/kc.sh start --optimized "$@"
+exit $?
diff --git 
a/deployment/terraform/aws/modules/keycloak/templates/container_definition.json 
b/deployment/terraform/aws/modules/keycloak/templates/container_definition.json
new file mode 100644
index 000000000..aed66f1f1
--- /dev/null
+++ 
b/deployment/terraform/aws/modules/keycloak/templates/container_definition.json
@@ -0,0 +1,113 @@
+[
+  {
+    "logConfiguration": {
+      "logDriver": "awslogs",
+      "options": {
+        "awslogs-group": "${log_group}",
+        "awslogs-region": "${region}",
+        "awslogs-stream-prefix": "${environment}"
+      }
+    },
+    "portMappings": [
+      {
+        "protocol": "tcp",
+        "containerPort": 8080
+      },
+      {
+        "protocol": "tcp",
+        "containerPort": 7800
+      },
+      {
+        "protocol": "tcp",
+        "containerPort": 9990
+      }
+    ],
+    "cpu": ${container_cpu_units},
+    "environment": [
+      {
+        "name": "KC_DB",
+        "value": "postgres"
+      },
+      {
+        "name": "KC_DB_URL",
+        "value": "jdbc:postgresql://${db_addr}:5432/keycloak"
+      },
+      {
+        "name": "KC_DB_ADDR",
+        "value": "${db_addr}"
+      },
+      {
+        "name": "KC_DB_USERNAME",
+        "value": "keycloak"
+      },
+      {
+        "name": "DNS_NAME",
+        "value": "${dns_name}"
+      },
+      {
+        "name": "ENVIRONMENT_NAME",
+        "value": "${environment}"
+      },
+      {
+        "name": "JAVA_OPTS",
+        "value": "-XX:+DisableExplicitGC -XX:+UseG1GC -Xms${jvm_heap_min}m 
-Xmx${jvm_heap_max}m -XX:MetaspaceSize=${jvm_meta_min}m 
-XX:MaxMetaspaceSize=${jvm_meta_max}m -Djava.net.preferIPv4Stack=true 
-Djava.net.preferIPv4Addresses -Djava.security.egd=file:/dev/urandom 
-Dnashorn.args=--no-deprecation-warning"
+      },
+      {
+        "name": "KEYCLOAK_ADMIN",
+        "value": "keycloak_admin"
+      },
+      {
+        "name": "KC_LOG_LEVEL",
+        "value": "INFO,org.infinispan:ERROR,org.jgroups:ERROR"
+      },
+      {
+        "name": "KC_HTTP_ENABLED",
+        "value": "true"
+      },
+      {
+        "name": "KC_PROXY",
+        "value": "edge"
+      },
+      {
+        "name": "KC_HOSTNAME_STRICT",
+        "value": "false"
+      },
+      {
+        "name": "KC_HOSTNAME_PATH",
+        "value": "/auth"
+      },
+      {
+        "name": "KC_HTTP_RELATIVE_PATH",
+        "value": "/auth"
+      },
+      {
+          "name": "ROOT_LOGLEVEL",
+          "value": "INFO"
+      }
+    ],
+    "secrets": [
+      {
+        "name": "KC_DB_PASSWORD",
+        "valueFrom": 
"arn:aws:ssm:${region}:${aws_account_id}:parameter/${name}/${environment}/DB_PASSWORD"
+      },
+      {
+        "name": "KEYCLOAK_ADMIN_PASSWORD",
+        "valueFrom": 
"arn:aws:ssm:${region}:${aws_account_id}:parameter/${name}/${environment}/KEYCLOAK_PASSWORD"
+      }
+    ],
+    "memory": ${container_memory_limit},
+    "memoryReservation": ${container_memory_reserved},
+    "stopTimeout": 20,
+    "image": "${image}",
+    "startTimeout": 30,
+    "healthCheck": null,
+    "essential": true,
+    "readonlyRootFilesystem": false,
+    "dockerLabels": {
+      "environment": "${environment}",
+      "service": "${name}"
+    },
+    "privileged": false,
+    "name": "${name}"
+  }
+]
diff --git a/deployment/terraform/aws/modules/keycloak/variables.tf 
b/deployment/terraform/aws/modules/keycloak/variables.tf
new file mode 100644
index 000000000..549ead70a
--- /dev/null
+++ b/deployment/terraform/aws/modules/keycloak/variables.tf
@@ -0,0 +1,231 @@
+variable "alb_certificate_arn" {
+  description = "ACM certificate ARN used by ALB"
+  type        = string
+}
+
+variable "alb_destroy_log_bucket" {
+  description = "Destroy ALB log bucket on teardown"
+  type        = bool
+}
+
+variable "rds_source_region" {
+  description = "Region of primary RDS cluster (needed to support encryption)"
+  type        = string
+}
+
+variable "container_cpu_units" {
+  description = "CPU units to reserve for container (1024 units == 1 CPU)"
+  type        = number
+}
+
+variable "container_memory_limit" {
+  description = "Container memory hard limit"
+  type        = number
+}
+
+variable "container_memory_reserved" {
+  description = "Container memory starting reservation"
+  type        = number
+}
+
+variable "container_port" {
+  description = "Keycloak port exposed in container"
+  type        = number
+}
+
+variable "db_allowed_cidr_blocks" {
+  description = "List of CIDR blocks allowed to access DB cluster"
+  type        = list(string)
+  default     = []
+}
+
+variable "db_backup_retention_days" {
+  description = "How long Database backups are retained"
+  type        = number
+}
+
+variable "db_backup_window" {
+  description = "Daily time range during which backups happen"
+  type        = string
+}
+
+variable "db_cluster_family" {
+  description = "Family of DB cluster parameter group"
+  type        = string
+}
+
+variable "db_cluster_size" {
+  description = "Number of RDS cluster instances"
+  type        = number
+}
+
+variable "db_engine_version" {
+  description = "Version of DB engine to use"
+  type        = string
+}
+
+variable "db_instance_type" {
+  description = "Instance type used for RDS instances"
+  type        = string
+}
+
+variable "db_maintenance_window" {
+  description = "Weekly time range during which system maintenance can occur 
(UTC)"
+  type        = string
+}
+
+variable "deletion_protection" {
+  description = "Protect resources from being deleted"
+  type        = bool
+}
+
+variable "deployment_maximum_percent" {
+  description = "Maximum task instances allowed to run"
+  type        = number
+}
+
+variable "deployment_minimum_healthy_percent" {
+  description = "Minimum percentage of healthy task instances"
+  type        = number
+}
+
+variable "desired_count" {
+  description = "Number of ECS task instances to run"
+  type        = number
+}
+
+variable "dns_name" {
+  description = "Keycloak DNS"
+  type        = string
+}
+
+variable "dns_zone_id" {
+  description = "Route53 Zone ID hosting Keycloak"
+  type        = string
+}
+
+variable "encryption_configuration" {
+  type = object({
+    encryption_type = string
+    kms_key         = any
+  })
+  description = "ECR encryption configuration"
+}
+
+variable "environment" {
+  description = "Environment name (development, production, etc)"
+  type        = string
+}
+
+variable "http_redirect" {
+  description = "Controls whether port 80 should redirect to 443 (or not 
listen)"
+  type        = bool
+}
+
+variable "http_ingress_cidr_blocks" {
+  description = "CIDR ranges allowed to connect to service port 80"
+  type        = list(string)
+}
+
+variable "https_ingress_cidr_blocks" {
+  description = "CIDR ranges allowed to connect to service port 443"
+  type        = list(string)
+}
+
+variable "internal" {
+  description = "Whether environment should be exposed to Internet (if not 
using network module)"
+  type        = bool
+}
+
+variable "jvm_heap_min" {
+  description = "Minimum JVM heap size for application in MB"
+  type        = number
+}
+
+variable "jvm_heap_max" {
+  description = "Maximum JVM heap size for application in MB"
+  type        = number
+}
+
+variable "jvm_meta_min" {
+  description = "Minimum JVM meta space size for application in MB"
+  type        = number
+}
+
+variable "jvm_meta_max" {
+  description = "Maximum JVM meta space size for application in MB"
+  type        = number
+}
+
+variable "log_retention_days" {
+  description = "Log retention for CloudWatch logs"
+  type        = number
+}
+
+variable "name" {
+  description = "Used by modules to construct labels"
+  type        = string
+}
+
+variable "namespace" {
+  description = "Used by modules to construct labels"
+  type        = string
+}
+
+variable "private_subnet_ids" {
+  description = "List of private subnet IDs"
+  type        = list(string)
+}
+
+variable "private_subnet_cidrs" {
+  description = "List of private subnet CIDR ranges"
+  type        = list(string)
+}
+
+variable "public_subnet_ids" {
+  description = "List of public subnet IDs"
+  type        = list(string)
+}
+
+variable "region" {
+  description = "AWS region to target"
+  type        = string
+}
+
+variable "route_table_ids" {
+  description = "List of route tables used by s3 VPC endpoint (if not using 
network module)"
+  type        = list(string)
+}
+
+variable "stickiness" {
+  type = object({
+    cookie_duration = number
+    enabled         = bool
+  })
+  description = "Target group sticky configuration"
+}
+
+variable "tags" {
+  description = "Default tags applied to resources"
+  type        = map(string)
+}
+
+variable "vpc_id" {
+  description = "AWS VPC ID"
+  type        = string
+}
+
+variable "ecr_repository_name" {
+  description = "Name of the ECR Repository"
+  default = "keycloak"
+}
+
+variable "docker_image_name" {
+  description = "Name of the Docker Image"
+  default = "keycloak-custos"
+}
+
+variable "docker_image_tag" {
+  description = "The Version Tag for the Docker Image"
+  default = "latest"
+}
diff --git a/deployment/terraform/aws/modules/keycloak/versions.tf 
b/deployment/terraform/aws/modules/keycloak/versions.tf
new file mode 100644
index 000000000..21c24a6e3
--- /dev/null
+++ b/deployment/terraform/aws/modules/keycloak/versions.tf
@@ -0,0 +1,10 @@
+terraform {
+  required_version = ">= 0.15.3"
+
+  required_providers {
+    aws = {
+      source  = "hashicorp/aws"
+      version = ">= 4.0"
+    }
+  }
+}
diff --git a/deployment/terraform/aws/modules/network/main.tf 
b/deployment/terraform/aws/modules/network/main.tf
new file mode 100644
index 000000000..d112eaf85
--- /dev/null
+++ b/deployment/terraform/aws/modules/network/main.tf
@@ -0,0 +1,88 @@
+data "aws_availability_zones" "available" {
+  state = "available"
+}
+
+resource "aws_vpc" "main" {
+  cidr_block = var.vpc_cidr
+  tags       = var.tags
+}
+
+resource "aws_internet_gateway" "main" {
+  vpc_id = aws_vpc.main.id
+  tags   = var.tags
+}
+
+resource "aws_nat_gateway" "main" {
+  count         = 2
+  subnet_id     = element(aws_subnet.public[*].id, count.index)
+  allocation_id = element(aws_eip.nat[*].id, count.index)
+  depends_on    = [aws_internet_gateway.main]
+  tags          = var.tags
+}
+
+resource "aws_eip" "nat" {
+  count      = 2
+  domain     = "vpc"
+  depends_on = [aws_internet_gateway.main]
+  tags       = var.tags
+}
+
+resource "aws_route_table" "public" {
+  vpc_id = aws_vpc.main.id
+  tags   = var.tags
+
+  route {
+    cidr_block = "0.0.0.0/0"
+    gateway_id = aws_internet_gateway.main.id
+  }
+}
+
+resource "aws_route_table" "private" {
+  count  = 2
+  vpc_id = aws_vpc.main.id
+  tags   = var.tags
+
+  route {
+    cidr_block     = "0.0.0.0/0"
+    nat_gateway_id = element(aws_nat_gateway.main[*].id, count.index)
+  }
+}
+
+resource "aws_route_table_association" "public" {
+  count          = 2
+  subnet_id      = element(aws_subnet.public[*].id, count.index)
+  route_table_id = aws_route_table.public.id
+}
+
+resource "aws_route_table_association" "private" {
+  count          = 2
+  subnet_id      = element(aws_subnet.private[*].id, count.index)
+  route_table_id = element(aws_route_table.private[*].id, count.index)
+}
+
+resource "aws_subnet" "public" {
+  count                   = 2
+  vpc_id                  = aws_vpc.main.id
+  cidr_block              = cidrsubnet(var.public_cidr, 1, count.index)
+  availability_zone       = 
element(data.aws_availability_zones.available.names, count.index)
+  map_public_ip_on_launch = true
+  tags                    = merge(
+    var.tags,
+    {
+      "Name" = "public-subnet-custos-${format("%02d", count.index + 1)}"
+    }
+  )
+}
+
+resource "aws_subnet" "private" {
+  count             = 2
+  vpc_id            = aws_vpc.main.id
+  cidr_block        = cidrsubnet(var.private_cidr, 1, count.index)
+  availability_zone = element(data.aws_availability_zones.available.names, 
count.index)
+  tags              = merge(
+    var.tags,
+    {
+      "Name" = "private-subnet-custos-${format("%02d", count.index + 1)}"
+    }
+  )
+}
diff --git a/deployment/terraform/aws/modules/network/outputs.tf 
b/deployment/terraform/aws/modules/network/outputs.tf
new file mode 100644
index 000000000..e7159a238
--- /dev/null
+++ b/deployment/terraform/aws/modules/network/outputs.tf
@@ -0,0 +1,27 @@
+output "vpc_id" {
+  value = aws_vpc.main.id
+}
+
+output "vpc_cidr" {
+  value = aws_vpc.main.cidr_block
+}
+
+output "public_subnet_cidrs" {
+  value = aws_subnet.public[*].cidr_block
+}
+
+output "public_subnet_ids" {
+  value = aws_subnet.public[*].id
+}
+
+output "private_subnet_cidrs" {
+  value = aws_subnet.private[*].cidr_block
+}
+
+output "private_subnet_ids" {
+  value = aws_subnet.private[*].id
+}
+
+output "availability_zones" {
+  value = data.aws_availability_zones.available.names
+}
\ No newline at end of file
diff --git a/deployment/terraform/aws/modules/network/variables.tf 
b/deployment/terraform/aws/modules/network/variables.tf
new file mode 100644
index 000000000..29c9777ca
--- /dev/null
+++ b/deployment/terraform/aws/modules/network/variables.tf
@@ -0,0 +1,19 @@
+variable "vpc_cidr" {
+  description = "RFC1918 CIDR range for VPC"
+  type        = string
+}
+
+variable "public_cidr" {
+  description = "RFC1918 CIDR range for public subnets (subset of vpc_cidr)"
+  type        = string
+}
+
+variable "private_cidr" {
+  description = "RFC1918 CIDR range for private subnets (subset of vpc_cidr)"
+  type        = string
+}
+
+variable "tags" {
+  description = "Tags applied to AWS resources"
+  type        = map(string)
+}
diff --git a/deployment/terraform/aws/modules/network/versions.tf 
b/deployment/terraform/aws/modules/network/versions.tf
new file mode 100644
index 000000000..21c24a6e3
--- /dev/null
+++ b/deployment/terraform/aws/modules/network/versions.tf
@@ -0,0 +1,10 @@
+terraform {
+  required_version = ">= 0.15.3"
+
+  required_providers {
+    aws = {
+      source  = "hashicorp/aws"
+      version = ">= 4.0"
+    }
+  }
+}
diff --git a/deployment/terraform/aws/modules/vault/README.md 
b/deployment/terraform/aws/modules/vault/README.md
new file mode 100644
index 000000000..4d88758af
--- /dev/null
+++ b/deployment/terraform/aws/modules/vault/README.md
@@ -0,0 +1,8 @@
+# Vault Configurations
+
+### Generating the Self Signed Certificate (Development Purpose Only)
+- Navigate to the `airavata-custos/terraform/modules/vault/resources` and run 
the command
+`openssl req -x509 -nodes -newkey rsa:2048 -keyout vault.key -out vault.crt 
-days 365 -config openssl-vault.cnf -extensions req_ext` 
+to generate the `vault.cert` and `vault.key`
+- Create a Secret using AWS Secrets Manager and copy the `vault.cert` content 
into the `vault_cert` and `vault_ca`. The `vault.key` content should go to the 
key `vault_pk`
+Then use the corresponding ARN value for variable `vault_secrets_manager_arn` 
in `terraform.vars`
\ No newline at end of file
diff --git a/deployment/terraform/aws/modules/vault/main.tf 
b/deployment/terraform/aws/modules/vault/main.tf
new file mode 100644
index 000000000..9945edb13
--- /dev/null
+++ b/deployment/terraform/aws/modules/vault/main.tf
@@ -0,0 +1,250 @@
+module "label" {
+  source      = "cloudposse/label/null"
+  version     = "0.25.0"
+  environment = var.environment
+  label_order = ["namespace", "name", "environment"]
+  name        = var.name
+  namespace   = var.namespace
+  tags        = var.tags
+}
+
+resource "aws_iam_role" "instance_role" {
+  name               = "${var.namespace}-${var.name}"
+  description        = "Vault role for ${module.label.id}"
+  assume_role_policy = data.aws_iam_policy_document.instance_role.json
+}
+
+data "aws_iam_policy_document" "instance_role" {
+  statement {
+    effect  = "Allow"
+    actions = [
+      "sts:AssumeRole",
+    ]
+    principals {
+      type        = "Service"
+      identifiers = ["ec2.amazonaws.com"]
+    }
+  }
+}
+
+resource "aws_iam_instance_profile" "vault" {
+  name_prefix = "${var.namespace}-${var.name}"
+  role        = aws_iam_role.instance_role.name
+  tags        = module.label.tags
+}
+
+resource "aws_iam_role_policy" "auto_join" {
+  name   = "${var.namespace}-${var.name}-auto-join"
+  policy = data.aws_iam_policy_document.auto_join.json
+  role   = aws_iam_role.instance_role.id
+}
+
+data "aws_iam_policy_document" "auto_join" {
+  statement {
+    effect  = "Allow"
+    actions = [
+      "ec2:DescribeInstances",
+      "ec2:DescribeTags"
+    ]
+    resources = ["*"]
+  }
+}
+
+resource "aws_iam_role_policy" "auto_unseal" {
+  name   = "${var.namespace}-${var.name}-auto-unseal"
+  policy = data.aws_iam_policy_document.auto_unseal.json
+  role   = aws_iam_role.instance_role.id
+}
+
+resource "aws_kms_key" "vault" {
+  description             = "AWS KMS key used for Vault auto-unseal and 
encryption"
+  key_usage               = "ENCRYPT_DECRYPT"
+  deletion_window_in_days = 7
+  is_enabled              = true
+  tags                    = merge(
+    var.tags,
+    {
+      "Name" = "${var.namespace}-${var.name}-key"
+    }
+  )
+}
+
+data "aws_iam_policy_document" "auto_unseal" {
+  statement {
+    effect  = "Allow"
+    actions = [
+      "kms:DescribeKey",
+      "kms:Encrypt",
+      "kms:Decrypt",
+    ]
+    resources = [
+      aws_kms_key.vault.arn,
+    ]
+  }
+}
+
+resource "aws_iam_role_policy" "secrets_manager" {
+  name   = "${var.namespace}-${var.name}-secrets-manager"
+  policy = data.aws_iam_policy_document.secrets_manager.json
+  role   = aws_iam_role.instance_role.id
+}
+
+data "aws_iam_policy_document" "secrets_manager" {
+  statement {
+    effect  = "Allow"
+    actions = [
+      "secretsmanager:GetSecretValue",
+    ]
+    resources = [
+      var.secrets_manager_arn,
+    ]
+  }
+}
+
+module "alb" {
+  source                                  = "cloudposse/alb/aws"
+  version                                 = "1.11.1"
+  alb_access_logs_s3_bucket_force_destroy = var.alb_destroy_log_bucket
+  attributes                              = ["alb"]
+  certificate_arn                         = var.alb_certificate_arn
+  deletion_protection_enabled             = var.deletion_protection
+  health_check_interval                   = 60
+  health_check_path                       = var.alb_health_check_path
+  health_check_timeout                    = 10
+  http_ingress_cidr_blocks                = var.http_ingress_cidr_blocks
+  http_redirect                           = var.http_redirect
+  https_enabled                           = true
+  https_ingress_cidr_blocks               = var.https_ingress_cidr_blocks
+  internal                                = true
+  lifecycle_rule_enabled                  = true
+  name                                    = module.label.id
+  subnet_ids                              = var.private_subnet_ids
+  tags                                    = module.label.tags
+  target_group_name                       = module.label.id
+  target_group_port                       = var.container_port
+  target_group_target_type                = "instance"
+  vpc_id                                  = var.vpc_id
+  stickiness                              = var.stickiness
+}
+
+resource "aws_security_group" "vault_private" {
+  name   = "${var.namespace}-${var.name}"
+  description = "Vault Security Group"
+  vpc_id = var.vpc_id
+  tags   = merge(
+    var.tags,
+    {
+      "Name" = "${var.namespace}-${var.name}-sg"
+    }
+  )
+}
+
+resource "aws_security_group_rule" "vault_internal_api" {
+  security_group_id = aws_security_group.vault_private.id
+  description       = "Vault Internal API"
+  type              = "ingress"
+  from_port         = 8200
+  to_port           = 8200
+  protocol          = "tcp"
+  self              = true
+}
+
+resource "aws_security_group_rule" "vault_internal_raft" {
+  security_group_id = aws_security_group.vault_private.id
+  description       = "Vault Raft"
+  type              = "ingress"
+  from_port         = 8201
+  to_port           = 8201
+  protocol          = "tcp"
+  self              = true
+}
+
+resource "aws_security_group_rule" "vault_ssh_inbound" {
+  description       = "Allow SSH"
+  security_group_id = aws_security_group.vault_private.id
+  type              = "ingress"
+  from_port         = 22
+  to_port           = 22
+  protocol          = "tcp"
+  self              = true
+}
+
+resource "aws_security_group_rule" "vault_alb_inbound" {
+  security_group_id        = aws_security_group.vault_private.id
+  description              = "Allow AWS ALB to reach Vault nodes"
+  type                     = "ingress"
+  from_port                = 8200
+  to_port                  = 8200
+  protocol                 = "tcp"
+  source_security_group_id = module.alb.security_group_id
+}
+
+resource "aws_security_group_rule" "vault_outbound" {
+  security_group_id = aws_security_group.vault_private.id
+  description       = "Allow Vault nodes to send outbound traffic"
+  type              = "egress"
+  from_port         = 0
+  to_port           = 0
+  protocol          = "-1"
+  cidr_blocks       = ["0.0.0.0/0"]
+}
+
+resource "aws_launch_template" "vault" {
+  name          = "${var.namespace}-${var.name}"
+  image_id      = var.ubuntu_ami
+  instance_type = var.instance_type
+  key_name      = var.ssh_key_name
+  user_data     = 
base64encode(templatefile("${path.module}/templates/install_vault_script.sh.tpl",
+    {
+      region                = var.region
+      name                  = var.namespace
+      vault_version         = var.vault_version
+      kms_key_arn           = aws_kms_key.vault.arn
+      secrets_manager_arn   = var.secrets_manager_arn
+      leader_tls_servername = var.leader_tls_servername
+    }))
+  vpc_security_group_ids = [
+    aws_security_group.vault_private.id,
+  ]
+
+  block_device_mappings {
+    device_name = "/dev/sda1"
+
+    ebs {
+      volume_type           = "gp3"
+      volume_size           = 100
+      throughput            = 150
+      iops                  = 3000
+      delete_on_termination = true
+    }
+  }
+
+  iam_instance_profile {
+    name = aws_iam_instance_profile.vault.name
+  }
+
+  metadata_options {
+    http_endpoint = "enabled"
+    http_tokens   = "required"
+  }
+}
+
+resource "aws_autoscaling_group" "vault" {
+  name                = "${var.namespace}-${var.name}-asg"
+  min_size            = var.min_nodes
+  max_size            = var.max_nodes
+  desired_capacity    = var.min_nodes
+  vpc_zone_identifier = var.private_subnet_ids
+  target_group_arns   = [module.alb.default_target_group_arn]
+
+  launch_template {
+    id      = aws_launch_template.vault.id
+    version = "$Latest"
+  }
+
+  tag {
+    key                 = "Name"
+    value               = "${var.namespace}-${var.name}-server"
+    propagate_at_launch = true
+  }
+}
\ No newline at end of file
diff --git a/deployment/terraform/aws/modules/vault/resources/openssl-vault.cnf 
b/deployment/terraform/aws/modules/vault/resources/openssl-vault.cnf
new file mode 100644
index 000000000..618080e77
--- /dev/null
+++ b/deployment/terraform/aws/modules/vault/resources/openssl-vault.cnf
@@ -0,0 +1,21 @@
+[req]
+default_bits = 2048
+prompt = no
+default_md = sha256
+distinguished_name = dn
+req_extensions = req_ext
+
+[dn]
+C = US
+ST = Georgia
+L = Atlanta
+O = "Apache Airavata Custos"
+CN = secrets.usecustos.org
+
+[req_ext]
+subjectAltName = @alt_names
+
+[alt_names]
+DNS.1 = secrets.usecustos.org
+DNS.2 = localhost
+IP.1 = 127.0.0.1
\ No newline at end of file
diff --git 
a/deployment/terraform/aws/modules/vault/templates/install_vault_script.sh.tpl 
b/deployment/terraform/aws/modules/vault/templates/install_vault_script.sh.tpl
new file mode 100644
index 000000000..f97465adf
--- /dev/null
+++ 
b/deployment/terraform/aws/modules/vault/templates/install_vault_script.sh.tpl
@@ -0,0 +1,75 @@
+#!/usr/bin/env bash
+
+imds_token=$( curl -Ss -H "X-aws-ec2-metadata-token-ttl-seconds: 30" -XPUT 
169.254.169.254/latest/api/token )
+instance_id=$( curl -Ss -H "X-aws-ec2-metadata-token: $imds_token" 
169.254.169.254/latest/meta-data/instance-id )
+local_ipv4=$( curl -Ss -H "X-aws-ec2-metadata-token: $imds_token" 
169.254.169.254/latest/meta-data/local-ipv4 )
+
+curl -fsSL https://apt.releases.hashicorp.com/gpg | apt-key add -
+apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com 
$(lsb_release -cs) main"
+apt-get update
+apt-get install -y vault=${vault_version}-* awscli jq
+
+timedatectl set-timezone UTC
+rm -rf /opt/vault/tls/*
+
+# /opt/vault/tls should be readable by all the users
+chmod 0755 /opt/vault/tls
+
+# Only the vault group be able to read the vault-key.pem
+touch /opt/vault/tls/vault-key.pem
+chown root:vault /opt/vault/tls/vault-key.pem
+chmod 0640 /opt/vault/tls/vault-key.pem
+
+secret_result=$(aws secretsmanager get-secret-value --secret-id 
${secrets_manager_arn} --region ${region} --output text --query SecretString)
+jq -r .vault_cert <<< "$secret_result" > /opt/vault/tls/vault-cert.pem
+jq -r .vault_ca <<< "$secret_result" > /opt/vault/tls/vault-ca.pem
+jq -r .vault_pk <<< "$secret_result" > /opt/vault/tls/vault-key.pem
+
+cat << EOF > /etc/vault.d/vault.hcl
+ui = true
+disable_mlock = true
+
+storage "raft" {
+  path    = "/opt/vault/data"
+  node_id = "$instance_id"
+  retry_join {
+    auto_join = "provider=aws region=${region} tag_key=${name}-vault 
tag_value=server"
+    auto_join_scheme = "https"
+    leader_tls_servername = "${leader_tls_servername}"
+    leader_ca_cert_file = "/opt/vault/tls/vault-ca.pem"
+    leader_client_cert_file = "/opt/vault/tls/vault-cert.pem"
+    leader_client_key_file = "/opt/vault/tls/vault-key.pem"
+  }
+}
+
+cluster_addr = "https://$local_ipv4:8201";
+api_addr = "https://$local_ipv4:8200";
+
+listener "tcp" {
+  address            = "0.0.0.0:8200"
+  tls_disable        = false
+  tls_cert_file      = "/opt/vault/tls/vault-cert.pem"
+  tls_key_file       = "/opt/vault/tls/vault-key.pem"
+  tls_client_ca_file = "/opt/vault/tls/vault-ca.pem"
+}
+
+seal "awskms" {
+  region     = "${region}"
+  kms_key_id = "${kms_key_arn}"
+}
+
+EOF
+
+# Only the vault group should be able to read vault.hcl
+chown root:root /etc/vault.d
+chown root:vault /etc/vault.d/vault.hcl
+chmod 640 /etc/vault.d/vault.hcl
+
+systemctl enable vault
+systemctl start vault
+
+echo "Vault profile"
+cat <<PROFILE | sudo tee /etc/profile.d/vault.sh
+export VAULT_ADDR="https://127.0.0.1:8200";
+export VAULT_CACERT="/opt/vault/tls/vault-ca.pem"
+PROFILE
diff --git a/deployment/terraform/aws/modules/vault/variables.tf 
b/deployment/terraform/aws/modules/vault/variables.tf
new file mode 100644
index 000000000..bf1784067
--- /dev/null
+++ b/deployment/terraform/aws/modules/vault/variables.tf
@@ -0,0 +1,124 @@
+variable "environment" {
+  description = "Environment name (development, production, etc)"
+  type        = string
+}
+
+variable "name" {
+  description = "Used by modules to construct labels"
+  type        = string
+  default     = "vault"
+}
+
+variable "tags" {
+  description = "Default tags applied to resources"
+  type        = map(string)
+}
+
+variable "region" {
+  description = "AWS region to target"
+  type        = string
+}
+
+variable "namespace" {
+  description = "Application namespace"
+  type        = string
+}
+
+variable "vpc_id" {
+  description = "AWS VPC ID"
+  type        = string
+}
+
+variable "alb_destroy_log_bucket" {
+  description = "Destroy ALB log bucket on teardown"
+  type        = bool
+}
+
+variable "alb_certificate_arn" {
+  description = "ACM certificate ARN used by ALB"
+  type        = string
+}
+
+variable "deletion_protection" {
+  description = "Protect resources from being deleted"
+  type        = bool
+}
+
+variable "http_ingress_cidr_blocks" {
+  description = "CIDR ranges allowed to connect to service port 80"
+  type        = list(string)
+}
+
+variable "http_redirect" {
+  description = "Controls whether port 80 should redirect to 443 (or not 
listen)"
+  type        = bool
+}
+
+variable "https_ingress_cidr_blocks" {
+  description = "CIDR ranges allowed to connect to service port 443"
+  type        = list(string)
+}
+
+variable "private_subnet_ids" {
+  description = "List of private subnet IDs"
+  type        = list(string)
+}
+
+variable "container_port" {
+  description = "Vault port exposed in container"
+  type        = number
+}
+
+variable "stickiness" {
+  type = object({
+    cookie_duration = number
+    enabled         = bool
+  })
+  description = "Target group sticky configuration"
+}
+
+variable "alb_health_check_path" {
+  type        = string
+  description = "Vault health check path"
+  default     = "/v1/sys/health"
+}
+
+variable "ubuntu_ami" {
+  type        = string
+  description = "AMI for Ubuntu"
+}
+
+variable "instance_type" {
+  type        = string
+  description = "EC2 instance type"
+}
+
+variable "ssh_key_name" {
+  type        = string
+  description = "key pair to use for SSH access to instance"
+}
+
+variable "vault_version" {
+  type        = string
+  description = "Vault version"
+}
+
+variable "leader_tls_servername" {
+  type        = string
+  description = "One of the shared DNS SAN used to create the certs use for 
mTLS"
+}
+
+variable "secrets_manager_arn" {
+  type        = string
+  description = "Secrets manager ARN"
+}
+
+variable "min_nodes" {
+  type        = number
+  description = "Minimum number of Vault nodes to deploy in ASG"
+}
+
+variable "max_nodes" {
+  type        = number
+  description = "Minimum number of Vault nodes to deploy in ASG"
+}
\ No newline at end of file
diff --git a/deployment/terraform/aws/modules/vault/versions.tf 
b/deployment/terraform/aws/modules/vault/versions.tf
new file mode 100644
index 000000000..21c24a6e3
--- /dev/null
+++ b/deployment/terraform/aws/modules/vault/versions.tf
@@ -0,0 +1,10 @@
+terraform {
+  required_version = ">= 0.15.3"
+
+  required_providers {
+    aws = {
+      source  = "hashicorp/aws"
+      version = ">= 4.0"
+    }
+  }
+}
diff --git a/deployment/terraform/aws/outputs.tf 
b/deployment/terraform/aws/outputs.tf
new file mode 100644
index 000000000..d7778a970
--- /dev/null
+++ b/deployment/terraform/aws/outputs.tf
@@ -0,0 +1,52 @@
+output "public_subnet_cidrs" {
+  value = var.enable_network ? module.network[0].public_subnet_cidrs : []
+}
+
+output "private_subnet_cidrs" {
+  value = var.enable_network ? module.network[0].private_subnet_cidrs : 
local.private_subnet_cidrs
+}
+
+output "state_table" {
+  value = module.terraform_state_backend.dynamodb_table_name
+}
+
+output "state_bucket" {
+  value = module.terraform_state_backend.s3_bucket_domain_name
+}
+
+output "alb_dns_name" {
+  value = module.keycloak.alb_dns_name
+}
+
+output "alb_log_bucket" {
+  value = module.keycloak.alb_log_bucket
+}
+
+output "ecr_repo" {
+  value = module.keycloak.ecr_repo
+}
+
+output "ecs_cluster" {
+  value = module.keycloak.ecs_cluster
+}
+
+output "ecs_service" {
+  value = module.keycloak.ecs_service
+}
+
+output "rds_cluster_endpoint" {
+  value = module.keycloak.rds_cluster_endpoint
+}
+
+output "rds_cluster_reader_endpoint" {
+  value = module.keycloak.rds_cluster_reader_endpoint
+}
+
+output "rds_cluster_database_name" {
+  value = module.keycloak.rds_cluster_database_name
+}
+
+output "rds_cluster_master_username" {
+  value     = module.keycloak.rds_cluster_master_username
+  sensitive = true
+}
diff --git a/deployment/terraform/aws/terraform.tfvars 
b/deployment/terraform/aws/terraform.tfvars
new file mode 100644
index 000000000..ea827dfc1
--- /dev/null
+++ b/deployment/terraform/aws/terraform.tfvars
@@ -0,0 +1,39 @@
+environment = "dev"
+namespace   = "custos"
+region      = "us-east-2"
+
+vpc_cidr     = "10.20.30.0/24"
+public_cidr  = "10.20.30.0/25"
+private_cidr = "10.20.30.128/25"
+
+
+keycloak_alb_certificate_arn = "<KEYCLOAK_ALB_CERT_ARN>"
+keycloak_dns_name            = "auth.usecustos.org"
+dns_zone_id                  = "<DNS_ZONE_ID>"
+
+container_cpu_units                = 1024
+container_memory_limit             = 2048
+container_memory_reserved          = 1024
+jvm_heap_min                       = 512
+jvm_heap_max                       = 1024
+jvm_meta_min                       = 128
+jvm_meta_max                       = 512
+deployment_maximum_percent         = 100
+deployment_minimum_healthy_percent = 50
+desired_count                      = 1
+log_retention_days                 = 5
+
+db_instance_type         = "db.r6g.large"
+db_backup_retention_days = 5
+db_cluster_size          = 2
+
+
+vault_alb_certificate_arn   = "<VAULT_ALB_CERT_ARN>"
+vault_ami                   = "<AMI_ID>"
+ec2_ssh_key_name            = "custos-auth"
+vault_instance_type         = "m5.xlarge"
+vault_version               = "1.11.0"
+vault_leader_tls_servername = "vault.usecustos.org"
+vault_secrets_manager_arn   = "<SECRETS_MANAGER_ARN>"
+vault_min_nodes             = 1
+vault_max_nodes             = 5
diff --git a/deployment/terraform/aws/variables.tf 
b/deployment/terraform/aws/variables.tf
new file mode 100644
index 000000000..f04456c85
--- /dev/null
+++ b/deployment/terraform/aws/variables.tf
@@ -0,0 +1,302 @@
+variable "environment" {
+  description = "Environment name (development, production, etc)"
+  type        = string
+}
+
+variable "namespace" {
+  description = "Application namespace"
+  type        = string
+}
+
+variable "region" {
+  description = "AWS region to target"
+  type        = string
+}
+
+variable "enable_network" {
+  description = "Use network module. Set to false to use your own network 
resources"
+  type        = bool
+  default     = true
+}
+
+variable "vpc_id" {
+  description = "AWS VPC ID (if not using network module)"
+  type        = string
+  default     = ""
+}
+
+variable "vpc_cidr" {
+  description = "RFC1918 CIDR range for VPC"
+  type        = string
+  default     = ""
+}
+
+variable "public_cidr" {
+  description = "RFC1918 CIDR range for public subnets (subset of vpc_cidr)"
+  type        = string
+  default     = ""
+}
+
+variable "private_cidr" {
+  description = "RFC1918 CIDR range for private subnets (subset of vpc_cidr)"
+  type        = string
+  default     = ""
+}
+
+variable "public_subnet_ids" {
+  description = "List of public subnet IDs for deployment if not using network 
module"
+  type        = list(string)
+  default     = []
+}
+
+variable "private_subnet_ids" {
+  description = "List of private subnet IDs for deployment if not using 
network module"
+  type        = list(string)
+  default     = []
+}
+
+variable "tags" {
+  description = "Standard tags for all resources"
+  type        = map(any)
+  default     = {
+    ManagedBy = "Terraform"
+  }
+}
+
+variable "keycloak_alb_certificate_arn" {
+  description = "ACM certificate used by Keycloak ALB"
+  type        = string
+}
+
+variable "alb_destroy_log_bucket" {
+  description = "Destroy ALB log bucket on teardown"
+  type        = bool
+  default     = true
+}
+
+variable "container_cpu_units" {
+  description = "CPU units to reserve for container (1024 units == 1 CPU)"
+  type        = number
+}
+
+variable "container_memory_limit" {
+  description = "Container memory hard limit"
+  type        = number
+}
+
+variable "container_memory_reserved" {
+  description = "Container memory starting reservation"
+  type        = number
+}
+
+variable "keycloak_container_port" {
+  description = "Keycloak port exposed in container"
+  type        = number
+  default     = 8080
+}
+
+variable "db_backup_retention_days" {
+  description = "How long Database backups are retained"
+  type        = number
+}
+
+variable "db_backup_window" {
+  description = "Daily time range during which backups happen"
+  type        = string
+  default     = "00:00-02:00"
+}
+
+variable "db_cluster_family" {
+  description = "Family of DB cluster parameter group"
+  type        = string
+  default     = "aurora-postgresql15"
+}
+
+variable "db_cluster_size" {
+  description = "Number of RDS cluster instances"
+  type        = number
+}
+
+variable "db_engine_version" {
+  description = "Version of DB engine to use"
+  type        = string
+  default     = "15.4"
+}
+
+variable "db_instance_type" {
+  description = "Instance type used for RDS instances"
+  type        = string
+}
+
+variable "db_maintenance_window" {
+  description = "Weekly time range during which system maintenance can occur 
(UTC)"
+  type        = string
+  default     = "sat:03:00-sat:04:00"
+}
+
+variable "deletion_protection" {
+  description = "Protect supporting resources from being deleted (ALB and RDS)"
+  type        = bool
+  default     = false
+}
+
+variable "deployment_maximum_percent" {
+  description = "Maximum task instances allowed to run"
+  type        = number
+}
+
+variable "deployment_minimum_healthy_percent" {
+  description = "Minimum percentage of healthy task instances"
+  type        = number
+}
+
+variable "desired_count" {
+  description = "Number of ECS task instances to run"
+  type        = number
+}
+
+variable "keycloak_dns_name" {
+  description = "Keycloak DNS"
+  type        = string
+}
+
+variable "dns_zone_id" {
+  description = "Route53 Zone ID hosting Services"
+  type        = string
+}
+
+variable "encryption_configuration" {
+  type = object({
+    encryption_type = string
+    kms_key         = any
+  })
+  description = "ECR encryption configuration"
+  default     = {
+    encryption_type = "AES256"
+    kms_key         = null
+  }
+}
+
+variable "http_redirect" {
+  description = "Controls whether port 80 should redirect to 443 (or not 
listen)"
+  type        = bool
+  default     = true
+}
+
+variable "http_ingress_cidr_blocks" {
+  description = "CIDR ranges allowed to connect to service port 80"
+  type        = list(string)
+  default     = ["0.0.0.0/0"]
+}
+
+variable "https_ingress_cidr_blocks" {
+  description = "CIDR ranges allowed to connect to service port 443"
+  type        = list(string)
+  default     = ["0.0.0.0/0"]
+}
+
+variable "jvm_heap_min" {
+  description = "Minimum JVM heap size for application in MB"
+  type        = number
+}
+
+variable "jvm_heap_max" {
+  description = "Maximum JVM heap size for application in MB"
+  type        = number
+}
+
+variable "jvm_meta_min" {
+  description = "Minimum JVM meta space size for application in MB"
+  type        = number
+}
+
+variable "jvm_meta_max" {
+  description = "Maximum JVM meta space size for application in MB"
+  type        = number
+}
+
+variable "internal" {
+  description = "Whether environment should be exposed to Internet (if not 
using network module)"
+  type        = string
+  default     = false
+}
+
+variable "log_retention_days" {
+  description = "Log retention for CloudWatch logs"
+  type        = number
+}
+
+variable "rds_source_region" {
+  description = "Region of primary RDS cluster (needed to support encryption)"
+  type        = string
+  default     = ""
+}
+
+variable "route_table_ids" {
+  description = "List of route tables used by s3 VPC endpoint (if not using 
network module)"
+  type        = list(string)
+  default     = []
+}
+
+variable "stickiness" {
+  type = object({
+    cookie_duration = number
+    enabled         = bool
+  })
+  description = "Target group sticky configuration"
+  default     = {
+    cookie_duration = null
+    enabled         = false
+  }
+}
+
+variable "vault_alb_certificate_arn" {
+  description = "ACM certificate used by Vault ALB"
+  type        = string
+}
+
+variable "vault_container_port" {
+  description = "Vault port"
+  type        = number
+  default     = 8200
+}
+
+variable "vault_ami" {
+  description = "AMI used for Vault"
+  type        = string
+}
+
+variable "ec2_ssh_key_name" {
+  description = "key pair to use for SSH access to instance"
+  type        = string
+}
+
+variable "vault_instance_type" {
+  type        = string
+  description = "EC2 instance type"
+}
+
+variable "vault_version" {
+  type        = string
+  description = "Vault version"
+}
+
+variable "vault_leader_tls_servername" {
+  type        = string
+  description = "One of the shared DNS SAN used to create the certs use for 
mTLS"
+}
+
+variable "vault_secrets_manager_arn" {
+  type        = string
+  description = "Secrets manager ARN"
+}
+
+variable "vault_min_nodes" {
+  type        = number
+  description = "Minimum number of Vault nodes to deploy in ASG"
+}
+
+variable "vault_max_nodes" {
+  type        = number
+  description = "Minimum number of Vault nodes to deploy in ASG"
+}

Reply via email to