├── scripts ├── aws_cloud_spec_gen │ ├── __init__.py │ ├── models │ │ ├── __init__.py │ │ ├── iam.py │ │ ├── structure.py │ │ └── security_group.py │ ├── requirements.txt │ ├── static │ │ ├── load_balancer.md │ │ ├── kms.md │ │ ├── launch_templates.md │ │ ├── autoscaling_groups.md │ │ ├── private_route_table_requirements.md │ │ ├── public_route_table_requirements.md │ │ ├── private_subnets_requirements.md │ │ ├── public_subnets_requirements.md │ │ ├── ec2_instances.md │ │ └── vpc_requirements.md │ ├── README.md │ ├── main.py │ └── structure.yaml └── least_permissions.py ├── tfsec.yaml ├── hooks ├── terraform_format.sh ├── tfsec.sh ├── terraform_validate.sh ├── terraform_docs.sh └── tflint.sh ├── modules ├── k8s_eks_addons │ ├── templates │ │ ├── ebs_values.yaml │ │ ├── load_balancer_controller_values.yaml │ │ ├── autoscaler_values.yaml │ │ └── nginx_values.yaml │ ├── versions.tf │ ├── kube-proxy.tf │ ├── coredns.tf │ ├── ingress-nginx.tf │ ├── gpu-operator.tf │ ├── ebs-csi.tf │ ├── efs-csi.tf │ ├── variables.tf │ ├── s3-csi.tf │ └── cluster-autoscaler.tf ├── ivs_aws_instance │ ├── k8s.tf │ ├── data.tf │ ├── secrets.tf │ ├── outputs.tf │ ├── locals.tf │ ├── opensearch.tf │ ├── backup.tf │ ├── postgresql.tf │ ├── storage.tf │ └── variables.tf ├── simphera_aws_instance │ ├── k8s.tf │ ├── secrets.tf │ ├── templates │ │ ├── bucket_policy.json │ │ └── minio-policy.json │ ├── versions.tf │ ├── locals.tf │ ├── outputs.tf │ ├── backup.tf │ ├── .terraform.lock.hcl │ ├── minio-storage.tf │ ├── variables.tf │ └── postgresql.tf └── eks │ ├── modules │ └── node-group │ │ ├── outputs.tf │ │ ├── data.tf │ │ ├── iam.tf │ │ ├── locals.tf │ │ ├── templates │ │ └── userdata.tpl │ │ ├── variables.tf │ │ ├── launch-template.tf │ │ └── node-group.tf │ ├── logs-group.tf │ ├── storage.tf │ ├── oidc.tf │ ├── versions.tf │ ├── kms.tf │ ├── cluster-auth.tf │ ├── node-groups.tf │ ├── iam.tf │ ├── outputs.tf │ ├── locals.tf │ ├── cluster.tf │ ├── vpc-cni.tf │ ├── data.tf │ └── variables.tf ├── .tflint.hcl ├── .vscode └── settings.json ├── templates ├── fluentbit_values.yaml ├── s3_ssl_policy.json ├── s3_log_policy.json ├── license_server_policy.json └── least_permissions │ ├── deploy_ivs_policy.json │ ├── deploy_simphera_policy.json │ └── deploy_all_policy.json ├── state-backend-template ├── .gitignore ├── versions.tf ├── tfvars.hcl.terraform-docs.yml ├── LICENSE ├── k8s-eks-addons.tf ├── .pre-commit-config.yaml ├── CONTRIBUTING.md ├── k8s.tf ├── data.tf ├── .gitattributes ├── efs.tf ├── simphera-instances.tf ├── providers.tf.example ├── outputs.tf ├── ivs-instances.tf ├── ecr.tf ├── CHANGELOG.md ├── .github └── workflows │ └── qualitygate.yml ├── logging.tf ├── network.tf ├── terraform.json.example └── patch-manager.tf /scripts/aws_cloud_spec_gen/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/aws_cloud_spec_gen/models/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tfsec.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | minimum_severity: CRITICAL -------------------------------------------------------------------------------- /scripts/aws_cloud_spec_gen/requirements.txt: -------------------------------------------------------------------------------- 1 | pyyaml -------------------------------------------------------------------------------- /hooks/terraform_format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | terraform fmt -recursive -------------------------------------------------------------------------------- /hooks/tfsec.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | tfsec --exclude-downloaded-modules --config-file tfsec.yaml -------------------------------------------------------------------------------- /modules/k8s_eks_addons/templates/ebs_values.yaml: -------------------------------------------------------------------------------- 1 | sidecars: 2 | snapshotter: 3 | forceEnable: false 4 | -------------------------------------------------------------------------------- /modules/ivs_aws_instance/k8s.tf: -------------------------------------------------------------------------------- 1 | resource "kubernetes_namespace" "k8s_namespace" { 2 | metadata { 3 | name = var.k8s_namespace 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.tflint.hcl: -------------------------------------------------------------------------------- 1 | plugin "aws" { 2 | enabled = true 3 | version = "0.27.0" 4 | source = "github.com/terraform-linters/tflint-ruleset-aws" 5 | } 6 | -------------------------------------------------------------------------------- /hooks/terraform_validate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #[ ! -d ".terraform" ] && terraform init || echo "skipping terraform init" 3 | terraform init 4 | terraform validate 5 | -------------------------------------------------------------------------------- /modules/simphera_aws_instance/k8s.tf: -------------------------------------------------------------------------------- 1 | 2 | resource "kubernetes_namespace" "k8s_namespace" { 3 | metadata { 4 | 5 | name = var.k8s_namespace 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /scripts/aws_cloud_spec_gen/static/load_balancer.md: -------------------------------------------------------------------------------- 1 | | Description | Mandatory | 2 | | ----------- | --------- | 3 | | Network Load Balancer for EKS created by nginx controller. | Yes | 4 | -------------------------------------------------------------------------------- /hooks/terraform_docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | terraform-docs markdown table --output-file README.md --output-mode inject . 3 | terraform-docs -c tfvars.hcl.terraform-docs.yml . 4 | terraform-docs tfvars json . 5 | -------------------------------------------------------------------------------- /modules/eks/modules/node-group/outputs.tf: -------------------------------------------------------------------------------- 1 | output "nodegroup_id" { 2 | value = aws_eks_node_group.node_group.id 3 | } 4 | 5 | output "nodegroup_role_id" { 6 | value = aws_iam_role.node_group.id 7 | } 8 | -------------------------------------------------------------------------------- /modules/k8s_eks_addons/templates/load_balancer_controller_values.yaml: -------------------------------------------------------------------------------- 1 | clusterName: ${eks_cluster_id} 2 | region: ${aws_region} 3 | image: 4 | repository: ${repository} 5 | serviceAccount: 6 | create: false 7 | name: ${service_account} 8 | -------------------------------------------------------------------------------- /modules/simphera_aws_instance/secrets.tf: -------------------------------------------------------------------------------- 1 | data "aws_secretsmanager_secret" "secrets" { 2 | name = var.secretname 3 | } 4 | 5 | data "aws_secretsmanager_secret_version" "secrets" { 6 | secret_id = data.aws_secretsmanager_secret.secrets.id 7 | } 8 | -------------------------------------------------------------------------------- /hooks/tflint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | [ ! -d ".tflint.d" ] && tflint --init 3 | tflint 4 | [ $? -eq 0 ] || exit $? # Pass the exit code from tflint 5 | tflint --config ../../.tflint.hcl --chdir ./modules/simphera_aws_instance 6 | [ $? -eq 0 ] || exit $? # Pass the exit code from tflint 7 | 8 | 9 | -------------------------------------------------------------------------------- /scripts/aws_cloud_spec_gen/static/kms.md: -------------------------------------------------------------------------------- 1 | | Description | Mandatory | 2 | | ----------- | ---------- | 3 | | EKS cluster secret encryption key | Yes | 4 | | KMS key used to encrypt Kubernetes, VPC Flow, Amazon RDS for PostgreSQL and SSM Patch manager log groups within infrastructure <_cluster_name_> | Yes | 5 | -------------------------------------------------------------------------------- /modules/eks/modules/node-group/data.tf: -------------------------------------------------------------------------------- 1 | data "aws_iam_policy_document" "node_group_assume_role_policy" { 2 | statement { 3 | sid = "EKSWorkerAssumeRole" 4 | actions = [ 5 | "sts:AssumeRole", 6 | ] 7 | principals { 8 | type = "Service" 9 | identifiers = [local.ec2_principal] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /modules/eks/logs-group.tf: -------------------------------------------------------------------------------- 1 | resource "aws_cloudwatch_log_group" "log_group" { 2 | count = var.create_cloudwatch_log_group ? 1 : 0 3 | 4 | name = "/aws/eks/${var.cluster_name}/cluster" 5 | retention_in_days = var.cloudwatch_log_group_retention_in_days 6 | kms_key_id = var.cloudwatch_log_group_kms_key_id 7 | 8 | tags = var.tags 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[terraform]": { 3 | "editor.defaultFormatter": "hashicorp.terraform", 4 | "editor.formatOnSave": true, 5 | "editor.formatOnSaveMode": "file" 6 | }, 7 | "[terraform-vars]": { 8 | "editor.defaultFormatter": "hashicorp.terraform", 9 | "editor.formatOnSave": true, 10 | "editor.formatOnSaveMode": "file" 11 | } 12 | } -------------------------------------------------------------------------------- /scripts/aws_cloud_spec_gen/static/launch_templates.md: -------------------------------------------------------------------------------- 1 | | Name | Mandatory | 2 | | ---- | ---------- | 3 | | Launch template for default node pool. | Yes | 4 | | Launch template for execution node pool. | No | 5 | | Launch template for GPU execution node pool. | No | 6 | | Launch template for IVS GPU execution node pool. | No | 7 | | Launch template for IVS WIndows execution node pool. | No | 8 | -------------------------------------------------------------------------------- /scripts/aws_cloud_spec_gen/static/autoscaling_groups.md: -------------------------------------------------------------------------------- 1 | | Name | Mandatory | 2 | | ---- | --------- | 3 | | Auto scaling group for default node pool. | Yes | 4 | | Auto scaling group for execution node pool. | No | 5 | | Auto scaling group for GPU execution node pool. | No | 6 | | Auto scaling group for IVS GPU execution node pool. | No | 7 | | Auto scaling group for IVS WIndows execution node pool. | No | -------------------------------------------------------------------------------- /templates/fluentbit_values.yaml: -------------------------------------------------------------------------------- 1 | serviceAccount: 2 | create: false 3 | name: ${service_account_name} 4 | 5 | cloudWatch: 6 | enabled: true 7 | region: ${aws_region} 8 | logGroupName: ${log_group_name} 9 | 10 | firehose: 11 | enabled: false 12 | 13 | kinesis: 14 | enabled: false 15 | 16 | elasticsearch: 17 | enabled: false 18 | 19 | nodeSelector: 20 | kubernetes.io/os: linux 21 | -------------------------------------------------------------------------------- /templates/s3_ssl_policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Action": "s3:*", 6 | "Effect": "Deny", 7 | "Resource": ["arn:aws:s3:::${bucket}", "arn:aws:s3:::${bucket}/*"], 8 | "Condition": { 9 | "Bool": { 10 | "aws:SecureTransport": "false" 11 | } 12 | }, 13 | "Principal": "*" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /modules/simphera_aws_instance/templates/bucket_policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Action": "s3:*", 6 | "Effect": "Deny", 7 | "Resource": ["arn:aws:s3:::${bucket}", "arn:aws:s3:::${bucket}/*"], 8 | "Condition": { 9 | "Bool": { 10 | "aws:SecureTransport": "false" 11 | } 12 | }, 13 | "Principal": "*" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /scripts/aws_cloud_spec_gen/static/private_route_table_requirements.md: -------------------------------------------------------------------------------- 1 | | Requirement | Description | Default value | Mandatory? | 2 | | ----------- | ----------- | -------------- | ---------- | 3 | | Routes | Minimum routes for network communication to work | 0.0.0.0/0 to \
\ to local | yes | 4 | | Subnet associations | Apply route table routes to a particular subnet | Explicit, all private subnets | yes | 5 | -------------------------------------------------------------------------------- /scripts/aws_cloud_spec_gen/static/public_route_table_requirements.md: -------------------------------------------------------------------------------- 1 | | Requirement | Description | Default value | Mandatory? | 2 | | ----------- | ----------- | -------------- | ---------- | 3 | | Routes | Minimum routes for network communication to work | 0.0.0.0/0 to \
\ to local | yes | 4 | | Subnet associations | Apply route table routes to a particular subnet | Explicit, all public subnets | yes | 5 | -------------------------------------------------------------------------------- /modules/eks/storage.tf: -------------------------------------------------------------------------------- 1 | resource "kubernetes_storage_class_v1" "gp3" { 2 | metadata { 3 | name = "gp3" 4 | annotations = { 5 | "storageclass.kubernetes.io/is-default-class" = "true" 6 | } 7 | } 8 | storage_provisioner = "ebs.csi.aws.com" 9 | volume_binding_mode = "WaitForFirstConsumer" 10 | parameters = { 11 | type = "gp3" 12 | fsType = "ext4" 13 | } 14 | depends_on = [aws_eks_cluster.eks] 15 | } 16 | -------------------------------------------------------------------------------- /modules/k8s_eks_addons/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3.0" 3 | 4 | required_providers { 5 | kubernetes = { 6 | source = "hashicorp/kubernetes" 7 | version = ">= 2.10" 8 | } 9 | 10 | helm = { 11 | source = "hashicorp/helm" 12 | version = ">= 2.4.1" 13 | } 14 | 15 | kubectl = { 16 | source = "gavinbunney/kubectl" 17 | version = ">= 1.19.0" 18 | } 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /modules/eks/oidc.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_openid_connect_provider" "oidc_provider" { 2 | client_id_list = distinct(compact(concat(["sts.${var.aws_context.partition_dns_suffix}"], []))) 3 | thumbprint_list = concat([data.tls_certificate.cluster_certificate.certificates[0].sha1_fingerprint], []) 4 | url = aws_eks_cluster.eks.identity[0].oidc[0].issuer 5 | 6 | tags = merge( 7 | { Name = "${var.cluster_name}-eks-irsa" }, 8 | var.tags 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /modules/ivs_aws_instance/data.tf: -------------------------------------------------------------------------------- 1 | data "aws_iam_policy_document" "opensearch_access" { 2 | count = var.opensearch.enable ? 1 : 0 3 | statement { 4 | effect = "Allow" 5 | 6 | principals { 7 | type = "*" 8 | identifiers = ["*"] 9 | } 10 | 11 | actions = ["es:*"] 12 | resources = ["arn:aws:es:${var.aws_context.region_name}:${var.aws_context.caller_identity_account_id}:domain/${var.opensearch.domain_name}/*"] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /templates/s3_log_policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "S3ServerAccessLogsPolicy", 6 | "Effect": "Allow", 7 | "Principal": { 8 | "Service": "logging.s3.amazonaws.com" 9 | }, 10 | "Action": "s3:PutObject", 11 | "Resource": "arn:aws:s3:::${bucket}/*", 12 | "Condition": { 13 | "StringEquals": { 14 | "aws:SourceAccount": "${account_id}" 15 | } 16 | } 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /state-backend-template: -------------------------------------------------------------------------------- 1 | 2 | terraform { 3 | backend "s3" { 4 | #The name of the bucket to be used to store the terraform state. You need to create this container manually. 5 | bucket = "terraform-state" 6 | 7 | #The name of the file to be used inside the container to be used for this terraform state. 8 | key = "simphera.tfstate" 9 | 10 | #The region of the bucket. 11 | region = "eu-central-1" 12 | 13 | #AWS profile to use 14 | profile = "wtc-development" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /modules/eks/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 5.60.0" 8 | } 9 | kubernetes = { 10 | source = "hashicorp/kubernetes" 11 | version = ">= 2.10" 12 | } 13 | tls = { 14 | source = "hashicorp/tls" 15 | version = ">= 4.0.5" 16 | } 17 | http = { 18 | source = "hashicorp/http" 19 | version = ">= 3.4.3" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /modules/simphera_aws_instance/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3.0" 3 | 4 | required_providers { 5 | 6 | aws = { 7 | source = "hashicorp/aws" 8 | version = ">= 4.0.0" 9 | # beginning with version 5.0 some arguments are removed from resource "aws_vpc". 10 | } 11 | kubernetes = { 12 | source = "hashicorp/kubernetes" 13 | version = ">= 2.10" 14 | } 15 | 16 | http = { 17 | source = "hashicorp/http" 18 | version = ">= 2.2.0" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform 3 | **/.terraform/* 4 | 5 | # .tfstate files 6 | *.tfstate 7 | *.tfstate.* 8 | 9 | # all .tfvars files except template ones 10 | **/*.tfvars 11 | !common/config_template.tfvars 12 | !instance/config_template.tfvars 13 | 14 | # all state-backends 15 | **/state-backend.tf 16 | 17 | 18 | **/OLD_* 19 | 20 | **/kubeconfig 21 | 22 | **/charts 23 | 24 | wildcard.crt 25 | wildcard.key 26 | 27 | temp 28 | terraform.log 29 | 30 | tfsec 31 | 32 | .vs 33 | -------------------------------------------------------------------------------- /modules/k8s_eks_addons/templates/autoscaler_values.yaml: -------------------------------------------------------------------------------- 1 | awsRegion: ${aws_region} 2 | 3 | autoDiscovery: 4 | clusterName: ${eks_cluster_id} 5 | tags: 6 | - k8s.io/cluster-autoscaler/enabled 7 | - k8s.io/cluster-autoscaler/${eks_cluster_id} 8 | extraArgs: 9 | aws-use-static-instance-list: true 10 | 11 | image: 12 | tag: ${image_tag} 13 | 14 | resources: 15 | limits: 16 | cpu: 200m 17 | memory: 512Mi 18 | requests: 19 | cpu: 200m 20 | memory: 512Mi 21 | 22 | rbac: 23 | serviceAccount: 24 | name: ${service_account} 25 | create: false 26 | -------------------------------------------------------------------------------- /modules/eks/kms.tf: -------------------------------------------------------------------------------- 1 | resource "aws_kms_key" "cluster" { 2 | count = var.aws_managed_kms ? 0 : 1 3 | description = "${var.cluster_name} EKS cluster secret encryption key" 4 | policy = data.aws_iam_policy_document.eks_key.json 5 | enable_key_rotation = true 6 | deletion_window_in_days = 30 7 | tags = var.tags 8 | } 9 | 10 | resource "aws_kms_alias" "cluster" { 11 | count = var.aws_managed_kms ? 0 : 1 12 | name = "alias/${var.cluster_name}" 13 | target_key_id = aws_kms_key.cluster[0].key_id 14 | } 15 | -------------------------------------------------------------------------------- /scripts/aws_cloud_spec_gen/static/private_subnets_requirements.md: -------------------------------------------------------------------------------- 1 | | Requirement | Description | Default value | Mandatory? | 2 | | ----------- | ----------- | -------------- | ---------- | 3 | | IPv4 CIDR blocks | Network size, ie number of available IPs per private subnet | 10.1.0.0/22
10.1.4.0/22
10.1.8.0/22 | yes | 4 | | Tags | Metadata for organizing your AWS resources | "kubernetes.io/cluster/\" = "shared"
"kubernetes.io/role/elb" = "1"
"purpose" = "private" | yes | 5 | | Network Access Lists | Allows or denies specific inbound or outbound traffic at the subnet level | Allow all inbound/outbound | yes | 6 | -------------------------------------------------------------------------------- /scripts/aws_cloud_spec_gen/static/public_subnets_requirements.md: -------------------------------------------------------------------------------- 1 | | Requirement | Description | Default value | Mandatory? | 2 | | ----------- | ----------- | -------------- | ---------- | 3 | | IPv4 CIDR blocks | Network size, ie number of available IPs per public subnet | 10.1.12.0/22
10.1.16.0/22
10.1.20.0/22 | yes | 4 | | Tags | Metadata for organizing your AWS resources | "kubernetes.io/cluster/\" = "shared"
"kubernetes.io/role/elb" = "1"
"purpose" = "public" | yes | 5 | | Network Access Lists | Allows or denies specific inbound or outbound traffic at the subnet level | Allow all inbound/outbound | yes | 6 | -------------------------------------------------------------------------------- /modules/ivs_aws_instance/secrets.tf: -------------------------------------------------------------------------------- 1 | data "aws_secretsmanager_secret" "opensearch_secret" { 2 | count = var.opensearch.enable ? 1 : 0 3 | name = var.opensearch.master_user_secret_name 4 | } 5 | 6 | data "aws_secretsmanager_secret_version" "opensearch_secret" { 7 | count = var.opensearch.enable ? 1 : 0 8 | secret_id = data.aws_secretsmanager_secret.opensearch_secret[0].id 9 | } 10 | 11 | data "aws_secretsmanager_secret" "database_secrets" { 12 | name = var.database_secretname 13 | } 14 | 15 | data "aws_secretsmanager_secret_version" "database_secrets" { 16 | secret_id = data.aws_secretsmanager_secret.database_secrets.id 17 | } 18 | -------------------------------------------------------------------------------- /scripts/aws_cloud_spec_gen/static/ec2_instances.md: -------------------------------------------------------------------------------- 1 | | AMI Name | Platform | Description | Mandatory | 2 | | -------- | -------- | ----------- | --------- | 3 | | amazon-eks-node | Linux/UNIX | Default node pool instances (auto-scaled) | Yes | 4 | | amazon-eks-node | Linux/UNIX | Execution node pool instances (auto-scaled) | No | 5 | | Windows_Server-2022-English-Core-EKS_Optimized | Windows | Windows Execution node pool instances (auto-scaled) | No | 6 | | ubuntu-eks/k8s_/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server | Linux/UNIX | GPU Execution node pool instances (auto-scaled) | No | 7 | | amzn2-ami-hvm-2.0.20210813.1-x86_64-gp2 | Amazon Linux | dSPACE license server | No | 8 | -------------------------------------------------------------------------------- /modules/k8s_eks_addons/kube-proxy.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | kube_proxy_addon_name = "kube-proxy" 3 | } 4 | 5 | data "aws_eks_addon_version" "kube_proxy" { 6 | addon_name = local.kube_proxy_addon_name 7 | kubernetes_version = var.addon_context.eks_cluster_version 8 | } 9 | 10 | resource "aws_eks_addon" "kube_proxy" { 11 | cluster_name = var.addon_context.eks_cluster_id 12 | addon_name = local.kube_proxy_addon_name 13 | addon_version = data.aws_eks_addon_version.kube_proxy.version 14 | preserve = true 15 | resolve_conflicts_on_create = "OVERWRITE" 16 | resolve_conflicts_on_update = "OVERWRITE" 17 | tags = var.tags 18 | } 19 | -------------------------------------------------------------------------------- /modules/eks/cluster-auth.tf: -------------------------------------------------------------------------------- 1 | resource "kubernetes_config_map" "aws_auth" { 2 | metadata { 3 | name = "aws-auth" 4 | namespace = "kube-system" 5 | labels = merge( 6 | { 7 | "app.kubernetes.io/managed-by" = "terraform" 8 | "app.kubernetes.io/created-by" = "terraform" 9 | }, 10 | ) 11 | } 12 | 13 | data = { 14 | mapRoles = yamlencode( 15 | distinct(concat( 16 | local.node_group_aws_auth_config_map, 17 | var.map_roles, 18 | )) 19 | ) 20 | mapUsers = yamlencode(var.map_users) 21 | mapAccounts = yamlencode(var.map_accounts) 22 | } 23 | 24 | depends_on = [ 25 | aws_eks_cluster.eks, 26 | data.http.eks_cluster_readiness 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.9.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 5.60.0" 8 | # minimum version 5.60.0 is required due to argument requirements for the aws_eks_cluster resource. 9 | } 10 | 11 | kubernetes = { 12 | source = "hashicorp/kubernetes" 13 | version = ">= 2.10" 14 | } 15 | 16 | kubectl = { 17 | source = "gavinbunney/kubectl" 18 | version = ">= 1.19.0" 19 | } 20 | 21 | helm = { 22 | source = "hashicorp/helm" 23 | version = ">= 2.13.2" 24 | } 25 | 26 | random = { 27 | source = "hashicorp/random" 28 | version = ">= 3.6.2" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tfvars.hcl.terraform-docs.yml: -------------------------------------------------------------------------------- 1 | # Original template for content: https://github.com/terraform-docs/terraform-docs/blob/v0.16.0/format/templates/tfvars_hcl.tmpl 2 | 3 | formatter: asciidoc 4 | content: | 5 | {{ if .Module.Inputs -}} 6 | {{- range $index, $element := .Module.Inputs }} 7 | {{ if $element.Description -}} 8 | {{ if gt (len $element.Description) 120 }} 9 | {{- range $value := (tostring $element.Description | split ". ") }}# {{ trimSuffix "." $value }}. 10 | {{ end -}} 11 | {{ else -}} 12 | # {{ tostring $element.Description }} 13 | {{ end -}} 14 | {{ end -}} 15 | {{- $element.Name }} = {{ $element.GetValue }} 16 | {{ end }} 17 | {{- end -}} 18 | settings: 19 | description: true 20 | -------------------------------------------------------------------------------- /modules/simphera_aws_instance/templates/minio-policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version" : "2012-10-17", 3 | "Statement" : [ 4 | { 5 | "Effect" : "Allow", 6 | "Action" : [ 7 | "s3:GetBucketLocation" 8 | ], 9 | "Resource" : [ 10 | "arn:aws:s3:::*" 11 | ] 12 | }, 13 | { 14 | "Effect" : "Allow", 15 | "Action" : [ 16 | "s3:PutObject", 17 | "s3:GetObject", 18 | "s3:ListBucket", 19 | "s3:DeleteObject", 20 | "s3:HeadBucket" 21 | ], 22 | "Resource" : [ 23 | "arn:aws:s3:::${bucket}", 24 | "arn:aws:s3:::${bucket}/*" 25 | ] 26 | }, 27 | { 28 | "Effect" : "Allow", 29 | "Action" : "s3:ListAllMyBuckets", 30 | "Resource" : "*" 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /modules/simphera_aws_instance/locals.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | eks_oidc_issuer = replace(var.eks_oidc_issuer_url, "https://", "") 3 | minio_serviceaccount = "minio-irsa" 4 | secret_postgres_username = "dbuser" # username is hardcoded because changing the username forces replacement of the db instance 5 | secrets = jsondecode(data.aws_secretsmanager_secret_version.secrets.secret_string) 6 | instancename = join("-", [var.infrastructurename, var.name]) 7 | backup_resources = concat([aws_db_instance.simphera.arn], var.enableKeycloak ? [aws_db_instance.keycloak[0].arn] : [], [aws_s3_bucket.bucket.arn]) 8 | db_simphera_id = "${local.instancename}-simphera" 9 | db_keycloak_id = "${local.instancename}-keycloak" 10 | backup_vault_name = "${local.instancename}-backup-vault" 11 | } 12 | -------------------------------------------------------------------------------- /modules/eks/modules/node-group/iam.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_role" "node_group" { 2 | name = "${var.node_group_context.eks_cluster_id}-${var.node_group_name}" 3 | description = "EKS Managed Node group IAM Role" 4 | assume_role_policy = data.aws_iam_policy_document.node_group_assume_role_policy.json 5 | force_detach_policies = true 6 | tags = var.tags 7 | } 8 | 9 | resource "aws_iam_instance_profile" "node_group" { 10 | name = "${var.node_group_context.eks_cluster_id}-${var.node_group_name}" 11 | role = aws_iam_role.node_group.name 12 | tags = var.tags 13 | lifecycle { 14 | create_before_destroy = true 15 | } 16 | } 17 | 18 | resource "aws_iam_role_policy_attachment" "node_group" { 19 | for_each = local.eks_worker_policies 20 | policy_arn = each.key 21 | role = aws_iam_role.node_group.id 22 | } 23 | -------------------------------------------------------------------------------- /modules/k8s_eks_addons/templates/nginx_values.yaml: -------------------------------------------------------------------------------- 1 | controller: 2 | service: 3 | annotations: 4 | service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags: "${tags}" 5 | service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "${protocol}" 6 | service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: '60' 7 | service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: 'true' 8 | service.beta.kubernetes.io/aws-load-balancer-target-node-labels: kubernetes.io/os=linux 9 | service.beta.kubernetes.io/aws-load-balancer-subnets: "${public_subnets}" 10 | service.beta.kubernetes.io/aws-load-balancer-type: "${aws_load_balancer_type}" 11 | ${aws_load_target-type} 12 | metrics: 13 | enabled: true 14 | port: 10254 15 | portName: metrics 16 | serviceMonitor: 17 | enabled: false 18 | -------------------------------------------------------------------------------- /modules/k8s_eks_addons/coredns.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | coredns_addon_name = "coredns" 3 | } 4 | 5 | data "aws_eks_addon_version" "coredns" { 6 | count = var.coredns_config.enable ? 1 : 0 7 | addon_name = local.coredns_addon_name 8 | kubernetes_version = var.addon_context.eks_cluster_version 9 | } 10 | 11 | resource "aws_eks_addon" "coredns" { 12 | count = var.coredns_config.enable ? 1 : 0 13 | cluster_name = var.addon_context.eks_cluster_id 14 | addon_name = local.coredns_addon_name 15 | addon_version = data.aws_eks_addon_version.coredns[0].version 16 | preserve = true 17 | resolve_conflicts_on_create = "OVERWRITE" 18 | resolve_conflicts_on_update = "OVERWRITE" 19 | configuration_values = var.coredns_config.configuration_values 20 | tags = var.tags 21 | } 22 | -------------------------------------------------------------------------------- /modules/simphera_aws_instance/outputs.tf: -------------------------------------------------------------------------------- 1 | 2 | output "backup_vaults" { 3 | description = "Backups vaults created for the SIMPHERA instance." 4 | value = [aws_backup_vault.backup-vault[*].name] 5 | } 6 | 7 | output "database_identifiers" { 8 | description = "Identifiers of the SIMPHERA and Keycloak databases created for this SIMPHERA instance." 9 | value = [aws_db_instance.simphera.identifier, var.enableKeycloak ? aws_db_instance.keycloak[0].identifier : ""] 10 | } 11 | 12 | output "database_endpoints" { 13 | description = "Endpoints of the SIMPHERA and Keycloak databases created for this SIMPHERA instance." 14 | value = [aws_db_instance.simphera.endpoint, var.enableKeycloak ? aws_db_instance.keycloak[0].endpoint : ""] 15 | } 16 | 17 | output "s3_buckets" { 18 | description = "S3 buckets created for this SIMPHERA instance." 19 | value = [aws_s3_bucket.bucket.bucket] 20 | } 21 | -------------------------------------------------------------------------------- /scripts/aws_cloud_spec_gen/static/vpc_requirements.md: -------------------------------------------------------------------------------- 1 | | Requirement | Description | Default value | Mandatory? | 2 | | ----------- | ----------- | -------------- | ---------- | 3 | | IPv4 CIDR block | Network size ie. number of available IPs in VPC | 10.1.0.0/18 | yes | 4 | | Availability zones | How many AZs to spread VPC across | 3 (at least 2 for high availability) | yes | 5 | | Private subnets | How many private subnets to create | 3 (at least 2 for high availability; one per each AZ) | yes | 6 | | Public subnets | How many public subnets to create | 3 (at least 2 for high availability; one per each AZ) | yes | 7 | | NAT gateway | Enable/disable NAT in VPC | enable | yes | 8 | | Single NAT gateway | Controls how many NAT gateways/Elastic IPs to provision | enable | no | 9 | | Internet gateway | Enable/disable IGW in VPC | enable | yes | 10 | | DNS hostnames | Determines whether the VPC supports assigning public DNS hostnames to instances with public IP addresses. | enable | yes | 11 | -------------------------------------------------------------------------------- /modules/ivs_aws_instance/outputs.tf: -------------------------------------------------------------------------------- 1 | output "opensearch_domain_endpoint" { 2 | description = "OpenSearch Domain endpoint" 3 | value = try(aws_opensearch_domain.opensearch[0].endpoint, null) 4 | } 5 | 6 | output "backup_vaults" { 7 | description = "Backups vaults created for the IVS instance." 8 | value = [aws_backup_vault.backup_vault[*].name] 9 | } 10 | 11 | output "database_identifiers" { 12 | description = "Identifiers of the IVS database created for this IVS instance." 13 | value = [var.enableIVSAuthentication ? aws_db_instance.ivs_authentication[0].identifier : ""] 14 | } 15 | 16 | output "database_endpoints" { 17 | description = "Endpoints of the IVS database created for this IVS instance." 18 | value = [var.enableIVSAuthentication ? aws_db_instance.ivs_authentication[0].endpoint : ""] 19 | } 20 | 21 | output "ivs_buckets_service_account" { 22 | description = "K8s service account name with access to the IVS buckets" 23 | value = local.ivs_buckets_service_account 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2021 dSPACE GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /k8s-eks-addons.tf: -------------------------------------------------------------------------------- 1 | module "k8s_eks_addons" { 2 | source = "./modules/k8s_eks_addons" 3 | 4 | ingress_nginx_config = merge(var.ingress_nginx_config, { subnets_ids = local.public_subnets }) 5 | cluster_autoscaler_config = var.cluster_autoscaler_config 6 | coredns_config = var.coredns_config 7 | efs_csi_config = var.efs_csi_config 8 | s3_csi_config = var.s3_csi_config 9 | aws_load_balancer_controller_config = var.aws_load_balancer_controller_config 10 | gpu_operator_config = merge( 11 | var.gpu_operator_config, 12 | var.ivsGpuNodePool ? { driver_versions = distinct(concat(var.gpu_operator_config.driver_versions, [var.ivsGpuDriverVersion])) } : {} 13 | ) 14 | addon_context = { 15 | aws_context = local.aws_context 16 | eks_cluster_id = module.eks.eks_cluster_id 17 | eks_cluster_version = module.eks.eks_cluster_version 18 | eks_oidc_issuer_url = replace(module.eks.eks_oidc_issuer_url, "https://", "") 19 | } 20 | tags = var.tags 21 | 22 | depends_on = [module.eks.node_groups] 23 | } 24 | -------------------------------------------------------------------------------- /modules/eks/node-groups.tf: -------------------------------------------------------------------------------- 1 | module "node_group" { 2 | source = "./modules/node-group" 3 | for_each = var.node_groups 4 | node_group_name = each.value.node_group_name 5 | subnet_ids = each.value.subnet_ids 6 | worker_security_group_ids = [aws_eks_cluster.eks.vpc_config[0].cluster_security_group_id] 7 | instance_types = each.value.instance_types 8 | capacity_type = each.value.capacity_type 9 | max_size = each.value.max_size 10 | min_size = each.value.min_size 11 | ami_type = each.value.ami_type 12 | custom_ami_id = try(each.value.custom_ami_id, null) 13 | block_device_name = each.value.block_device_name 14 | volume_size = each.value.volume_size 15 | k8s_labels = try(each.value.k8s_labels, {}) 16 | k8s_taints = try(each.value.k8s_taints, []) 17 | node_group_context = local.node_group_context 18 | tags = var.tags 19 | 20 | depends_on = [kubernetes_config_map.aws_auth] 21 | } 22 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: local 3 | hooks: 4 | - id: terraform_format 5 | name: terraform_format 6 | entry: --entrypoint /src/hooks/terraform_format.sh hashicorp/terraform:1.9.0 7 | language: docker_image 8 | - repo: local 9 | hooks: 10 | - id: terraform_validate 11 | name: terraform_validate 12 | entry: "--entrypoint /src/hooks/terraform_validate.sh -v tfvalidate:/src/cache hashicorp/terraform:1.9.0" 13 | language: docker_image 14 | - repo: local 15 | hooks: 16 | - id: tflint 17 | name: tflint 18 | entry: --entrypoint /src/hooks/tflint.sh ghcr.io/terraform-linters/tflint:v0.52.0 19 | language: docker_image 20 | - repo: local 21 | hooks: 22 | - id: tfsec 23 | name: tfsec 24 | entry: --entrypoint /src/hooks/tfsec.sh aquasec/tfsec:v1.28 25 | language: docker_image 26 | - repo: local 27 | hooks: 28 | - id: terraform_docs 29 | name: terraform_docs 30 | entry: --entrypoint /src/hooks/terraform_docs.sh quay.io/terraform-docs/terraform-docs:0.16.0 31 | language: docker_image 32 | -------------------------------------------------------------------------------- /modules/eks/modules/node-group/locals.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | policy_arn_prefix = "arn:${var.node_group_context.aws_context.partition_id}:iam::aws:policy" 3 | ec2_principal = "ec2.${var.node_group_context.aws_context.partition_dns_suffix}" 4 | eks_worker_policies = { 5 | for k, v in toset(concat([ 6 | "${local.policy_arn_prefix}/AmazonEKSWorkerNodePolicy", 7 | "${local.policy_arn_prefix}/AmazonEKS_CNI_Policy", 8 | "${local.policy_arn_prefix}/AmazonEC2ContainerRegistryReadOnly", 9 | "${local.policy_arn_prefix}/AmazonSSMManagedInstanceCore"], 10 | )) : k => v 11 | } 12 | common_tags = merge( 13 | var.tags, 14 | { 15 | Name = "${var.node_group_context.eks_cluster_id}-${var.node_group_name}" 16 | "kubernetes.io/cluster/${var.node_group_context.eks_cluster_id}" = "owned" 17 | "k8s.io/cluster-autoscaler/${var.node_group_context.eks_cluster_id}" = "owned" 18 | "k8s.io/cluster-autoscaler/enabled" = "TRUE" 19 | "managed-by" = "terraform" 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | # VS Code 3 | It is recommended to use VS Code for the development of the reference architecture. 4 | The extension that is used for Terraform development is [hashicorp.terraform](https://marketplace.visualstudio.com/items?itemName=HashiCorp.terraform). 5 | The `.vscode` folder contains a `settings.json` applies the canonial formatting on every `.tf` and `.tfvars` inside the workspace. 6 | 7 | # Pre-commit hook 8 | 9 | It is also recommended to install the [pre-commit](https://pre-commit.com/) hook on your development machine. 10 | You can install the hook using these commands on Windows: 11 | 12 | ```powershell 13 | choco install python 14 | pip install pre-commit==3.4.0 15 | pre-commit install 16 | ``` 17 | 18 | The hook is configured in `.pre-commit-config.yaml`. 19 | The hooks calls [`terraform fmt`](https://developer.hashicorp.com/terraform/cli/commands/fmt) and [`terraform validate`](https://developer.hashicorp.com/terraform/cli/commands/validate) on every `git commit`. 20 | 21 | # GitHub Workflow 22 | 23 | In addition, there is a GitHub Workflow defined in `.github/workflows/qualitygate.yml` that runs `terraform fmt` and `terraform validate` when a pull request is created. 24 | -------------------------------------------------------------------------------- /modules/ivs_aws_instance/locals.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | master_user_secret = var.opensearch.enable ? jsondecode(data.aws_secretsmanager_secret_version.opensearch_secret[0].secret_string) : null 3 | secret_postgres_username = "dbuser" # username is hardcoded because changing the username forces replacement of the db instance 4 | database_secretname = jsondecode(data.aws_secretsmanager_secret_version.database_secrets.secret_string) 5 | db_ivs_id = "ivs-authentication-${var.instancename}" 6 | instance_identifier = "${var.eks_cluster_id}-${var.instancename}-${var.k8s_namespace}" 7 | goofys_user_agent_name = "aws:UserAgent\": \"aws-sdk-go/${var.goofys_user_agent_sdk_and_go_version["sdk_version"]} (go${var.goofys_user_agent_sdk_and_go_version["go_version"]}; linux; amd64)" 8 | ivs_buckets_service_account = "${local.instance_identifier}-sa" 9 | data_bucket_arn = "arn:aws:s3:::${var.data_bucket.name}" 10 | raw_data_bucket_arn = "arn:aws:s3:::${var.raw_data_bucket.name}" 11 | managed_buckets = concat(var.data_bucket.create ? [local.data_bucket_arn] : [], var.raw_data_bucket.create ? [local.raw_data_bucket_arn] : []) 12 | } 13 | -------------------------------------------------------------------------------- /k8s.tf: -------------------------------------------------------------------------------- 1 | module "eks" { 2 | source = "./modules/eks" 3 | cluster_version = var.kubernetesVersion 4 | cluster_name = var.infrastructurename 5 | subnet_ids = local.private_subnets 6 | eks_api_subnet_ids = var.eks_api_subnet_ids 7 | node_groups = local.node_pools 8 | map_accounts = var.map_accounts 9 | map_users = var.map_users 10 | map_roles = var.map_roles 11 | cloudwatch_log_group_kms_key_id = var.aws_managed_kms ? null : aws_kms_key.kms_key_cloudwatch_log_group[0].arn 12 | cloudwatch_log_group_retention_in_days = var.cloudwatch_retention 13 | aws_context = local.aws_context 14 | tags = var.tags 15 | aws_managed_kms = var.aws_managed_kms 16 | 17 | depends_on = [module.vpc] 18 | } 19 | 20 | resource "kubernetes_namespace" "monitoring_namespace" { 21 | metadata { 22 | name = var.simphera_monitoring_namespace 23 | } 24 | depends_on = [module.k8s_eks_addons] 25 | } 26 | -------------------------------------------------------------------------------- /data.tf: -------------------------------------------------------------------------------- 1 | data "aws_availability_zones" "available" { 2 | state = "available" 3 | } 4 | 5 | data "aws_caller_identity" "current" {} 6 | 7 | data "aws_partition" "current" {} 8 | 9 | data "aws_region" "current" {} 10 | 11 | data "aws_iam_session_context" "current" { 12 | arn = data.aws_caller_identity.current.arn 13 | } 14 | 15 | data "aws_vpc" "preconfigured" { 16 | count = local.create_vpc ? 0 : 1 17 | id = var.vpcId 18 | } 19 | 20 | data "aws_subnets" "private_subnets" { 21 | count = local.create_vpc ? 0 : 1 22 | filter { 23 | name = "vpc-id" 24 | values = [var.vpcId] 25 | } 26 | 27 | tags = { 28 | purpose = "private" 29 | } 30 | } 31 | 32 | data "aws_subnet" "private_subnet" { 33 | for_each = local.create_vpc ? toset([]) : toset(data.aws_subnets.private_subnets[0].ids) 34 | id = each.value 35 | } 36 | 37 | data "aws_subnets" "public_subnets" { 38 | count = local.create_vpc ? 0 : 1 39 | filter { 40 | name = "vpc-id" 41 | values = [var.vpcId] 42 | } 43 | 44 | tags = { 45 | purpose = "public" 46 | } 47 | } 48 | 49 | data "aws_subnet" "public_subnet" { 50 | for_each = local.create_vpc ? toset([]) : toset(data.aws_subnets.public_subnets[0].ids) 51 | id = each.value 52 | } 53 | -------------------------------------------------------------------------------- /modules/eks/iam.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_role" "cluster_role" { 2 | name = local.cluster_iam_role_name 3 | path = null 4 | description = "AWS IAM role that provides permissions for the Kubernetes control plane to make calls to AWS API operations." 5 | assume_role_policy = data.aws_iam_policy_document.assume_role_policy.json 6 | permissions_boundary = null 7 | force_detach_policies = true 8 | 9 | # cloudwatch related inline_policy 10 | dynamic "inline_policy" { 11 | for_each = var.create_cloudwatch_log_group ? [1] : [] 12 | content { 13 | name = local.cluster_iam_role_name 14 | 15 | policy = jsonencode({ 16 | Version = "2012-10-17" 17 | Statement = [ 18 | { 19 | Action = ["logs:CreateLogGroup"] 20 | Effect = "Deny" 21 | Resource = aws_cloudwatch_log_group.log_group[0].arn 22 | }, 23 | ] 24 | }) 25 | } 26 | } 27 | 28 | tags = var.tags 29 | } 30 | 31 | resource "aws_iam_role_policy_attachment" "cluster_role" { 32 | for_each = toset([ 33 | "${local.policy_arn_prefix}/AmazonEKSClusterPolicy", 34 | "${local.policy_arn_prefix}/AmazonEKSVPCResourceController", 35 | ]) 36 | policy_arn = each.value 37 | role = aws_iam_role.cluster_role.name 38 | } 39 | -------------------------------------------------------------------------------- /modules/eks/modules/node-group/templates/userdata.tpl: -------------------------------------------------------------------------------- 1 | MIME-Version: 1.0 2 | Content-Type: multipart/mixed; boundary="//" 3 | 4 | --// 5 | Content-Type: text/x-shellscript; charset="us-ascii" 6 | #!/bin/bash 7 | set -ex 8 | 9 | %{ if length(pre_userdata) > 0 ~} 10 | # User-supplied pre userdata 11 | ${pre_userdata} 12 | %{ endif ~} 13 | %{ if format_mount_nvme_disk ~} 14 | echo "Format and Mount NVMe Disks if available" 15 | IDX=1 16 | DEVICES=$(lsblk -o NAME,TYPE -dsn | awk '/disk/ {print $1}') 17 | 18 | for DEV in $DEVICES 19 | do 20 | mkfs.xfs /dev/$${DEV} 21 | mkdir -p /local$${IDX} 22 | 23 | echo /dev/$${DEV} /local$${IDX} xfs defaults,noatime 1 2 >> /etc/fstab 24 | 25 | IDX=$(($${IDX} + 1)) 26 | done 27 | mount -a 28 | %{ endif ~} 29 | %{ if length(service_ipv4_cidr) > 0 ~} 30 | export SERVICE_IPV4_CIDR=${service_ipv4_cidr} 31 | %{ endif ~} 32 | %{ if length(service_ipv6_cidr) > 0 ~} 33 | export SERVICE_IPV6_CIDR=${service_ipv6_cidr} 34 | %{ endif ~} 35 | %{ if length(custom_ami_id) > 0 ~} 36 | B64_CLUSTER_CA=${cluster_ca_base64} 37 | API_SERVER_URL=${cluster_endpoint} 38 | /etc/eks/bootstrap.sh ${eks_cluster_id} --kubelet-extra-args "${kubelet_extra_args}" ${bootstrap_extra_args} 39 | %{ endif ~} 40 | %{ if length(post_userdata) > 0 ~} 41 | # User-supplied post userdata 42 | ${post_userdata} 43 | %{ endif ~} 44 | --//-- 45 | -------------------------------------------------------------------------------- /modules/eks/outputs.tf: -------------------------------------------------------------------------------- 1 | output "cluster_primary_security_group_id" { 2 | description = "Cluster security group that was created by Amazon EKS for the cluster. Managed node groups use this security group for control-plane-to-data-plane communication. Referred to as 'Cluster security group' in the EKS console" 3 | value = aws_eks_cluster.eks.vpc_config[0].cluster_security_group_id 4 | } 5 | 6 | output "eks_cluster_id" { 7 | description = "The name/id of the EKS cluster. Will block on cluster creation until the cluster is really ready" 8 | value = aws_eks_cluster.eks.id 9 | } 10 | 11 | output "eks_cluster_version" { 12 | description = "" 13 | value = aws_eks_cluster.eks.version 14 | } 15 | 16 | output "eks_oidc_issuer" { 17 | description = "The OpenID Connect identity provider issuer" 18 | value = aws_eks_cluster.eks.identity[0].oidc[0].issuer 19 | } 20 | 21 | output "eks_oidc_issuer_url" { 22 | description = "The URL on the EKS cluster OIDC Issuer" 23 | value = local.eks_oidc_issuer_url 24 | } 25 | 26 | output "eks_oidc_provider_arn" { 27 | description = "The ARN of the OIDC Provider" 28 | value = aws_iam_openid_connect_provider.oidc_provider.arn 29 | } 30 | 31 | output "node_groups" { 32 | description = "Outputs from EKS Managed node groups" 33 | value = module.node_group[*] 34 | } 35 | -------------------------------------------------------------------------------- /templates/license_server_policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": [ 7 | "s3:ListBucket" 8 | ], 9 | "Resource": "arn:aws:s3:::${bucket}" 10 | }, 11 | { 12 | "Effect": "Allow", 13 | "Action": [ 14 | "s3:PutObject", 15 | "s3:GetObject", 16 | "s3:DeleteObject" 17 | ], 18 | "Resource": "arn:aws:s3:::${bucket}/*" 19 | }, 20 | { 21 | "Effect": "Allow", 22 | "Action": [ 23 | "ssm:UpdateInstanceInformation", 24 | "ssmmessages:CreateControlChannel", 25 | "ssmmessages:CreateDataChannel", 26 | "ssmmessages:OpenControlChannel", 27 | "ssmmessages:OpenDataChannel" 28 | ], 29 | "Resource": "*" 30 | }, 31 | { 32 | "Effect": "Allow", 33 | "Action": [ 34 | "s3:GetEncryptionConfiguration" 35 | ], 36 | "Resource": "*" 37 | }, 38 | { 39 | "Action": [ 40 | "logs:PutLogEvents", 41 | "logs:DescribeLogStreams", 42 | "logs:CreateLogStream", 43 | "logs:CreateLogGroup" 44 | ], 45 | "Effect": "Allow", 46 | "Resource": "*", 47 | "Sid": "" 48 | } 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /modules/k8s_eks_addons/ingress-nginx.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | string_tags = join(",", [for key, value in var.tags : "${key}=${value}"]) 3 | } 4 | resource "kubernetes_namespace_v1" "ingress_nginx" { 5 | count = var.ingress_nginx_config.enable ? 1 : 0 6 | 7 | metadata { 8 | name = "nginx" 9 | } 10 | } 11 | 12 | resource "helm_release" "ingress_nginx" { 13 | count = var.ingress_nginx_config.enable ? 1 : 0 14 | 15 | namespace = kubernetes_namespace_v1.ingress_nginx[0].metadata[0].name 16 | name = "ingress-nginx" 17 | chart = "ingress-nginx" 18 | repository = var.ingress_nginx_config.helm_repository 19 | version = var.ingress_nginx_config.helm_version 20 | description = "The NGINX HelmChart Ingress Controller deployment configuration" 21 | dependency_update = true 22 | values = [ 23 | templatefile("${path.module}/templates/nginx_values.yaml", { 24 | tags = local.string_tags 25 | public_subnets = join(", ", var.ingress_nginx_config.subnets_ids) 26 | protocol = var.aws_load_balancer_controller_config.enable ? "ssl" : "tcp" 27 | aws_load_balancer_type = var.aws_load_balancer_controller_config.enable ? "external" : "nlb" 28 | aws_load_target-type = var.aws_load_balancer_controller_config.enable ? "service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip" : "" 29 | }), 30 | var.ingress_nginx_config.chart_values 31 | ] 32 | timeout = 1200 33 | depends_on = [helm_release.aws_load_balancer_controller] 34 | } 35 | -------------------------------------------------------------------------------- /modules/eks/locals.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | cluster_iam_role_name = "${var.cluster_name}-cluster-role" 3 | cluster_iam_role_pathed_arn = "arn:${var.aws_context.partition_id}:iam::${var.aws_context.caller_identity_account_id}:role/${local.cluster_iam_role_name}" 4 | policy_arn_prefix = "arn:${var.aws_context.partition}:iam::aws:policy" 5 | eks_api_subnet_ids = length(var.eks_api_subnet_ids) > 0 ? var.eks_api_subnet_ids : var.subnet_ids 6 | node_group_context = { 7 | # EKS Cluster Config 8 | eks_cluster_id = aws_eks_cluster.eks.id 9 | cluster_ca_base64 = aws_eks_cluster.eks.certificate_authority[0].data 10 | cluster_endpoint = aws_eks_cluster.eks.endpoint 11 | cluster_version = var.cluster_version 12 | 13 | # Data sources 14 | aws_context = var.aws_context 15 | } 16 | node_group_aws_auth_config_map = [ 17 | for node in var.node_groups : { 18 | rolearn : "arn:${var.aws_context.partition_id}:iam::${var.aws_context.caller_identity_account_id}:role/${aws_eks_cluster.eks.id}-${node.node_group_name}" 19 | username : "system:node:{{EC2PrivateDNSName}}" 20 | groups : concat( 21 | ["system:bootstrappers", "system:nodes"], 22 | strcontains(node.ami_type, "WINDOWS") ? ["eks:kube-proxy-windows"] : [] 23 | ) 24 | } 25 | ] 26 | windows_enabled = anytrue([for node in var.node_groups : strcontains(node.ami_type, "WINDOWS")]) 27 | windows_vpc_cni_configuration = <<-YAML 28 | enableWindowsIpam: "true" 29 | enableWindowsPrefixDelegation: "true" 30 | YAML 31 | } 32 | -------------------------------------------------------------------------------- /modules/k8s_eks_addons/gpu-operator.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | gpu_driver_versions_escaped = { for driver in var.gpu_operator_config.driver_versions : driver => replace(driver, ".", "-") if var.gpu_operator_config.enable } 3 | } 4 | 5 | 6 | resource "helm_release" "gpu_operator" { 7 | count = var.gpu_operator_config.enable ? 1 : 0 8 | 9 | namespace = "kube-system" 10 | name = "gpu-operator" 11 | chart = "gpu-operator" 12 | create_namespace = true 13 | repository = var.gpu_operator_config.helm_repository 14 | version = var.gpu_operator_config.helm_version 15 | description = "The GPU operator HelmChart deployment configuration" 16 | dependency_update = true 17 | values = [ 18 | var.gpu_operator_config.chart_values 19 | ] 20 | timeout = 1200 21 | wait = false 22 | } 23 | 24 | # kubernetes_manifest from hashicorp/kubernetes provider doesnt work with custom resources yet, 25 | # see https://github.com/hashicorp/terraform-provider-kubernetes/issues/1775 for more information 26 | resource "kubectl_manifest" "nvidia-driver" { 27 | for_each = local.gpu_driver_versions_escaped 28 | 29 | yaml_body = < 1 ? true : false 27 | dynamic "zone_awareness_config" { 28 | for_each = var.opensearch.instance_count > 1 ? [1] : [] 29 | content { 30 | availability_zone_count = var.opensearch.instance_count < 3 ? var.opensearch.instance_count : 3 31 | } 32 | } 33 | } 34 | ebs_options { 35 | ebs_enabled = true 36 | volume_type = "gp3" 37 | volume_size = var.opensearch.volume_size 38 | iops = 3000 39 | throughput = 125 40 | } 41 | vpc_options { 42 | subnet_ids = slice(var.opensearch.subnet_ids, 0, var.opensearch.instance_count < 3 ? var.opensearch.instance_count : 3) 43 | 44 | security_group_ids = var.opensearch.security_group_ids 45 | } 46 | access_policies = data.aws_iam_policy_document.opensearch_access[0].json 47 | tags = var.tags 48 | } 49 | -------------------------------------------------------------------------------- /efs.tf: -------------------------------------------------------------------------------- 1 | resource "aws_efs_file_system" "efs_file_system" { 2 | count = local.create_efs 3 | encrypted = true 4 | tags = var.tags 5 | } 6 | 7 | data "aws_iam_policy_document" "policy" { 8 | count = local.create_efs 9 | statement { 10 | sid = "EfsPolicy" 11 | effect = "Allow" 12 | 13 | principals { 14 | type = "AWS" 15 | identifiers = ["*"] 16 | } 17 | 18 | actions = [ 19 | "elasticfilesystem:ClientMount", 20 | "elasticfilesystem:ClientWrite", 21 | "elasticfilesystem:CreateAccessPoint", 22 | "elasticfilesystem:TagResource", 23 | ] 24 | 25 | resources = [aws_efs_file_system.efs_file_system[0].arn] 26 | 27 | condition { 28 | test = "Bool" 29 | variable = "aws:SecureTransport" 30 | values = ["true"] 31 | } 32 | } 33 | } 34 | 35 | resource "aws_efs_file_system_policy" "policy" { 36 | count = local.create_efs 37 | file_system_id = aws_efs_file_system.efs_file_system[0].id 38 | policy = data.aws_iam_policy_document.policy[0].json 39 | } 40 | 41 | resource "aws_efs_mount_target" "mount_target" { 42 | for_each = local.storage_subnets 43 | file_system_id = aws_efs_file_system.efs_file_system[0].id 44 | subnet_id = each.value 45 | security_groups = [module.eks.cluster_primary_security_group_id] 46 | } 47 | 48 | resource "kubernetes_storage_class_v1" "efs" { 49 | count = local.create_efs 50 | metadata { 51 | name = "efs" 52 | } 53 | 54 | storage_provisioner = "efs.csi.aws.com" 55 | parameters = { 56 | provisioningMode = "efs-ap" # Dynamic provisioning 57 | fileSystemId = aws_efs_file_system.efs_file_system[0].id 58 | directoryPerms = "700" 59 | } 60 | 61 | mount_options = [ 62 | "iam" 63 | ] 64 | 65 | depends_on = [ 66 | module.k8s_eks_addons 67 | ] 68 | } 69 | -------------------------------------------------------------------------------- /simphera-instances.tf: -------------------------------------------------------------------------------- 1 | module "simphera_instance" { 2 | source = "./modules/simphera_aws_instance" 3 | for_each = var.simpheraInstances 4 | backup_retention = each.value.backup_retention 5 | cloudwatch_retention = var.cloudwatch_retention 6 | db_instance_type_keycloak = each.value.db_instance_type_keycloak 7 | db_instance_type_simphera = each.value.db_instance_type_simphera 8 | eks_oidc_issuer_url = module.eks.eks_oidc_issuer_url 9 | eks_oidc_provider_arn = module.eks.eks_oidc_provider_arn 10 | enable_backup_service = each.value.enable_backup_service 11 | enable_deletion_protection = each.value.enable_deletion_protection 12 | enableKeycloak = each.value.enable_keycloak 13 | infrastructurename = local.infrastructurename 14 | k8s_namespace = each.value.k8s_namespace 15 | kms_key_cloudwatch = var.aws_managed_kms ? null : aws_kms_key.kms_key_cloudwatch_log_group[0].arn 16 | log_bucket = aws_s3_bucket.bucket_logs.id 17 | name = each.value.name 18 | postgresql_security_group_id = module.security_group.security_group_id 19 | postgresqlApplyImmediately = each.value.postgresqlApplyImmediately 20 | postgresqlMaxStorage = each.value.postgresqlMaxStorage 21 | postgresqlMaxStorageKeycloak = each.value.postgresqlMaxStorageKeycloak 22 | postgresqlStorage = each.value.postgresqlStorage 23 | postgresqlStorageKeycloak = each.value.postgresqlStorageKeycloak 24 | postgresqlVersion = each.value.postgresqlVersion 25 | private_subnets = local.private_subnets 26 | region = local.region 27 | secretname = each.value.secretname 28 | tags = var.tags 29 | depends_on = [module.eks, kubernetes_storage_class_v1.efs] 30 | } 31 | -------------------------------------------------------------------------------- /providers.tf.example: -------------------------------------------------------------------------------- 1 | data "aws_eks_cluster" "cluster" { 2 | name = module.eks.eks_cluster_id 3 | } 4 | 5 | data "aws_eks_cluster_auth" "cluster" { 6 | name = module.eks.eks_cluster_id 7 | } 8 | 9 | provider "aws" { 10 | profile = "change_me" 11 | region = "change_me" 12 | } 13 | 14 | provider "kubernetes" { 15 | host = data.aws_eks_cluster.cluster.endpoint 16 | cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority[0].data) 17 | 18 | # https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs#exec-plugins 19 | exec { 20 | api_version = "client.authentication.k8s.io/v1beta1" 21 | args = ["eks", "get-token", "--profile", "change_me", "--cluster-name", data.aws_eks_cluster.cluster.name] 22 | command = "aws" 23 | } 24 | } 25 | 26 | provider "helm" { 27 | kubernetes { 28 | host = data.aws_eks_cluster.cluster.endpoint 29 | cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority[0].data) 30 | token = data.aws_eks_cluster_auth.cluster.token 31 | 32 | # Uncomment if you run into Helm timeout issues on Linux 33 | #exec { 34 | # api_version = "client.authentication.k8s.io/v1beta1" 35 | # args = ["eks", "--profile=change_me", "get-token", "--cluster-name", data.aws_eks_cluster.cluster.name] 36 | # command = "aws" 37 | #} 38 | } 39 | } 40 | 41 | provider "kubectl" { 42 | host = data.aws_eks_cluster.cluster.endpoint 43 | cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority[0].data) 44 | token = data.aws_eks_cluster_auth.cluster.token 45 | load_config_file = false 46 | exec { 47 | api_version = "client.authentication.k8s.io/v1beta1" 48 | args = ["eks", "get-token", "--profile", "change_me", "--cluster-name", data.aws_eks_cluster.cluster.name] 49 | command = "aws" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /modules/eks/cluster.tf: -------------------------------------------------------------------------------- 1 | resource "aws_eks_cluster" "eks" { 2 | name = var.cluster_name 3 | role_arn = aws_iam_role.cluster_role.arn 4 | version = var.cluster_version 5 | enabled_cluster_log_types = var.cluster_enabled_log_types 6 | bootstrap_self_managed_addons = false 7 | 8 | vpc_config { 9 | subnet_ids = local.eks_api_subnet_ids 10 | endpoint_private_access = false 11 | endpoint_public_access = true #tfsec:ignore:aws-eks-no-public-cluster-access 12 | public_access_cidrs = ["0.0.0.0/0"] #tfsec:ignore:aws-eks-no-public-cluster-access-to-cidr 13 | # desired behaviour is to have a public access to the cluster 14 | } 15 | 16 | kubernetes_network_config { 17 | ip_family = "ipv4" 18 | } 19 | 20 | dynamic "encryption_config" { 21 | for_each = var.aws_managed_kms ? [] : [1] 22 | content { 23 | resources = ["secrets"] 24 | provider { 25 | key_arn = aws_kms_key.cluster[0].arn 26 | } 27 | } 28 | } 29 | access_config { 30 | authentication_mode = "CONFIG_MAP" 31 | bootstrap_cluster_creator_admin_permissions = true 32 | } 33 | tags = var.tags 34 | 35 | 36 | timeouts { 37 | create = var.cluster_timeouts["create"] 38 | update = var.cluster_timeouts["update"] 39 | delete = var.cluster_timeouts["delete"] 40 | } 41 | 42 | lifecycle { 43 | ignore_changes = [ 44 | bootstrap_self_managed_addons, 45 | access_config[0].bootstrap_cluster_creator_admin_permissions 46 | ] 47 | } 48 | 49 | depends_on = [ 50 | aws_iam_role_policy_attachment.cluster_role, 51 | aws_cloudwatch_log_group.log_group 52 | ] 53 | } 54 | 55 | resource "aws_ec2_tag" "cluster_primary_security_group" { 56 | for_each = { for k, v in var.tags : k => v if k != "Name" } 57 | 58 | resource_id = aws_eks_cluster.eks.vpc_config[0].cluster_security_group_id 59 | key = each.key 60 | value = each.value 61 | } 62 | -------------------------------------------------------------------------------- /scripts/aws_cloud_spec_gen/README.md: -------------------------------------------------------------------------------- 1 | # AWS Cloud spec generation 2 | 3 | This script is used for generating `AWSCloudSpec.md` of the current state of infrastructure. 4 | Using AWS SDK cli, script is filtering AWS resources by the tag `Cluster` and generate `AWSCloudSpec.md`. 5 | 6 | ## Prerequisit 7 | - reference architecture is deployed with everything enabled 8 | - `var.tags` must contain key `Cluster`, with the name of EKS cluster being deployed as a value 9 | - since script is replacing custom names with generic strings your `*.tfvars` must not have same string set for certain resources, and neither of them cannot be substring of the other, e.g.: 10 | ```terraform 11 | infrastructurename = "CLUSTER_NAME" 12 | simpheraInstances = { 13 | "SIMPHERA_STAGE_NAME" : { 14 | "name" : "SIMPHERA_INSTANCE_NAME" 15 | } 16 | } 17 | ivsInstances = { 18 | "IVS_STAGE_NAME" : {} 19 | } 20 | ``` 21 | - use only one GPU driver version in tfvars 22 | ```terraform 23 | gpu_operator_config = { 24 | driver_versions = ["DRIVER_VERSION"] 25 | } 26 | ivsGpuDriverVersion = "DRIVER_VERSION" 27 | ``` 28 | 29 | ## How to run a script 30 | - move to directory `.\scripts\aws_cloud_spec_gen` 31 | - install python requirements 32 | - pip install -r requirements.txt 33 | - set environment variables for AWS credentials, `AWS_PROFILE` and `AWS_REGION` 34 | - run script: 35 | ```powershell 36 | python main.py --cluster_id CLUSTER_NAME ` 37 | --simphera_stage SIMPHERA_STAGE_NAME ` 38 | --simphera_instance SIMPHERA_INSTANCE_NAME ` 39 | --ivs_stage IVS_STAGE_NAME ` 40 | --gpu_driver DRIVER_VERSION 41 | ``` 42 | - output is found at location `.\scripts\aws_cloud_spec_gen\AWSCloudSpec.md` 43 | 44 | ## Manual adaptation 45 | - For each resource that has `Mandatory` column, specify is it mandatory or not 46 | - Any missing resource description should be added 47 | - In `Policies` section, for column `Policy name`, add missing links to the policy definition, either online or relative local link 48 | -------------------------------------------------------------------------------- /modules/simphera_aws_instance/backup.tf: -------------------------------------------------------------------------------- 1 | resource "aws_backup_vault" "backup-vault" { 2 | count = var.enable_backup_service ? 1 : 0 3 | name = local.backup_vault_name 4 | tags = var.tags 5 | } 6 | 7 | resource "aws_backup_plan" "backup-plan" { 8 | count = var.enable_backup_service ? 1 : 0 9 | name = "${local.instancename}-backup-plan" 10 | 11 | rule { 12 | rule_name = "${local.instancename}-backup-rule" 13 | target_vault_name = aws_backup_vault.backup-vault[0].name 14 | recovery_point_tags = var.tags 15 | enable_continuous_backup = true 16 | 17 | lifecycle { 18 | delete_after = var.backup_retention 19 | } 20 | } 21 | tags = var.tags 22 | } 23 | 24 | resource "aws_backup_selection" "backup-selection-rds-s3" { 25 | count = var.enable_backup_service ? 1 : 0 26 | name = "${local.instancename}-rds-s3" 27 | iam_role_arn = aws_iam_role.backup_iam_role[0].arn 28 | plan_id = aws_backup_plan.backup-plan[0].id 29 | resources = local.backup_resources 30 | } 31 | 32 | resource "aws_iam_role" "backup_iam_role" { 33 | count = var.enable_backup_service ? 1 : 0 34 | name = "${var.name}-backup-role" 35 | description = "Role for enabling backup job execution in AWS" 36 | assume_role_policy = < dict: 23 | to_replace = [ 24 | {"name": args.cluster_name, "generic": "<_cluster_name_>"}, 25 | {"name": args.ivs_stage, "generic": "<_ivs_stage_>"}, 26 | {"name": args.simphera_stage, "generic": "<_simphera_stage_>"}, 27 | {"name": args.simphera_instance, "generic": "<_simphera_instance_name_>"}, 28 | {"name": os.environ["AWS_REGION"], "generic": "<_aws_region_>"}, 29 | {"name": args.gpu_driver, "generic": "<_gpu_driver_version_>"}, 30 | ] 31 | for element in to_replace: 32 | output = output.replace(element["name"], element["generic"]) 33 | return output 34 | 35 | 36 | def main(args): 37 | vpc_id = get_vpc_id(args.cluster_name) 38 | categories = get_categories("structure.yaml", args.cluster_name) 39 | security_groups = get_security_groups(vpc_id) 40 | roles, policies = get_roles_and_policies(args.cluster_name) 41 | output_buffer = populate_categories(categories, security_groups, roles, policies) 42 | output = replace_with_generic(args, "".join(output_buffer)) 43 | with open("AWSCloudSpec.md", "w") as file: 44 | file.write(output) 45 | 46 | 47 | if __name__ == "__main__": 48 | args = parse_args() 49 | main(args) 50 | -------------------------------------------------------------------------------- /outputs.tf: -------------------------------------------------------------------------------- 1 | output "account_id" { 2 | description = "The AWS account id used for creating resources." 3 | value = local.account_id 4 | } 5 | 6 | output "backup_vaults" { 7 | description = "Backups vaults managed by terraform." 8 | value = { 9 | simphera = { 10 | for name, instance in module.simphera_instance : 11 | name => flatten(instance.backup_vaults) 12 | } 13 | ivs = { 14 | for name, instance in module.ivs_instance : 15 | name => flatten(instance.backup_vaults) 16 | } 17 | } 18 | } 19 | 20 | output "database_identifiers" { 21 | description = "Identifiers of the databases from all instances." 22 | value = { 23 | simphera = { 24 | for name, instance in module.simphera_instance : 25 | name => flatten(instance.database_identifiers) 26 | } 27 | ivs = { 28 | for name, instance in module.ivs_instance : 29 | name => flatten(instance.database_identifiers) 30 | } 31 | } 32 | } 33 | 34 | output "database_endpoints" { 35 | description = "Endpoints of the databases from all instances." 36 | value = { 37 | simphera = { 38 | for name, instance in module.simphera_instance : 39 | name => flatten(instance.database_endpoints) 40 | } 41 | ivs = { 42 | for name, instance in module.ivs_instance : 43 | name => flatten(instance.database_endpoints) 44 | } 45 | } 46 | } 47 | 48 | output "s3_buckets" { 49 | description = "S3 buckets managed by terraform." 50 | value = local.s3_buckets 51 | } 52 | 53 | output "eks_cluster_id" { 54 | description = "Amazon EKS Cluster Name" 55 | value = module.eks.eks_cluster_id 56 | } 57 | 58 | output "opensearch_domain_endpoints" { 59 | description = "List of OpenSearch Domains endpoints of IVS instances" 60 | value = [for key, value in module.ivs_instance : value.opensearch_domain_endpoint] 61 | } 62 | 63 | output "ivs_buckets_service_accounts" { 64 | description = "List of K8s service account names with access to the IVS buckets" 65 | value = [for name, instance in module.ivs_instance : instance.ivs_buckets_service_account] 66 | } 67 | 68 | output "ivs_node_groups_roles" { 69 | value = merge(local.ivs_node_groups_roles, var.windows_execution_node.enable ? { winexecnode : module.eks.node_groups[0]["winexecnodes"].nodegroup_role_id } : {}) 70 | } 71 | -------------------------------------------------------------------------------- /scripts/aws_cloud_spec_gen/models/iam.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | AWS_POLICY_DOC = "https://raw.githubusercontent.com/SummitRoute/aws_managed_policies/master/policies/" 4 | 5 | 6 | class Role: 7 | """! Class representation of AWS IAM Role.""" 8 | 9 | def __init__(self, name: str, description: str): 10 | self.name = name 11 | self.description = description 12 | self.related_policies: list[Policy] = list() 13 | 14 | def get_related_policies_ref(self) -> str: 15 | """! Method for getting string representation for this self.releated_policies. 16 | 17 | @return string 18 | """ 19 | policies_ref = "" 20 | for policy in self.related_policies: 21 | policies_ref += policy.get_list_item() 22 | return policies_ref 23 | 24 | def markdown(self) -> str: 25 | """! Method for getting Markdown representation of this object. 26 | 27 | @return string 28 | """ 29 | return f"| {self.name} | {self.description} | {self.get_related_policies_ref()} |" 30 | 31 | 32 | class Policy: 33 | """! Class representation of AWS IAM policy.""" 34 | 35 | def __init__(self, name: str, description: str, managed_by: str): 36 | self.name = name 37 | self.description = description 38 | self.managed_by = managed_by 39 | 40 | def get_list_item(self) -> str: 41 | """! Method for getting HTML list element string from this object. 42 | 43 | @return string 44 | """ 45 | if self.description != None: 46 | return f"
  • [{self.name}](#{self.name})
  • " 47 | return f"
  • {self.name}
  • " 48 | 49 | def get_markdown_name(self) -> str: 50 | """! Method for getting Markdown href from this objects name. 51 | 52 | @return string 53 | """ 54 | if self.managed_by == "AWS": 55 | return f"[{self.name}]({AWS_POLICY_DOC}{self.name})" 56 | return f"[{self.name}](./)" 57 | 58 | def markdown(self) -> str: 59 | """! Method for getting Markdown representation of this object. 60 | 61 | @return string 62 | """ 63 | name_ref = f'' + self.get_markdown_name() 64 | return f"| {name_ref} | {self.description} | {self.managed_by} |" 65 | -------------------------------------------------------------------------------- /modules/eks/vpc-cni.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | aws_vpc_cni_addon_name = "vpc-cni" 3 | aws_vpc_cni_service_account = "aws-node" 4 | aws_vpc_cni_namespace = "kube-system" 5 | eks_oidc_issuer_url = split("//", aws_eks_cluster.eks.identity[0].oidc[0].issuer)[1] 6 | } 7 | 8 | data "aws_eks_addon_version" "aws_vpc_cni" { 9 | addon_name = local.aws_vpc_cni_addon_name 10 | kubernetes_version = aws_eks_cluster.eks.version 11 | } 12 | 13 | resource "aws_eks_addon" "aws_vpc_cni" { 14 | cluster_name = aws_eks_cluster.eks.id 15 | addon_name = local.aws_vpc_cni_addon_name 16 | addon_version = data.aws_eks_addon_version.aws_vpc_cni.version 17 | service_account_role_arn = aws_iam_role.aws_vpc_cni_role.arn 18 | preserve = true 19 | resolve_conflicts_on_create = "OVERWRITE" 20 | resolve_conflicts_on_update = "OVERWRITE" 21 | tags = var.tags 22 | configuration_values = local.windows_enabled ? local.windows_vpc_cni_configuration : null 23 | } 24 | 25 | resource "aws_iam_role" "aws_vpc_cni_role" { 26 | name = format("%s-%s-%s", aws_eks_cluster.eks.id, trimsuffix(local.aws_vpc_cni_service_account, "-sa"), "irsa") 27 | description = "AWS IAM Role for the Kubernetes service account ${local.aws_vpc_cni_service_account}." 28 | 29 | assume_role_policy = jsonencode({ 30 | "Version" : "2012-10-17", 31 | "Statement" : [ 32 | { 33 | "Effect" : "Allow", 34 | "Principal" : { 35 | "Federated" : "arn:${var.aws_context.partition_id}:iam::${var.aws_context.caller_identity_account_id}:oidc-provider/${split("//", aws_eks_cluster.eks.identity[0].oidc[0].issuer)[1]}" 36 | }, 37 | "Action" : "sts:AssumeRoleWithWebIdentity", 38 | "Condition" : { 39 | "StringLike" : { 40 | "${local.eks_oidc_issuer_url}:sub" : "system:serviceaccount:${local.aws_vpc_cni_namespace}:${local.aws_vpc_cni_service_account}", 41 | "${local.eks_oidc_issuer_url}:aud" : "sts.amazonaws.com" 42 | } 43 | } 44 | } 45 | ] 46 | }) 47 | 48 | force_detach_policies = true 49 | 50 | tags = var.tags 51 | } 52 | 53 | resource "aws_iam_role_policy_attachment" "aws_vpc_cni_policy_attachment" { 54 | policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy" 55 | role = aws_iam_role.aws_vpc_cni_role.name 56 | } 57 | -------------------------------------------------------------------------------- /ivs-instances.tf: -------------------------------------------------------------------------------- 1 | module "ivs_instance" { 2 | source = "./modules/ivs_aws_instance" 3 | for_each = var.ivsInstances 4 | aws_context = local.aws_context 5 | backup_retention = each.value.backup_retention 6 | backup_schedule = each.value.backup_schedule 7 | backup_service_enable = each.value.backup_service_enable 8 | cloudwatch_retention = var.cloudwatch_retention 9 | database_secretname = each.value.database_secretname 10 | data_bucket = each.value.data_bucket 11 | db_instance_type_ivs = each.value.db_instance_type_ivs 12 | eks_cluster_id = var.infrastructurename 13 | eks_oidc_issuer = replace(module.eks.eks_oidc_issuer_url, "https://", "") 14 | eks_oidc_provider_arn = module.eks.eks_oidc_provider_arn 15 | enable_deletion_protection = each.value.enable_deletion_protection 16 | enableIVSAuthentication = each.value.enable_ivs_authentication 17 | goofys_user_agent_sdk_and_go_version = each.value.goofys_user_agent_sdk_and_go_version 18 | instancename = each.key 19 | ivs_release_name = each.value.ivs_release_name 20 | k8s_namespace = each.value.k8s_namespace 21 | kms_key_cloudwatch = var.aws_managed_kms ? null : aws_kms_key.kms_key_cloudwatch_log_group[0].arn 22 | log_bucket = aws_s3_bucket.bucket_logs.id 23 | nodeRoleNames = local.ivs_node_groups_roles 24 | opensearch = merge(each.value.opensearch, { 25 | domain_name = "${var.infrastructurename}-${each.key}" 26 | subnet_ids = local.private_subnets 27 | security_group_ids = [module.eks.cluster_primary_security_group_id] 28 | } 29 | ) 30 | postgresql_security_group_id = module.security_group.security_group_id 31 | postgresqlApplyImmediately = each.value.postgresqlApplyImmediately 32 | postgresqlVersion = each.value.postgresqlVersion 33 | private_subnets = local.private_subnets 34 | raw_data_bucket = each.value.raw_data_bucket 35 | region = local.region 36 | tags = var.tags 37 | depends_on = [module.k8s_eks_addons] 38 | } 39 | -------------------------------------------------------------------------------- /modules/k8s_eks_addons/ebs-csi.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | aws_ebs_csi_addon_name = "aws-ebs-csi-driver" 3 | aws_ebs_csi_namespace = "kube-system" 4 | # This service account is automatically created by the add-on. 5 | aws_ebs_csi_service_account = "ebs-csi-controller-sa" 6 | } 7 | 8 | data "aws_eks_addon_version" "aws_ebs_csi_driver" { 9 | addon_name = local.aws_ebs_csi_addon_name 10 | kubernetes_version = var.addon_context.eks_cluster_version 11 | } 12 | 13 | resource "aws_eks_addon" "aws_ebs_csi_driver" { 14 | cluster_name = var.addon_context.eks_cluster_id 15 | addon_name = local.aws_ebs_csi_addon_name 16 | addon_version = data.aws_eks_addon_version.aws_ebs_csi_driver.version 17 | service_account_role_arn = aws_iam_role.ebs_csi_driver_role.arn 18 | preserve = false 19 | resolve_conflicts_on_create = "OVERWRITE" 20 | resolve_conflicts_on_update = "OVERWRITE" 21 | tags = var.tags 22 | configuration_values = file("${path.module}/templates/ebs_values.yaml") 23 | } 24 | 25 | resource "aws_iam_role" "ebs_csi_driver_role" { 26 | name = format("%s-%s-%s", var.addon_context.eks_cluster_id, trimsuffix(local.aws_ebs_csi_service_account, "-sa"), "irsa") 27 | description = "AWS IAM Role for the Kubernetes service account ${local.aws_ebs_csi_service_account}." 28 | 29 | assume_role_policy = jsonencode({ 30 | "Version" : "2012-10-17", 31 | "Statement" : [ 32 | { 33 | "Effect" : "Allow", 34 | "Principal" : { 35 | "Federated" : "arn:${var.addon_context.aws_context.partition_id}:iam::${var.addon_context.aws_context.caller_identity_account_id}:oidc-provider/${var.addon_context.eks_oidc_issuer_url}" 36 | }, 37 | "Action" : "sts:AssumeRoleWithWebIdentity", 38 | "Condition" : { 39 | "StringLike" : { 40 | "${var.addon_context.eks_oidc_issuer_url}:sub" : "system:serviceaccount:${local.aws_ebs_csi_namespace}:${local.aws_ebs_csi_service_account}", 41 | "${var.addon_context.eks_oidc_issuer_url}:aud" : "sts.amazonaws.com" 42 | } 43 | } 44 | } 45 | ] 46 | }) 47 | 48 | force_detach_policies = true 49 | 50 | tags = var.tags 51 | } 52 | 53 | resource "aws_iam_role_policy_attachment" "ebs_csi_driver_policy_attachment" { 54 | policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy" 55 | role = aws_iam_role.ebs_csi_driver_role.name 56 | } 57 | 58 | -------------------------------------------------------------------------------- /modules/k8s_eks_addons/efs-csi.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | aws_efs_csi_addon_name = "aws-efs-csi-driver" 3 | aws_efs_csi_namespace = "kube-system" 4 | aws_efs_csi_service_account = "efs-csi-controller-sa" 5 | } 6 | 7 | data "aws_eks_addon_version" "aws_efs_csi_driver" { 8 | count = var.efs_csi_config.enable ? 1 : 0 9 | addon_name = local.aws_efs_csi_addon_name 10 | kubernetes_version = var.addon_context.eks_cluster_version 11 | } 12 | 13 | resource "aws_eks_addon" "aws_efs_csi_driver" { 14 | count = var.efs_csi_config.enable ? 1 : 0 15 | cluster_name = var.addon_context.eks_cluster_id 16 | addon_name = local.aws_efs_csi_addon_name 17 | addon_version = data.aws_eks_addon_version.aws_efs_csi_driver[0].version 18 | service_account_role_arn = aws_iam_role.efs_csi_driver_role[0].arn 19 | preserve = true 20 | resolve_conflicts_on_create = "OVERWRITE" 21 | resolve_conflicts_on_update = "OVERWRITE" 22 | tags = var.tags 23 | } 24 | 25 | resource "aws_iam_role" "efs_csi_driver_role" { 26 | count = var.efs_csi_config.enable ? 1 : 0 27 | name = format("%s-%s-%s", var.addon_context.eks_cluster_id, trimsuffix(local.aws_efs_csi_service_account, "-sa"), "irsa") 28 | description = "AWS IAM Role for the Kubernetes service account ${local.aws_efs_csi_service_account}." 29 | 30 | assume_role_policy = jsonencode({ 31 | "Version" : "2012-10-17", 32 | "Statement" : [ 33 | { 34 | "Effect" : "Allow", 35 | "Principal" : { 36 | "Federated" : "arn:${var.addon_context.aws_context.partition_id}:iam::${var.addon_context.aws_context.caller_identity_account_id}:oidc-provider/${var.addon_context.eks_oidc_issuer_url}" 37 | }, 38 | "Action" : "sts:AssumeRoleWithWebIdentity", 39 | "Condition" : { 40 | "StringLike" : { 41 | "${var.addon_context.eks_oidc_issuer_url}:sub" : "system:serviceaccount:${local.aws_efs_csi_namespace}:${local.aws_efs_csi_service_account}", 42 | "${var.addon_context.eks_oidc_issuer_url}:aud" : "sts.amazonaws.com" 43 | } 44 | } 45 | } 46 | ] 47 | }) 48 | 49 | force_detach_policies = true 50 | 51 | tags = var.tags 52 | } 53 | 54 | resource "aws_iam_role_policy_attachment" "efs_csi_driver_policy_attachment" { 55 | count = var.efs_csi_config.enable ? 1 : 0 56 | policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEFSCSIDriverPolicy" 57 | role = aws_iam_role.efs_csi_driver_role[0].name 58 | } 59 | -------------------------------------------------------------------------------- /modules/k8s_eks_addons/variables.tf: -------------------------------------------------------------------------------- 1 | variable "addon_context" { 2 | description = "AWS and EKS metadata" 3 | type = object({ 4 | aws_context = object({ 5 | caller_identity_account_id = string 6 | partition_dns_suffix = string 7 | partition_id = string 8 | partition = string 9 | region_name = string 10 | iam_issuer_arn = string 11 | }) 12 | eks_cluster_id = string 13 | eks_cluster_version = string 14 | eks_oidc_issuer_url = string 15 | }) 16 | } 17 | 18 | variable "tags" { 19 | description = "A map of tags to add to all resources" 20 | type = map(string) 21 | default = {} 22 | } 23 | 24 | variable "ingress_nginx_config" { 25 | description = "Ingress Nginx configuration" 26 | type = object({ 27 | enable = bool 28 | helm_repository = string 29 | helm_version = string 30 | chart_values = string 31 | subnets_ids = list(string) 32 | }) 33 | } 34 | 35 | variable "cluster_autoscaler_config" { 36 | description = "Cluster Autoscaler configuration." 37 | type = object({ 38 | enable = bool 39 | helm_repository = string 40 | helm_version = string 41 | chart_values = string 42 | }) 43 | } 44 | 45 | variable "coredns_config" { 46 | type = object({ 47 | enable = optional(bool, true) 48 | configuration_values = optional(string, null) 49 | }) 50 | description = "Input configuration for AWS EKS add-on coredns." 51 | } 52 | 53 | variable "efs_csi_config" { 54 | type = object({ 55 | enable = optional(bool, true) 56 | }) 57 | description = "Input configuration for AWS EKS add-on efs csi." 58 | } 59 | 60 | variable "aws_load_balancer_controller_config" { 61 | description = "AWS Load Balancer Controller configuration." 62 | type = object({ 63 | enable = bool 64 | helm_repository = string 65 | helm_version = string 66 | chart_values = string 67 | }) 68 | } 69 | 70 | variable "s3_csi_config" { 71 | type = object({ 72 | enable = optional(bool, false) 73 | configuration_values = optional(string, null) 74 | }) 75 | description = "Input configuration for AWS EKS add-on aws-mountpoint-s3-csi-driver." 76 | } 77 | 78 | variable "gpu_operator_config" { 79 | description = "GPU operator configuration" 80 | type = object({ 81 | enable = bool 82 | helm_repository = string 83 | helm_version = string 84 | chart_values = string 85 | driver_versions = list(string) 86 | }) 87 | } 88 | -------------------------------------------------------------------------------- /modules/eks/modules/node-group/variables.tf: -------------------------------------------------------------------------------- 1 | variable "node_group_name" { 2 | description = "Name of the node group" 3 | type = string 4 | } 5 | 6 | variable "subnet_ids" { 7 | description = "A list of subnet IDs where the nodes/node groups will be provisioned" 8 | type = list(string) 9 | } 10 | 11 | variable "worker_security_group_ids" { 12 | description = "A list of security group IDs to associate with the network interface of the nodes" 13 | type = list(string) 14 | } 15 | 16 | variable "instance_types" { 17 | description = "List of instance types associated with the EKS Node Group" 18 | type = list(string) 19 | } 20 | 21 | variable "capacity_type" { 22 | description = "Capacity type associated with the EKS Node Group, 'ON_DEMAND' or 'SPOT'" 23 | type = string 24 | } 25 | 26 | variable "max_size" { 27 | description = "Maximum number of worker nodes" 28 | type = number 29 | } 30 | 31 | variable "min_size" { 32 | description = "Minimum number of worker nodes" 33 | type = number 34 | } 35 | 36 | variable "custom_ami_id" { 37 | description = "The AMI from which to launch the instance" 38 | type = string 39 | default = null 40 | } 41 | 42 | variable "ami_type" { 43 | description = "The AMI type" 44 | type = string 45 | } 46 | 47 | variable "block_device_name" { 48 | description = "The name of the device to mount" 49 | type = string 50 | default = "/dev/xvda" 51 | } 52 | 53 | variable "volume_size" { 54 | description = "The size of the volume in gigabytes" 55 | type = number 56 | } 57 | 58 | variable "k8s_labels" { 59 | description = "Key-value map of Kubernetes labels" 60 | type = map(string) 61 | default = {} 62 | } 63 | 64 | variable "k8s_taints" { 65 | description = "The Kubernetes taints to be applied to the nodes in the node group" 66 | type = list(object({ 67 | key = string 68 | value = string 69 | effect = string 70 | })) 71 | default = [] 72 | } 73 | 74 | variable "node_group_context" { 75 | description = "Context values for the node group. AWS, EKS, VPC and IAM context" 76 | type = object({ 77 | eks_cluster_id = string 78 | cluster_ca_base64 = string 79 | cluster_endpoint = string 80 | cluster_version = string 81 | aws_context = object({ 82 | partition_dns_suffix = string 83 | partition_id = string 84 | }) 85 | }) 86 | } 87 | 88 | variable "tags" { 89 | description = "A map of tags to add to all resources" 90 | type = map(string) 91 | default = {} 92 | } 93 | -------------------------------------------------------------------------------- /modules/eks/modules/node-group/launch-template.tf: -------------------------------------------------------------------------------- 1 | resource "aws_launch_template" "node_group" { 2 | name = "${var.node_group_context.eks_cluster_id}-${var.node_group_name}" 3 | description = "Launch Template for EKS Managed Node Groups" 4 | update_default_version = true 5 | user_data = ( 6 | strcontains(var.node_group_name, "gpu") ? base64encode( 7 | templatefile("${path.module}/templates/userdata.tpl", { 8 | eks_cluster_id = var.node_group_context.eks_cluster_id 9 | cluster_ca_base64 = var.node_group_context.cluster_ca_base64 10 | cluster_endpoint = var.node_group_context.cluster_endpoint 11 | custom_ami_id = var.custom_ami_id 12 | pre_userdata = "" 13 | bootstrap_extra_args = "" 14 | post_userdata = "" 15 | kubelet_extra_args = "" 16 | service_ipv6_cidr = "" 17 | service_ipv4_cidr = "" 18 | format_mount_nvme_disk = false 19 | }) 20 | ) : 21 | strcontains(var.ami_type, "WINDOWS") ? null : 22 | strcontains(var.ami_type, "BOTTLEROCKET") ? base64encode(< Args: 18 | parser = argparse.ArgumentParser() 19 | parser.add_argument( 20 | "--log_path", type=Path, required=True, help="Output of terraform execution after setting TF_LOG=trace" 21 | ) 22 | return parser.parse_args() 23 | 24 | 25 | special_services = {"Secrets Manager": "secretsmanager", "Auto Scaling": "autoscaling", "CloudWatch Logs": "logs"} 26 | 27 | regex_string_request = r"rpc\.method=(?P\S+).*rpc\.service=(?P\S+)" 28 | regex_string_response = r"rpc\.service=(?P\S+).*rpc\.method=(?P\S+)" 29 | regex_string_special_request = r"rpc\.service=\"(?P[^\"]+)\".*rpc\.method=(?P\S+)" 30 | regex_string_special_response = r"rpc\.method=(?P\S+).*rpc\.service=\"(?P[^\"]+)\"" 31 | timestamp_pattern = r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}[+-]\d{4}" 32 | 33 | 34 | def format_log(log_path: Path) -> list: 35 | with open(log_path, "r", encoding="utf-8") as logs: 36 | lines = logs.readlines() 37 | formatted_lines = [] 38 | for line in lines: 39 | matched = re.match(timestamp_pattern, line) 40 | if matched: 41 | formatted_lines.append(line.strip("\n").replace("\n", "")) 42 | elif len(formatted_lines) > 0: 43 | formatted_lines[-1] = f"{formatted_lines[-1]} {line}".strip("\n").replace("\n", " ") 44 | return formatted_lines 45 | 46 | 47 | def create_action(match: dict) -> str: 48 | service = special_services.get(match["service"], match["service"]).lower() 49 | return f"{service}:{match['method']}" 50 | 51 | 52 | def extract_actions(logs: list) -> set: 53 | actions = set() 54 | for line in logs: 55 | match = re.search(regex_string_special_request, line) 56 | if match: 57 | actions.add(create_action(match)) 58 | continue 59 | match = re.search(regex_string_special_response, line) 60 | if match: 61 | actions.add(create_action(match)) 62 | continue 63 | match = re.search(regex_string_request, line) 64 | if match: 65 | actions.add(create_action(match)) 66 | continue 67 | match = re.search(regex_string_response, line) 68 | if match: 69 | actions.add(create_action(match)) 70 | continue 71 | return actions 72 | 73 | 74 | def create_policy(actions: set, output_path: Path) -> None: 75 | policy = { 76 | "Version": "2012-10-17", 77 | "Statement": [{"Effect": "Allow", "Action": list(sorted(actions)), "Resource": "*"}], 78 | } 79 | with open(output_path, "w+", encoding="utf-8") as output: 80 | logging.info("Outputting to %s", output_path) 81 | json.dump(policy, output, indent=4) 82 | 83 | 84 | def main(args: Args): 85 | logs = format_log(args.log_path) 86 | actions = extract_actions(logs) 87 | output_path = Path(f"{args.log_path.parent}\\{args.log_path.stem}_output.json") 88 | create_policy(actions, output_path) 89 | 90 | 91 | if __name__ == "__main__": 92 | parsed_arguments = parse_args() 93 | main(parsed_arguments) 94 | -------------------------------------------------------------------------------- /.github/workflows/qualitygate.yml: -------------------------------------------------------------------------------- 1 | name: qualitygate 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened] 6 | jobs: 7 | qualitygate: 8 | name: Qualitygate 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | with: 13 | #fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis 14 | ref: ${{ github.event.pull_request.head.ref }} 15 | - name: HashiCorp - Setup Terraform 16 | uses: hashicorp/setup-terraform@v2.0.0 17 | with: 18 | # # The hostname of a Terraform Cloud/Enterprise instance to place within the credentials block of the Terraform CLI configuration file. Defaults to `app.terraform.io`. 19 | # cli_config_credentials_hostname: # optional, default is app.terraform.io 20 | # # The API token for a Terraform Cloud/Enterprise instance to place within the credentials block of the Terraform CLI configuration file. 21 | # cli_config_credentials_token: # optional 22 | # # The version of Terraform CLI to install. Instead of full version string you can also specify constraint string starting with "<" (for example `<1.13.0`) to install the latest version satisfying the constraint. A value of `latest` will install the latest version of Terraform CLI. Defaults to `latest`. 23 | terraform_version: 1.9.0 # optional, default is latest 24 | # # Whether or not to install a wrapper to wrap subsequent calls of the `terraform` binary and expose its STDOUT, STDERR, and exit code as outputs named `stdout`, `stderr`, and `exitcode` respectively. Defaults to `true`. 25 | # terraform_wrapper: # optional, default is true 26 | 27 | - name: Setup tflint 28 | uses: terraform-linters/setup-tflint@v2 29 | 30 | - name: Terraform format 31 | run: terraform fmt -check -recursive 32 | 33 | - name: Terraform init 34 | run: terraform init -backend=false 35 | 36 | - name: Terraform validate 37 | run: terraform validate 38 | 39 | - name: Init TFLint 40 | run: tflint --init 41 | 42 | - name: Terraform lint Root 43 | run: tflint 44 | 45 | - name: Terraform lint SIMPHERA Base 46 | run: tflint --config ../../.tflint.hcl --chdir ./modules/simphera_aws_instance 47 | 48 | - name: tfsec action 49 | uses: aquasecurity/tfsec-action@v1.0.2 50 | with: 51 | additional_args: --exclude-downloaded-modules --config-file tfsec.yaml 52 | 53 | - name: Terraform-docs Update README.md 54 | uses: terraform-docs/gh-actions@v1.0.0 55 | with: 56 | working-dir: . 57 | output-file: README.md 58 | output-method: inject 59 | git-push: "true" 60 | 61 | - name: Terraform-docs Regenerate terraform.tfvars.example 62 | uses: terraform-docs/gh-actions@v1.0.0 63 | with: 64 | working-dir: . 65 | config-file: tfvars.hcl.terraform-docs.yml 66 | output-file: terraform.tfvars.example 67 | output-method: replace 68 | template: | 69 | {{ .Content }} 70 | git-push: "true" 71 | 72 | - name: Terraform-docs Regenerate terraform.json.example 73 | uses: terraform-docs/gh-actions@v1.0.0 74 | with: 75 | working-dir: . 76 | output-file: terraform.json.example 77 | output-format: tfvars json 78 | output-method: replace 79 | template: | 80 | {{ .Content }} 81 | git-push: "true" 82 | 83 | -------------------------------------------------------------------------------- /modules/k8s_eks_addons/s3-csi.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | aws_s3_csi_addon_name = "aws-mountpoint-s3-csi-driver" 3 | aws_s3_csi_namespace = "kube-system" 4 | aws_s3_csi_service_account = "s3-csi-driver-sa" 5 | } 6 | 7 | data "aws_eks_addon_version" "aws-mountpoint-s3-csi-driver" { 8 | count = var.s3_csi_config.enable ? 1 : 0 9 | addon_name = local.aws_s3_csi_addon_name 10 | kubernetes_version = var.addon_context.eks_cluster_version 11 | } 12 | 13 | resource "aws_eks_addon" "aws-mountpoint-s3-csi-driver" { 14 | count = var.s3_csi_config.enable ? 1 : 0 15 | cluster_name = var.addon_context.eks_cluster_id 16 | addon_name = local.aws_s3_csi_addon_name 17 | addon_version = data.aws_eks_addon_version.aws-mountpoint-s3-csi-driver[0].version 18 | service_account_role_arn = aws_iam_role.s3_csi_driver_role[0].arn 19 | preserve = true 20 | resolve_conflicts_on_create = "OVERWRITE" 21 | resolve_conflicts_on_update = "OVERWRITE" 22 | configuration_values = var.coredns_config.configuration_values 23 | tags = var.tags 24 | } 25 | 26 | resource "aws_iam_role" "s3_csi_driver_role" { 27 | count = var.s3_csi_config.enable ? 1 : 0 28 | name = format("%s-%s-%s", var.addon_context.eks_cluster_id, trimsuffix(local.aws_s3_csi_service_account, "-sa"), "irsa") 29 | description = "AWS IAM Role for the Kubernetes service account ${local.aws_s3_csi_service_account}." 30 | 31 | assume_role_policy = jsonencode({ 32 | "Version" : "2012-10-17", 33 | "Statement" : [ 34 | { 35 | "Effect" : "Allow", 36 | "Principal" : { 37 | "Federated" : "arn:${var.addon_context.aws_context.partition_id}:iam::${var.addon_context.aws_context.caller_identity_account_id}:oidc-provider/${var.addon_context.eks_oidc_issuer_url}" 38 | }, 39 | "Action" : "sts:AssumeRoleWithWebIdentity", 40 | "Condition" : { 41 | "StringLike" : { 42 | "${var.addon_context.eks_oidc_issuer_url}:sub" : "system:serviceaccount:${local.aws_s3_csi_namespace}:${local.aws_s3_csi_service_account}", 43 | "${var.addon_context.eks_oidc_issuer_url}:aud" : "sts.amazonaws.com" 44 | } 45 | } 46 | } 47 | ] 48 | }) 49 | 50 | force_detach_policies = true 51 | 52 | tags = var.tags 53 | } 54 | 55 | resource "aws_iam_policy" "Amazons3CSIDriverPolicy" { 56 | count = var.s3_csi_config.enable ? 1 : 0 57 | name = "${var.addon_context.eks_cluster_id}-s3-csi-driver-irsa" 58 | description = "Amazons3CSIDriverPolicy" 59 | 60 | policy = jsonencode({ 61 | "Version" : "2012-10-17", 62 | "Statement" : [ 63 | { 64 | "Sid" : "MountpointFullBucketAccess", 65 | "Effect" : "Allow", 66 | "Action" : [ 67 | "s3:ListBucket" 68 | ], 69 | "Resource" : [ 70 | "arn:aws:s3:::*" 71 | ] 72 | }, 73 | { 74 | "Sid" : "MountpointFullObjectAccess", 75 | "Effect" : "Allow", 76 | "Action" : [ 77 | "s3:GetObject", 78 | "s3:PutObject", 79 | "s3:AbortMultipartUpload", 80 | "s3:DeleteObject" 81 | ], 82 | "Resource" : [ 83 | "arn:aws:s3:::*" 84 | ] 85 | } 86 | ] 87 | }) 88 | } 89 | 90 | resource "aws_iam_role_policy_attachment" "s3_csi_driver_policy_attachment" { 91 | count = var.s3_csi_config.enable ? 1 : 0 92 | policy_arn = aws_iam_policy.Amazons3CSIDriverPolicy[0].arn 93 | role = aws_iam_role.s3_csi_driver_role[0].name 94 | 95 | depends_on = [aws_iam_policy.Amazons3CSIDriverPolicy] 96 | } 97 | -------------------------------------------------------------------------------- /modules/simphera_aws_instance/.terraform.lock.hcl: -------------------------------------------------------------------------------- 1 | # This file is maintained automatically by "terraform init". 2 | # Manual edits may be lost in future updates. 3 | 4 | provider "registry.terraform.io/hashicorp/aws" { 5 | version = "4.46.0" 6 | hashes = [ 7 | "h1:mRM6S6Yoqze/+bhLrRbvWJYNcnQzI7Q/78pLq15KJWI=", 8 | "zh:1678e6a4bdb3d81a6713adc62ca0fdb8250c584e10c10d1daca72316e9db8df2", 9 | "zh:329903acf86ef6072502736dff4c43c2b50f762a958f76aa924e2d74c7fca1e3", 10 | "zh:33db8131fe0ec7e1d9f30bc9f65c2440e9c1f708d681b6062757a351f1df7ce6", 11 | "zh:3a3b010bc393784c16f4b6cdce7f76db93d5efa323fce4920bfea9e9ba6abe44", 12 | "zh:979e2713a5759a7483a065e149e3cb69db9225326fc0457fa3fc3a48aed0c63f", 13 | "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", 14 | "zh:9efcf0067e16ad53da7504178a05eb2118770b4ae00c193c10ecad4cbfce308e", 15 | "zh:a10655bf1b6376ab7f3e55efadf54dc70f7bd07ca11369557c312095076f9d62", 16 | "zh:b0394dd42cbd2a718a7dd7ae0283f04769aaf8b3d52664e141da59c0171a11ab", 17 | "zh:b958e614c2cf6d9c05a6ad5e94dc5c04b97ebfb84415da068be5a081b5ebbe24", 18 | "zh:ba5069e624210c63ad9e633a8eb0108b21f2322bc4967ba2b82d09168c466888", 19 | "zh:d7dfa597a17186e7f4d741dd7111849f1c0dd6f7ebc983043d8262d2fb37b408", 20 | "zh:e8a641ca2c99f96d64fa2725875e797273984981d3e54772a2823541c44e3cd3", 21 | "zh:f89898b7067c4246293a8007f59f5cfcac7b8dd251d39886c7a53ba596251466", 22 | "zh:fb1e1df1d5cc208e08a850f8e84423bce080f01f5e901791c79df369d3ed52f2", 23 | ] 24 | } 25 | 26 | provider "registry.terraform.io/hashicorp/http" { 27 | version = "3.2.1" 28 | hashes = [ 29 | "h1:ifGB/kA8VssmJsqUL0TvF2rBBhqKMXHGbYzSEQT7MfY=", 30 | "zh:088b3b3128034485e11dff8da16e857d316fbefeaaf5bef24cceda34c6980641", 31 | "zh:09ed1f2462ea4590b112e048c4af556f0b6eafc7cf2c75bb2ac21cd87ca59377", 32 | "zh:39c6b0b4d3f0f65e783c467d3f634e2394820b8aef907fcc24493f21dcf73ca3", 33 | "zh:47aab45327daecd33158a36c1a36004180a518bf1620cdd5cfc5e1fe77d5a86f", 34 | "zh:4d70a990aa48116ab6f194eef393082c21cf58bece933b63575c63c1d2b66818", 35 | "zh:65470c43fda950c7e9ac89417303c470146de984201fff6ef84299ea29e02d30", 36 | "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", 37 | "zh:842b4dd63e438f5cd5fdfba1c09b8fdf268e8766e6690988ee24e8b25bfd9e8d", 38 | "zh:a167a057f7e2d80c78d4b4057538588131fceb983d5c93b07675ad9eb1aa5790", 39 | "zh:d0ba69b62b6db788cfe3cf8f7dc6e9a0eabe2927dc119d7fe3fe6573ee559e66", 40 | "zh:e28d24c1d5ff24b1d1cc6f0074a1f41a6974f473f4ff7a37e55c7b6dca68308a", 41 | "zh:fde8a50554960e5366fd0e1ca330a7c1d24ae6bbb2888137a5c83d83ce14fd18", 42 | ] 43 | } 44 | 45 | provider "registry.terraform.io/hashicorp/kubernetes" { 46 | version = "2.16.1" 47 | hashes = [ 48 | "h1:O23HBuu2cPnLfW/lqvMM6eAeVx7eZgjqsK+Nz/FX2Gg=", 49 | "zh:06224975f5910d41e73b35a4d5079861da2c24f9353e3ebb015fbb3b3b996b1c", 50 | "zh:2bc400a8d9fe7755cca27c2551564a9e2609cfadc77f526ef855114ee02d446f", 51 | "zh:3a479014187af1d0aec3a1d3d9c09551b801956fe6dd29af1186dec86712731b", 52 | "zh:73fb0a69f1abdb02858b6589f7fab6d989a0f422f7ad95ed662aaa84872d3473", 53 | "zh:a33852cd382cbc8e06d3f6c018b468ad809d24d912d64722e037aed1f9bf39db", 54 | "zh:b533ff2214dca90296b1d22eace7eaa7e3efe5a7ae9da66a112094abc932db4f", 55 | "zh:ddf74d8bb1aeb01dc2c36ef40e2b283d32b2a96db73f6daaf179fa2f10949c80", 56 | "zh:e720f3a15d34e795fa9ff90bc755e838ebb4aef894aa2a423fb16dfa6d6b0667", 57 | "zh:e789ae70a658800cb0a19ef7e4e9b26b5a38a92b43d1f41d64fc8bb46539cefb", 58 | "zh:e8aed7dc0bd8f843d607dee5f72640dbef6835a8b1c6ea12cea5b4ec53e463f7", 59 | "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", 60 | "zh:fb3ac4f43c8b0dfc0b0103dd0f062ea72b3a34518d4c8808e3a44c9a3dd5f024", 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /modules/eks/data.tf: -------------------------------------------------------------------------------- 1 | data "aws_eks_cluster" "cluster" { 2 | name = aws_eks_cluster.eks.id 3 | } 4 | 5 | data "http" "eks_cluster_readiness" { 6 | url = join("/", [data.aws_eks_cluster.cluster.endpoint, "healthz"]) 7 | ca_cert_pem = base64decode(data.aws_eks_cluster.cluster.certificate_authority[0].data) 8 | request_timeout_ms = 600000 9 | } 10 | 11 | data "aws_iam_policy_document" "eks_key" { 12 | statement { 13 | sid = "Allow access for all principals in the account that are authorized" 14 | effect = "Allow" 15 | actions = [ 16 | "kms:CreateGrant", 17 | "kms:Decrypt", 18 | "kms:DescribeKey", 19 | "kms:Encrypt", 20 | "kms:GenerateDataKey*", 21 | "kms:ReEncrypt*", 22 | ] 23 | resources = ["*"] 24 | 25 | principals { 26 | type = "AWS" 27 | identifiers = [ 28 | "arn:${var.aws_context.partition_id}:iam::${var.aws_context.caller_identity_account_id}:root" 29 | ] 30 | } 31 | 32 | condition { 33 | test = "StringEquals" 34 | variable = "kms:CallerAccount" 35 | values = [var.aws_context.caller_identity_account_id] 36 | } 37 | 38 | condition { 39 | test = "StringEquals" 40 | variable = "kms:ViaService" 41 | values = ["eks.${var.aws_context.region_name}.amazonaws.com"] 42 | } 43 | } 44 | 45 | statement { 46 | sid = "Allow direct access to key metadata to the account" 47 | effect = "Allow" 48 | actions = [ 49 | "kms:Describe*", 50 | "kms:Get*", 51 | "kms:List*", 52 | "kms:RevokeGrant", 53 | ] 54 | resources = ["*"] 55 | 56 | principals { 57 | type = "AWS" 58 | identifiers = [ 59 | "arn:${var.aws_context.partition_id}:iam::${var.aws_context.caller_identity_account_id}:root" 60 | ] 61 | } 62 | } 63 | 64 | statement { 65 | sid = "Allow access for Key Administrators" 66 | effect = "Allow" 67 | actions = [ 68 | "kms:*" 69 | ] 70 | resources = ["*"] 71 | 72 | principals { 73 | type = "AWS" 74 | identifiers = [var.aws_context.iam_issuer_arn] 75 | } 76 | } 77 | 78 | statement { 79 | sid = "Allow use of the key" 80 | effect = "Allow" 81 | actions = [ 82 | "kms:Decrypt", 83 | "kms:DescribeKey", 84 | "kms:Encrypt", 85 | "kms:GenerateDataKey*", 86 | "kms:ReEncrypt*", 87 | ] 88 | resources = ["*"] 89 | 90 | principals { 91 | type = "AWS" 92 | identifiers = [ 93 | local.cluster_iam_role_pathed_arn 94 | ] 95 | } 96 | } 97 | 98 | statement { 99 | sid = "Allow attachment of persistent resources" 100 | effect = "Allow" 101 | actions = [ 102 | "kms:CreateGrant", 103 | "kms:ListGrants", 104 | "kms:RevokeGrant", 105 | ] 106 | resources = ["*"] 107 | 108 | principals { 109 | type = "AWS" 110 | identifiers = [ 111 | local.cluster_iam_role_pathed_arn 112 | ] 113 | } 114 | 115 | condition { 116 | test = "Bool" 117 | variable = "kms:GrantIsForAWSResource" 118 | values = ["true"] 119 | } 120 | } 121 | } 122 | 123 | data "aws_iam_policy_document" "assume_role_policy" { 124 | statement { 125 | sid = "EKSClusterAssumeRole" 126 | actions = ["sts:AssumeRole"] 127 | 128 | principals { 129 | type = "Service" 130 | identifiers = ["eks.${var.aws_context.partition_dns_suffix}"] 131 | } 132 | } 133 | } 134 | 135 | data "tls_certificate" "cluster_certificate" { 136 | url = aws_eks_cluster.eks.identity[0].oidc[0].issuer 137 | } 138 | -------------------------------------------------------------------------------- /logging.tf: -------------------------------------------------------------------------------- 1 | resource "aws_s3_bucket" "bucket_logs" { 2 | bucket = "${var.infrastructurename}-logs" 3 | tags = var.tags 4 | force_destroy = true 5 | } 6 | 7 | resource "aws_s3_bucket_public_access_block" "buckets_logs_access" { 8 | bucket = aws_s3_bucket.bucket_logs.id 9 | block_public_acls = true 10 | block_public_policy = true 11 | ignore_public_acls = true 12 | restrict_public_buckets = true 13 | } 14 | 15 | resource "aws_s3_bucket_policy" "log_bucket_policy" { 16 | bucket = aws_s3_bucket.bucket_logs.bucket 17 | policy = jsonencode(local.log_bucket_policy) 18 | } 19 | 20 | resource "aws_s3_bucket_server_side_encryption_configuration" "bucket_logs_encryption" { 21 | bucket = aws_s3_bucket.bucket_logs.bucket 22 | 23 | rule { 24 | apply_server_side_encryption_by_default { 25 | sse_algorithm = "aws:kms" 26 | kms_master_key_id = var.aws_managed_kms ? null : aws_kms_key.s3_logs_key[0].arn 27 | } 28 | } 29 | } 30 | 31 | resource "aws_kms_key" "s3_logs_key" { 32 | count = var.aws_managed_kms ? 0 : 1 33 | description = "KMS key for encrypting S3 access logs" 34 | enable_key_rotation = true 35 | tags = var.tags 36 | policy = < str: 15 | """! Method for creating string representation of AWS resource instance. 16 | @param parent (models.structure.ResourceType) Object of ResourceType to which this instance belongs to. 17 | 18 | @return string 19 | """ 20 | if parent.arn_regex_name: 21 | self.name = re.search(parent.arn_regex_name, self.arn).group("name") 22 | if parent.arn_regex_id: 23 | self.name = re.search(parent.arn_regex_id, self.arn).group("id") 24 | 25 | return f"| {self.name} | tbd | tbd |" 26 | 27 | 28 | class ResourceType: 29 | """! Class representation of AWS resource type.""" 30 | 31 | def __init__(self, name: str, icon: str, arn: str, type: str, source: str, arn_regex_name: str, arn_regex_id: str): 32 | self.name: str = name 33 | self.icon: str = icon 34 | self.arn = arn 35 | self.type = type 36 | self.source = source 37 | self.arn_regex_name: str = arn_regex_name 38 | self.arn_regex_id: str = arn_regex_id 39 | self.instances: list[Instance] = list() 40 | 41 | def get_title(self) -> str: 42 | """! Method for creating Markdown title string of this object 43 | 44 | @return string 45 | """ 46 | title = f'### ' 47 | if self.icon != None: 48 | title = title + f"![{self.name}]({PREFIX}{self.icon})" 49 | title = f"{title} {self.name}" 50 | return title 51 | 52 | def markdown(self) -> str: 53 | """! Method for creating Markdown representation of this object. 54 | 55 | @return string 56 | """ 57 | if self.instances: 58 | entries = list() 59 | entries.append("| Name | Description | Mandatory |") 60 | entries.append("| - | - | - |") 61 | for instance in self.instances: 62 | entries.append(instance.get_entry(self)) 63 | return f"{self.get_title()}\n\n{"\n".join(entries)}\n\n" 64 | return "" 65 | 66 | 67 | class Service: 68 | """! Class representation of AWS service""" 69 | 70 | def __init__(self, name: str, icon: str): 71 | self.name = name 72 | self.icon = icon 73 | self.resources: list[ResourceType] = list() 74 | 75 | def get_title(self) -> str: 76 | """! Method for creating Markdown title string of this object 77 | 78 | @return string 79 | """ 80 | return f'## ![{self.name}]({PREFIX}{self.icon}) {self.name}' 81 | 82 | 83 | class Category: 84 | """! Class representation of AWS Category.""" 85 | 86 | def __init__(self, name: str, icon: str, path: str): 87 | self.name = name 88 | self.icon = icon 89 | self.path = path 90 | self.services: list[Service] = list() 91 | 92 | def get_title(self) -> str: 93 | """! Method for creating Markdown title string of this object 94 | 95 | @return string 96 | """ 97 | return f'# ![{self.name}]({PREFIX}{self.icon}) {self.name}' 98 | 99 | @staticmethod 100 | def from_data(data: dict) -> Category: 101 | """! Static method for creating instance of this object from dictionary 102 | 103 | @return models.structure.Category 104 | """ 105 | return Category(data["name"], data["path"] + "/" + data["icon"], data["path"]) 106 | -------------------------------------------------------------------------------- /network.tf: -------------------------------------------------------------------------------- 1 | module "vpc" { 2 | count = local.create_vpc ? 1 : 0 3 | source = "terraform-aws-modules/vpc/aws" 4 | version = "v5.8.1" 5 | name = "${local.infrastructurename}-vpc" 6 | cidr = var.vpcCidr 7 | azs = data.aws_availability_zones.available.names 8 | private_subnets = var.vpcPrivateSubnets 9 | public_subnets = var.vpcPublicSubnets 10 | enable_nat_gateway = true 11 | create_igw = true 12 | enable_dns_hostnames = true 13 | single_nat_gateway = true 14 | tags = var.tags 15 | public_subnet_tags = { 16 | "kubernetes.io/cluster/${local.infrastructurename}" = "shared" 17 | "kubernetes.io/role/elb" = "1" 18 | "purpose" = "public" 19 | } 20 | private_subnet_tags = { 21 | "kubernetes.io/cluster/${local.infrastructurename}" = "shared" 22 | "kubernetes.io/role/internal-elb" = "1" 23 | "purpose" = "private" 24 | } 25 | } 26 | 27 | module "security_group" { 28 | source = "terraform-aws-modules/security-group/aws" 29 | version = "~> 4" 30 | name = "${var.infrastructurename}-db-sg" 31 | description = "PostgreSQL security group" 32 | vpc_id = local.vpc_id 33 | tags = var.tags 34 | ingress_with_cidr_blocks = [ 35 | { 36 | from_port = 5432 37 | to_port = 5432 38 | protocol = "tcp" 39 | description = "PostgreSQL access from within VPC" 40 | cidr_blocks = local.create_vpc ? module.vpc[0].vpc_cidr_block : data.aws_vpc.preconfigured[0].cidr_block 41 | }, 42 | ] 43 | } 44 | 45 | # [EC2.6] VPC flow logging should be enabled in all VPCs 46 | # https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-ec2-6 47 | resource "aws_flow_log" "flowlog" { 48 | count = local.create_vpc ? 1 : 0 49 | iam_role_arn = aws_iam_role.flowlogs_role[0].arn 50 | log_destination = aws_cloudwatch_log_group.flowlogs[0].arn 51 | traffic_type = "ALL" 52 | vpc_id = local.vpc_id 53 | tags = var.tags 54 | } 55 | 56 | resource "aws_cloudwatch_log_group" "flowlogs" { 57 | count = local.create_vpc ? 1 : 0 58 | name = local.flowlogs_cloudwatch_loggroup 59 | retention_in_days = var.cloudwatch_retention 60 | kms_key_id = var.aws_managed_kms ? null : aws_kms_key.kms_key_cloudwatch_log_group[0].arn 61 | tags = var.tags 62 | } 63 | 64 | resource "aws_iam_role" "flowlogs_role" { 65 | count = local.create_vpc ? 1 : 0 66 | name = "${local.infrastructurename}-flowlogs-role" 67 | description = "AWS IAM service role for VPC flow logs." 68 | assume_role_policy = < str: 15 | """! Static method for parsing rule description from queried AWS Security group Rule. 16 | @param data (dict) https://docs.aws.amazon.com/cli/latest/reference/ec2/describe-security-groups.html#output 17 | 18 | @return string 19 | """ 20 | for range in data["IpRanges"]: 21 | if range.get("Description", "") != "": 22 | return range["Description"] 23 | return "" 24 | 25 | @staticmethod 26 | def get_port_range(data: dict) -> str: 27 | """! Static method for parsing rule's from and to port from queried AWS Security group rule. 28 | @param data (dict) https://docs.aws.amazon.com/cli/latest/reference/ec2/describe-security-groups.html#output 29 | 30 | @return string 31 | """ 32 | fromport = data.get("FromPort", False) 33 | toport = data.get("ToPort") 34 | if fromport: 35 | return f"{fromport} - {toport}" if fromport != toport else fromport 36 | return "All" 37 | 38 | @staticmethod 39 | def get_protocol(data: dict) -> str: 40 | """! Static method for parsing rule's protocol from queried AWS Security group rule. 41 | @param data (dict) https://docs.aws.amazon.com/cli/latest/reference/ec2/describe-security-groups.html#output 42 | 43 | @return string 44 | """ 45 | if data["IpProtocol"] == "-1": 46 | return "All" 47 | return data["IpProtocol"] 48 | 49 | @staticmethod 50 | def from_data(direction: str, data: dict) -> Rule: 51 | """! Static method for creating instance of this class from direction and data. 52 | @param direction (str) is the rule inbound or outbound 53 | @param data (dict) https://docs.aws.amazon.com/cli/latest/reference/ec2/describe-security-groups.html#output 54 | 55 | @return models.security_group.Rule 56 | """ 57 | return Rule(direction, Rule.get_description(data), Rule.get_port_range(data), Rule.get_protocol(data)) 58 | 59 | def markdown_table(self) -> str: 60 | """! Method for getting Markdown representation of this object. 61 | 62 | @return str 63 | """ 64 | return f"{self.direction}{self.protocol}{self.port_range}{self.description}" 65 | 66 | 67 | class SecurityGroup: 68 | """! Class representation of AWS Security group.""" 69 | 70 | def __init__(self, name: str, description: str, rules: list[Rule]): 71 | self.name = name 72 | self.description = description 73 | self.rules: list[Rule] = rules 74 | 75 | def markdown_table(self) -> str: 76 | """! Method for getting Markdown representation of this object. 77 | 78 | @return str 79 | """ 80 | rowspan = 1 if len(self.rules) == 0 else len(self.rules) 81 | markdown = f'{self.name}{self.description}' 82 | if len(self.rules) != 0: 83 | markdown = f"{markdown}{self.rules[0].markdown_table()}" 84 | for rule in self.rules[1:]: 85 | markdown += f"{rule.markdown_table()}" 86 | return markdown 87 | 88 | @staticmethod 89 | def get_name(data: dict) -> str: 90 | """! Static method for parsing Security group name from queried AWS Security group data. 91 | @param data (dict) https://docs.aws.amazon.com/cli/latest/reference/ec2/describe-security-groups.html#output 92 | 93 | @return string 94 | """ 95 | for tag in data.get("Tags", []): 96 | if tag["Key"] == "Name": 97 | return tag["Value"] 98 | return data["GroupName"] 99 | 100 | @staticmethod 101 | def from_data(data: dict) -> SecurityGroup: 102 | """! Static method for creating instance of this class from queried data. 103 | @param data (dict) https://docs.aws.amazon.com/cli/latest/reference/ec2/describe-security-groups.html#output 104 | 105 | @return models.security_group.SecurityGroup 106 | """ 107 | name = SecurityGroup.get_name(data) 108 | description = data.get("Description", "") 109 | inbound_rules = [Rule.from_data("inbound", rule_data) for rule_data in data["IpPermissions"]] 110 | outbound_rules = [Rule.from_data("outbound", rule_data) for rule_data in data["IpPermissionsEgress"]] 111 | rules = list([*inbound_rules, *outbound_rules]) 112 | if len(rules) == 0: 113 | rules.append(Rule("-", "-", "-", "-")) 114 | return SecurityGroup(name, description, rules) 115 | -------------------------------------------------------------------------------- /modules/ivs_aws_instance/storage.tf: -------------------------------------------------------------------------------- 1 | resource "aws_s3_bucket" "data_bucket" { 2 | count = var.data_bucket.create ? 1 : 0 3 | bucket = var.data_bucket.name 4 | tags = var.tags 5 | force_destroy = var.enable_deletion_protection ? false : true 6 | } 7 | 8 | resource "aws_s3_bucket_logging" "data_bucket_logging" { 9 | count = var.data_bucket.create ? 1 : 0 10 | bucket = aws_s3_bucket.data_bucket[0].id 11 | target_bucket = var.log_bucket 12 | target_prefix = "logs/bucket/${aws_s3_bucket.data_bucket[0].id}/" 13 | } 14 | 15 | resource "aws_s3_bucket" "rawdata_bucket" { 16 | count = var.raw_data_bucket.create ? 1 : 0 17 | bucket = var.raw_data_bucket.name 18 | tags = var.tags 19 | force_destroy = var.enable_deletion_protection ? false : true 20 | } 21 | 22 | resource "aws_s3_bucket_logging" "rawdata_bucket_logging" { 23 | count = var.raw_data_bucket.create ? 1 : 0 24 | bucket = aws_s3_bucket.rawdata_bucket[0].id 25 | target_bucket = var.log_bucket 26 | target_prefix = "logs/bucket/${aws_s3_bucket.rawdata_bucket[0].id}/" 27 | } 28 | 29 | resource "aws_iam_role_policy" "eks_node_s3_access_policy" { 30 | for_each = var.nodeRoleNames 31 | role = each.value 32 | name = "s3-access-policy" 33 | policy = <.*) 36 | - name: Add-on 37 | arn: eks:addon 38 | arn_regex_name: addon/.*/(?P.*)/.* 39 | - name: Node group 40 | arn: eks:nodegroup 41 | - name: Database 42 | path: Database 43 | icon: Database.png 44 | services: 45 | - name: Amazon Relational Database 46 | icon: RDS.png 47 | resources: 48 | - name: PostgreSQL instance 49 | icon: AuroraPostgreSQLInstance.png 50 | arn: rds:db 51 | arn_regex_name: .*:(?P.*)$ 52 | - name: Subnet group 53 | arn: rds:subgrp 54 | arn_regex_name: subgrp:(?P.*)$ 55 | - name: Management & Governance 56 | path: ManagementGovernance 57 | icon: ManagementGovernance.png 58 | services: 59 | - name: Amazon CloudWatch 60 | icon: CloudWatch.png 61 | resources: 62 | - name: Log groups 63 | icon: CloudWatchLogs.png 64 | arn: logs:log-group 65 | arn_regex_name: .*:(?P\/.*)$ 66 | - name: Networking & Content Delivery 67 | icon: NetworkingContentDelivery.png 68 | path: NetworkingContentDelivery 69 | services: 70 | - name: Amazon Virtual Private Cloud 71 | icon: VirtualPrivateCloud.png 72 | resources: 73 | - name: VPC Requirements 74 | type: requirements 75 | source: vpc_requirements.md 76 | - name: Internet gateway 77 | icon: VPCInternetGateway.png 78 | arn: ec2:internet-gateway 79 | - name: NAT gateway 80 | icon: VPCNATGateway.png 81 | arn: ec2:natgateway 82 | - name: Route table 83 | arn: ec2:route-table 84 | - name: Security group 85 | arn: ec2:security-group 86 | - name: Subnet 87 | arn: ec2:subnet 88 | - name: Private subnets requirements 89 | type: requirements 90 | source: private_subnets_requirements.md 91 | - name: Public subnets requirements 92 | type: requirements 93 | source: public_subnets_requirements.md 94 | - name: "'Private' route table requirements" 95 | type: requirements 96 | source: private_route_table_requirements.md 97 | - name: "'Public' route table requirements" 98 | type: requirements 99 | source: public_route_table_requirements.md 100 | - name: Virtual Private Cloud 101 | arn: ec2:vpc 102 | - name: Elastic Load Balancing 103 | icon: ElasticLoadBalancing.png 104 | resources: 105 | - name: Network Load Balancer 106 | icon: ElasticLoadBalancingNetworkLoadBalancer.png 107 | arn: "elasticloadbalancing:loadbalancer/net" 108 | type: static 109 | source: load_balancer.md 110 | - name: Storage 111 | path: Storage 112 | icon: Storage.png 113 | services: 114 | - name: Amazon Simple Storage Service 115 | icon: SimpleStorageService.png 116 | resources: 117 | - name: Bucket 118 | icon: SimpleStorageServiceBucket.png 119 | arn: "s3" 120 | arn_regex_name: .*:(?P.*)$ 121 | - name: AWS Backup 122 | icon: Backup.png 123 | resources: 124 | - name: Backup Vault 125 | icon: BackupBackupVault.png 126 | arn: "backup:backup-vault" 127 | arn_regex_name: .*:(?P.*)$ 128 | - name: Analytics 129 | path: Analytics 130 | icon: Analytics.png 131 | services: 132 | - name: Amazon OpenSearch Service 133 | icon: OpenSearchService.png 134 | resources: 135 | - name: Domain 136 | arn: "es:domain" 137 | arn_regex_name: .*:domain/(?P.*)$ 138 | - name: Security, Identity, & Compliance 139 | path: SecurityIdentityCompliance 140 | icon: SecurityIdentityCompliance.png 141 | services: 142 | - name: AWS Key Management Service 143 | icon: KeyManagementService.png 144 | resources: 145 | - name: Customer managed keys 146 | arn: kms:key 147 | type: static 148 | source: kms.md 149 | - name: AWS Identity and Access Management 150 | icon: IdentityandAccessManagement.png 151 | resources: 152 | - name: Roles 153 | icon: IdentityAccessManagementRole.png 154 | - name: Policies 155 | icon: IdentityAccessManagementPermissions.png 156 | -------------------------------------------------------------------------------- /modules/k8s_eks_addons/cluster-autoscaler.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | name = "cluster-autoscaler" 3 | namespace = "kube-system" 4 | service_account = "${local.name}-sa" 5 | } 6 | 7 | data "aws_iam_policy_document" "cluster_autoscaler" { 8 | count = var.cluster_autoscaler_config.enable ? 1 : 0 9 | 10 | statement { 11 | sid = "" 12 | effect = "Allow" 13 | resources = ["*"] 14 | 15 | actions = [ 16 | "autoscaling:DescribeAutoScalingGroups", 17 | "autoscaling:DescribeAutoScalingInstances", 18 | "autoscaling:DescribeLaunchConfigurations", 19 | "autoscaling:DescribeTags", 20 | "ec2:DescribeInstanceTypes", 21 | "ec2:DescribeLaunchTemplateVersions", 22 | "autoscaling:DescribeScalingActivities" 23 | ] 24 | } 25 | 26 | statement { 27 | sid = "" 28 | effect = "Allow" 29 | resources = ["*"] 30 | 31 | actions = [ 32 | "ec2:DescribeInstanceTypes", 33 | ] 34 | 35 | condition { 36 | test = "StringEquals" 37 | variable = "autoscaling:ResourceTag/k8s.io/cluster-autoscaler/${var.addon_context.eks_cluster_id}" 38 | values = ["owned"] 39 | } 40 | } 41 | 42 | statement { 43 | sid = "" 44 | effect = "Allow" 45 | resources = ["arn:${var.addon_context.aws_context.partition_id}:autoscaling:${var.addon_context.aws_context.region_name}:${var.addon_context.aws_context.caller_identity_account_id}:autoScalingGroup:*"] 46 | 47 | actions = [ 48 | "autoscaling:SetDesiredCapacity", 49 | "autoscaling:TerminateInstanceInAutoScalingGroup", 50 | ] 51 | 52 | condition { 53 | test = "StringEquals" 54 | variable = "autoscaling:ResourceTag/k8s.io/cluster-autoscaler/${var.addon_context.eks_cluster_id}" 55 | values = ["owned"] 56 | } 57 | } 58 | 59 | statement { 60 | sid = "" 61 | effect = "Allow" 62 | resources = ["arn:${var.addon_context.aws_context.partition_id}:eks:${var.addon_context.aws_context.region_name}:${var.addon_context.aws_context.caller_identity_account_id}:nodegroup/${var.addon_context.eks_cluster_id}/*"] 63 | 64 | actions = [ 65 | "eks:DescribeNodegroup", 66 | ] 67 | 68 | condition { 69 | test = "StringEquals" 70 | variable = "autoscaling:ResourceTag/k8s.io/cluster-autoscaler/${var.addon_context.eks_cluster_id}" 71 | values = ["owned"] 72 | } 73 | } 74 | } 75 | 76 | resource "aws_iam_policy" "cluster_autoscaler" { 77 | count = var.cluster_autoscaler_config.enable ? 1 : 0 78 | name = "${var.addon_context.eks_cluster_id}-cluster-autoscaler-irsa" 79 | description = "Cluster Autoscaler IAM policy" 80 | policy = data.aws_iam_policy_document.cluster_autoscaler[0].json 81 | 82 | tags = var.tags 83 | } 84 | 85 | resource "helm_release" "cluster_autoscaler" { 86 | count = var.cluster_autoscaler_config.enable ? 1 : 0 87 | name = local.name 88 | namespace = local.namespace 89 | repository = var.cluster_autoscaler_config.helm_repository 90 | chart = local.name 91 | version = var.cluster_autoscaler_config.helm_version 92 | timeout = 1200 93 | values = [templatefile("${path.module}/templates/autoscaler_values.yaml", { 94 | aws_region = var.addon_context.aws_context.region_name 95 | eks_cluster_id = var.addon_context.eks_cluster_id 96 | image_tag = "v${var.addon_context.eks_cluster_version}.0" 97 | service_account = local.service_account 98 | }), 99 | var.cluster_autoscaler_config.chart_values 100 | ] 101 | description = "Cluster AutoScaler helm Chart deployment configuration." 102 | dependency_update = true 103 | 104 | depends_on = [aws_iam_role.cluster_autoscaler] 105 | } 106 | 107 | resource "kubernetes_service_account_v1" "cluster_autoscaler" { 108 | count = var.cluster_autoscaler_config.enable ? 1 : 0 109 | metadata { 110 | name = local.service_account 111 | namespace = local.namespace 112 | annotations = { "eks.amazonaws.com/role-arn" : aws_iam_role.cluster_autoscaler[0].arn } 113 | } 114 | 115 | automount_service_account_token = true 116 | } 117 | 118 | resource "aws_iam_role" "cluster_autoscaler" { 119 | count = var.cluster_autoscaler_config.enable ? 1 : 0 120 | 121 | name = format("%s-%s-%s", var.addon_context.eks_cluster_id, trim(local.service_account, "-*"), "irsa") 122 | description = "AWS IAM Role for the Kubernetes service account ${local.service_account}." 123 | assume_role_policy = jsonencode({ 124 | "Version" : "2012-10-17", 125 | "Statement" : [ 126 | { 127 | "Effect" : "Allow", 128 | "Principal" : { 129 | "Federated" : "arn:${var.addon_context.aws_context.partition_id}:iam::${var.addon_context.aws_context.caller_identity_account_id}:oidc-provider/${var.addon_context.eks_oidc_issuer_url}" 130 | }, 131 | "Action" : "sts:AssumeRoleWithWebIdentity", 132 | "Condition" : { 133 | "StringLike" : { 134 | "${var.addon_context.eks_oidc_issuer_url}:sub" : "system:serviceaccount:${local.namespace}:${local.service_account}", 135 | "${var.addon_context.eks_oidc_issuer_url}:aud" : "sts.amazonaws.com" 136 | } 137 | } 138 | } 139 | ] 140 | }) 141 | force_detach_policies = true 142 | 143 | tags = var.tags 144 | } 145 | 146 | resource "aws_iam_role_policy_attachment" "cluster_autoscaler" { 147 | count = var.cluster_autoscaler_config.enable ? 1 : 0 148 | 149 | policy_arn = aws_iam_policy.cluster_autoscaler[0].arn 150 | role = aws_iam_role.cluster_autoscaler[0].name 151 | } 152 | -------------------------------------------------------------------------------- /patch-manager.tf: -------------------------------------------------------------------------------- 1 | resource "aws_ssm_maintenance_window" "scan" { 2 | count = var.enable_patching ? 1 : 0 3 | name = "scan-${var.infrastructurename}" 4 | cutoff = 0 5 | description = "Maintenance window for scanning for patch compliance" 6 | duration = var.maintainance_duration 7 | schedule = var.scan_schedule 8 | schedule_timezone = "UTC" 9 | tags = var.tags 10 | } 11 | 12 | resource "aws_ssm_maintenance_window" "install" { 13 | count = var.enable_patching ? 1 : 0 14 | name = "install-${var.infrastructurename}" 15 | cutoff = 0 16 | description = "Maintenance window for applying patches" 17 | duration = var.maintainance_duration 18 | schedule = var.install_schedule 19 | schedule_timezone = "UTC" 20 | tags = var.tags 21 | } 22 | 23 | resource "aws_ssm_maintenance_window_target" "scan" { 24 | count = var.enable_patching ? 1 : 0 25 | window_id = aws_ssm_maintenance_window.scan[0].id 26 | resource_type = "INSTANCE" 27 | 28 | targets { 29 | key = "tag:Patch Group" 30 | values = [local.patchgroupid] 31 | } 32 | } 33 | 34 | resource "aws_ssm_maintenance_window_target" "scan_eks_nodes" { 35 | count = var.enable_patching ? 1 : 0 36 | window_id = aws_ssm_maintenance_window.scan[0].id 37 | resource_type = "INSTANCE" 38 | 39 | targets { 40 | key = "tag:eks:cluster-name" 41 | values = [module.eks.eks_cluster_id] 42 | } 43 | 44 | } 45 | 46 | resource "aws_ssm_maintenance_window_target" "install" { 47 | count = var.enable_patching && var.licenseServer ? 1 : 0 48 | window_id = aws_ssm_maintenance_window.install[0].id 49 | resource_type = "INSTANCE" 50 | 51 | targets { 52 | key = "tag:Patch Group" 53 | values = [local.patchgroupid] 54 | } 55 | } 56 | 57 | resource "aws_ssm_maintenance_window_task" "scan" { 58 | count = var.enable_patching ? 1 : 0 59 | max_concurrency = 50 60 | max_errors = 0 61 | priority = 1 62 | task_type = "RUN_COMMAND" 63 | task_arn = "AWS-RunPatchBaseline" 64 | window_id = aws_ssm_maintenance_window.scan[0].id 65 | targets { 66 | key = "WindowTargetIds" 67 | values = [aws_ssm_maintenance_window_target.scan[0].id, aws_ssm_maintenance_window_target.scan_eks_nodes[0].id] 68 | } 69 | task_invocation_parameters { 70 | run_command_parameters { 71 | comment = "Runs a compliance scan" 72 | timeout_seconds = 600 73 | 74 | cloudwatch_config { 75 | cloudwatch_log_group_name = aws_cloudwatch_log_group.ssm_scan_log_group[0].name 76 | cloudwatch_output_enabled = true 77 | } 78 | 79 | parameter { 80 | name = "Operation" 81 | values = ["Scan"] 82 | } 83 | } 84 | } 85 | } 86 | 87 | 88 | resource "aws_ssm_maintenance_window_task" "install" { 89 | count = var.enable_patching && var.licenseServer ? 1 : 0 90 | max_concurrency = 50 91 | max_errors = 0 92 | priority = 1 93 | task_type = "RUN_COMMAND" 94 | task_arn = "AWS-RunPatchBaseline" 95 | window_id = aws_ssm_maintenance_window.install[0].id 96 | 97 | targets { 98 | key = "WindowTargetIds" 99 | values = [aws_ssm_maintenance_window_target.install[0].id] 100 | } 101 | 102 | task_invocation_parameters { 103 | run_command_parameters { 104 | comment = "Installs necessary patches" 105 | timeout_seconds = 600 106 | 107 | cloudwatch_config { 108 | cloudwatch_log_group_name = aws_cloudwatch_log_group.ssm_install_log_group[0].name 109 | cloudwatch_output_enabled = true 110 | } 111 | parameter { 112 | name = "Operation" 113 | values = ["Install"] 114 | } 115 | } 116 | } 117 | } 118 | 119 | resource "aws_cloudwatch_log_group" "ssm_scan_log_group" { 120 | count = var.enable_patching ? 1 : 0 121 | name = local.patch_manager_cloudwatch_loggroup_scan 122 | retention_in_days = var.cloudwatch_retention 123 | kms_key_id = var.aws_managed_kms ? null : aws_kms_key.kms_key_cloudwatch_log_group[0].arn 124 | tags = var.tags 125 | } 126 | 127 | resource "aws_cloudwatch_log_group" "ssm_install_log_group" { 128 | count = var.enable_patching && var.licenseServer ? 1 : 0 129 | name = local.patch_manager_cloudwatch_loggroup_install 130 | retention_in_days = var.cloudwatch_retention 131 | kms_key_id = var.aws_managed_kms ? null : aws_kms_key.kms_key_cloudwatch_log_group[0].arn 132 | tags = var.tags 133 | } 134 | 135 | resource "aws_ssm_patch_baseline" "production" { 136 | count = var.enable_patching && var.licenseServer ? 1 : 0 137 | name = "${var.infrastructurename}-patch-baseline" 138 | description = "Default Patch Baseline for Amazon Linux 2 Provided by AWS but with Medium Severity Security Patches." 139 | operating_system = "AMAZON_LINUX_2" 140 | approval_rule { 141 | approve_after_days = 7 142 | 143 | patch_filter { 144 | key = "SEVERITY" 145 | values = ["Important", "Critical", "Medium"] 146 | } 147 | patch_filter { 148 | key = "CLASSIFICATION" 149 | values = ["Security"] 150 | } 151 | } 152 | approval_rule { 153 | approve_after_days = 7 154 | 155 | patch_filter { 156 | key = "CLASSIFICATION" 157 | values = ["Bugfix"] 158 | } 159 | } 160 | tags = var.tags 161 | } 162 | 163 | resource "aws_ssm_patch_group" "patch_group" { 164 | count = var.enable_patching && var.licenseServer ? 1 : 0 165 | baseline_id = aws_ssm_patch_baseline.production[0].id 166 | patch_group = local.patchgroupid 167 | } 168 | -------------------------------------------------------------------------------- /templates/least_permissions/deploy_ivs_policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": [ 7 | "autoscaling:CreateOrUpdateTags", 8 | "autoscaling:DescribeTags", 9 | "backup:CreateBackupPlan", 10 | "backup:CreateBackupSelection", 11 | "backup:CreateBackupVault", 12 | "backup:DescribeBackupVault", 13 | "backup:GetBackupPlan", 14 | "backup:GetBackupSelection", 15 | "backup:ListTags", 16 | "ec2:AllocateAddress", 17 | "ec2:AssociateRouteTable", 18 | "ec2:AttachInternetGateway", 19 | "ec2:AuthorizeSecurityGroupEgress", 20 | "ec2:AuthorizeSecurityGroupIngress", 21 | "ec2:CreateFlowLogs", 22 | "ec2:CreateInternetGateway", 23 | "ec2:CreateLaunchTemplate", 24 | "ec2:CreateNatGateway", 25 | "ec2:CreateNetworkAclEntry", 26 | "ec2:CreateRoute", 27 | "ec2:CreateRouteTable", 28 | "ec2:CreateSecurityGroup", 29 | "ec2:CreateSubnet", 30 | "ec2:CreateTags", 31 | "ec2:CreateVpc", 32 | "ec2:DeleteNetworkAclEntry", 33 | "ec2:DescribeAddresses", 34 | "ec2:DescribeAddressesAttribute", 35 | "ec2:DescribeAvailabilityZones", 36 | "ec2:DescribeFlowLogs", 37 | "ec2:DescribeImages", 38 | "ec2:DescribeInstanceAttribute", 39 | "ec2:DescribeInstanceCreditSpecifications", 40 | "ec2:DescribeInstanceTypes", 41 | "ec2:DescribeInstances", 42 | "ec2:DescribeInternetGateways", 43 | "ec2:DescribeLaunchTemplateVersions", 44 | "ec2:DescribeLaunchTemplates", 45 | "ec2:DescribeNatGateways", 46 | "ec2:DescribeNetworkAcls", 47 | "ec2:DescribeRouteTables", 48 | "ec2:DescribeSecurityGroupRules", 49 | "ec2:DescribeSecurityGroups", 50 | "ec2:DescribeSubnets", 51 | "ec2:DescribeTags", 52 | "ec2:DescribeVolumes", 53 | "ec2:DescribeVpcAttribute", 54 | "ec2:DescribeVpcs", 55 | "ec2:ModifyVpcAttribute", 56 | "ec2:RevokeSecurityGroupEgress", 57 | "ec2:RevokeSecurityGroupIngress", 58 | "ec2:RunInstances", 59 | "eks:CreateAddon", 60 | "eks:CreateCluster", 61 | "eks:CreateNodegroup", 62 | "eks:DescribeAddon", 63 | "eks:DescribeAddonVersions", 64 | "eks:DescribeCluster", 65 | "eks:DescribeNodegroup", 66 | "iam:AddRoleToInstanceProfile", 67 | "iam:AttachRolePolicy", 68 | "iam:CreateInstanceProfile", 69 | "iam:CreateOpenIDConnectProvider", 70 | "iam:CreatePolicy", 71 | "iam:CreateRole", 72 | "iam:GetInstanceProfile", 73 | "iam:GetOpenIDConnectProvider", 74 | "iam:GetPolicy", 75 | "iam:GetPolicyVersion", 76 | "iam:GetRole", 77 | "iam:GetRolePolicy", 78 | "iam:ListAttachedRolePolicies", 79 | "iam:ListRolePolicies", 80 | "iam:PutRolePolicy", 81 | "kms:CreateAlias", 82 | "kms:CreateKey", 83 | "kms:DescribeKey", 84 | "kms:EnableKeyRotation", 85 | "kms:GetKeyPolicy", 86 | "kms:GetKeyRotationStatus", 87 | "kms:ListAliases", 88 | "kms:ListResourceTags", 89 | "logs:CreateLogGroup", 90 | "logs:DescribeLogGroups", 91 | "logs:ListTagsForResource", 92 | "logs:PutRetentionPolicy", 93 | "opensearch:CreateDomain", 94 | "opensearch:DescribeDomain", 95 | "opensearch:DescribeDomainConfig", 96 | "opensearch:GetCompatibleVersions", 97 | "opensearch:ListTags", 98 | "s3:CreateBucket", 99 | "s3:GetBucketAccelerateConfiguration", 100 | "s3:GetBucketAcl", 101 | "s3:GetBucketCors", 102 | "s3:GetBucketEncryption", 103 | "s3:GetBucketLifecycleConfiguration", 104 | "s3:GetBucketLogging", 105 | "s3:GetBucketPolicy", 106 | "s3:GetBucketReplication", 107 | "s3:GetBucketRequestPayment", 108 | "s3:GetBucketTagging", 109 | "s3:GetBucketVersioning", 110 | "s3:GetBucketWebsite", 111 | "s3:GetObject", 112 | "s3:GetObjectLockConfiguration", 113 | "s3:GetPublicAccessBlock", 114 | "s3:HeadBucket", 115 | "s3:HeadObject", 116 | "s3:ListObjectsV2", 117 | "s3:PutBucketEncryption", 118 | "s3:PutBucketLogging", 119 | "s3:PutBucketPolicy", 120 | "s3:PutBucketTagging", 121 | "s3:PutBucketVersioning", 122 | "s3:PutObject", 123 | "s3:PutPublicAccessBlock", 124 | "secretsmanager:DescribeSecret", 125 | "secretsmanager:GetResourcePolicy", 126 | "secretsmanager:GetSecretValue", 127 | "ssm:CreateMaintenanceWindow", 128 | "ssm:CreatePatchBaseline", 129 | "ssm:DescribeMaintenanceWindowTargets", 130 | "ssm:DescribePatchGroups", 131 | "ssm:GetMaintenanceWindow", 132 | "ssm:GetMaintenanceWindowTask", 133 | "ssm:GetPatchBaseline", 134 | "ssm:ListTagsForResource", 135 | "ssm:RegisterPatchBaselineForPatchGroup", 136 | "ssm:RegisterTargetWithMaintenanceWindow", 137 | "ssm:RegisterTaskWithMaintenanceWindow", 138 | "sso:GetRoleCredentials", 139 | "sts:GetCallerIdentity" 140 | ], 141 | "Resource": "*" 142 | } 143 | ] 144 | } 145 | -------------------------------------------------------------------------------- /templates/least_permissions/deploy_simphera_policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": [ 7 | "autoscaling:CreateOrUpdateTags", 8 | "autoscaling:DescribeTags", 9 | "backup:CreateBackupPlan", 10 | "backup:CreateBackupSelection", 11 | "backup:CreateBackupVault", 12 | "backup:DescribeBackupVault", 13 | "backup:GetBackupPlan", 14 | "backup:GetBackupSelection", 15 | "backup:ListTags", 16 | "ec2:AllocateAddress", 17 | "ec2:AssociateRouteTable", 18 | "ec2:AttachInternetGateway", 19 | "ec2:AuthorizeSecurityGroupEgress", 20 | "ec2:AuthorizeSecurityGroupIngress", 21 | "ec2:CreateFlowLogs", 22 | "ec2:CreateInternetGateway", 23 | "ec2:CreateLaunchTemplate", 24 | "ec2:CreateNatGateway", 25 | "ec2:CreateNetworkAclEntry", 26 | "ec2:CreateRoute", 27 | "ec2:CreateRouteTable", 28 | "ec2:CreateSecurityGroup", 29 | "ec2:CreateSubnet", 30 | "ec2:CreateTags", 31 | "ec2:CreateVpc", 32 | "ec2:DeleteNetworkAclEntry", 33 | "ec2:DescribeAddresses", 34 | "ec2:DescribeAddressesAttribute", 35 | "ec2:DescribeAvailabilityZones", 36 | "ec2:DescribeFlowLogs", 37 | "ec2:DescribeImages", 38 | "ec2:DescribeInstanceAttribute", 39 | "ec2:DescribeInstanceCreditSpecifications", 40 | "ec2:DescribeInstanceTypes", 41 | "ec2:DescribeInstances", 42 | "ec2:DescribeInternetGateways", 43 | "ec2:DescribeLaunchTemplateVersions", 44 | "ec2:DescribeLaunchTemplates", 45 | "ec2:DescribeNatGateways", 46 | "ec2:DescribeNetworkAcls", 47 | "ec2:DescribeRouteTables", 48 | "ec2:DescribeSecurityGroupRules", 49 | "ec2:DescribeSecurityGroups", 50 | "ec2:DescribeSubnets", 51 | "ec2:DescribeTags", 52 | "ec2:DescribeVolumes", 53 | "ec2:DescribeVpcAttribute", 54 | "ec2:DescribeVpcs", 55 | "ec2:ModifyVpcAttribute", 56 | "ec2:RevokeSecurityGroupEgress", 57 | "ec2:RevokeSecurityGroupIngress", 58 | "ec2:RunInstances", 59 | "efs:CreateFileSystem", 60 | "efs:CreateMountTarget", 61 | "efs:DescribeFileSystemPolicy", 62 | "efs:DescribeFileSystems", 63 | "efs:DescribeLifecycleConfiguration", 64 | "efs:DescribeMountTargetSecurityGroups", 65 | "efs:DescribeMountTargets", 66 | "efs:PutFileSystemPolicy", 67 | "eks:CreateAddon", 68 | "eks:CreateCluster", 69 | "eks:CreateNodegroup", 70 | "eks:DescribeAddon", 71 | "eks:DescribeAddonVersions", 72 | "eks:DescribeCluster", 73 | "eks:DescribeNodegroup", 74 | "iam:AddRoleToInstanceProfile", 75 | "iam:AttachRolePolicy", 76 | "iam:CreateInstanceProfile", 77 | "iam:CreateOpenIDConnectProvider", 78 | "iam:CreatePolicy", 79 | "iam:CreateRole", 80 | "iam:GetInstanceProfile", 81 | "iam:GetOpenIDConnectProvider", 82 | "iam:GetPolicy", 83 | "iam:GetPolicyVersion", 84 | "iam:GetRole", 85 | "iam:GetRolePolicy", 86 | "iam:ListAttachedRolePolicies", 87 | "iam:ListRolePolicies", 88 | "iam:PutRolePolicy", 89 | "kms:CreateAlias", 90 | "kms:CreateKey", 91 | "kms:DescribeKey", 92 | "kms:EnableKeyRotation", 93 | "kms:GetKeyPolicy", 94 | "kms:GetKeyRotationStatus", 95 | "kms:ListAliases", 96 | "kms:ListResourceTags", 97 | "logs:CreateLogGroup", 98 | "logs:DescribeLogGroups", 99 | "logs:ListTagsForResource", 100 | "logs:PutRetentionPolicy", 101 | "rds:CreateDBInstance", 102 | "rds:CreateDBSubnetGroup", 103 | "rds:DescribeDBInstances", 104 | "rds:DescribeDBSubnetGroups", 105 | "rds:ListTagsForResource", 106 | "s3:CreateBucket", 107 | "s3:GetBucketAccelerateConfiguration", 108 | "s3:GetBucketAcl", 109 | "s3:GetBucketCors", 110 | "s3:GetBucketEncryption", 111 | "s3:GetBucketLifecycleConfiguration", 112 | "s3:GetBucketLogging", 113 | "s3:GetBucketPolicy", 114 | "s3:GetBucketReplication", 115 | "s3:GetBucketRequestPayment", 116 | "s3:GetBucketTagging", 117 | "s3:GetBucketVersioning", 118 | "s3:GetBucketWebsite", 119 | "s3:GetObject", 120 | "s3:GetObjectLockConfiguration", 121 | "s3:GetPublicAccessBlock", 122 | "s3:HeadBucket", 123 | "s3:HeadObject", 124 | "s3:ListObjectsV2", 125 | "s3:PutBucketEncryption", 126 | "s3:PutBucketLogging", 127 | "s3:PutBucketPolicy", 128 | "s3:PutBucketTagging", 129 | "s3:PutBucketVersioning", 130 | "s3:PutObject", 131 | "s3:PutPublicAccessBlock", 132 | "secretsmanager:DescribeSecret", 133 | "secretsmanager:GetResourcePolicy", 134 | "secretsmanager:GetSecretValue", 135 | "ssm:CreateMaintenanceWindow", 136 | "ssm:CreatePatchBaseline", 137 | "ssm:DescribeMaintenanceWindowTargets", 138 | "ssm:DescribePatchGroups", 139 | "ssm:GetMaintenanceWindow", 140 | "ssm:GetMaintenanceWindowTask", 141 | "ssm:GetPatchBaseline", 142 | "ssm:ListTagsForResource", 143 | "ssm:RegisterPatchBaselineForPatchGroup", 144 | "ssm:RegisterTargetWithMaintenanceWindow", 145 | "ssm:RegisterTaskWithMaintenanceWindow", 146 | "sso:GetRoleCredentials", 147 | "sts:GetCallerIdentity" 148 | ], 149 | "Resource": "*" 150 | } 151 | ] 152 | } 153 | -------------------------------------------------------------------------------- /templates/least_permissions/deploy_all_policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": [ 7 | "autoscaling:CreateOrUpdateTags", 8 | "autoscaling:DescribeTags", 9 | "backup:CreateBackupPlan", 10 | "backup:CreateBackupSelection", 11 | "backup:CreateBackupVault", 12 | "backup:DescribeBackupVault", 13 | "backup:GetBackupPlan", 14 | "backup:GetBackupSelection", 15 | "backup:ListTags", 16 | "ec2:AllocateAddress", 17 | "ec2:AssociateRouteTable", 18 | "ec2:AttachInternetGateway", 19 | "ec2:AuthorizeSecurityGroupEgress", 20 | "ec2:AuthorizeSecurityGroupIngress", 21 | "ec2:CreateFlowLogs", 22 | "ec2:CreateInternetGateway", 23 | "ec2:CreateLaunchTemplate", 24 | "ec2:CreateNatGateway", 25 | "ec2:CreateNetworkAclEntry", 26 | "ec2:CreateRoute", 27 | "ec2:CreateRouteTable", 28 | "ec2:CreateSecurityGroup", 29 | "ec2:CreateSubnet", 30 | "ec2:CreateTags", 31 | "ec2:CreateVpc", 32 | "ec2:DeleteNetworkAclEntry", 33 | "ec2:DescribeAddresses", 34 | "ec2:DescribeAddressesAttribute", 35 | "ec2:DescribeAvailabilityZones", 36 | "ec2:DescribeFlowLogs", 37 | "ec2:DescribeImages", 38 | "ec2:DescribeInstanceAttribute", 39 | "ec2:DescribeInstanceCreditSpecifications", 40 | "ec2:DescribeInstanceTypes", 41 | "ec2:DescribeInstances", 42 | "ec2:DescribeInternetGateways", 43 | "ec2:DescribeLaunchTemplateVersions", 44 | "ec2:DescribeLaunchTemplates", 45 | "ec2:DescribeNatGateways", 46 | "ec2:DescribeNetworkAcls", 47 | "ec2:DescribeRouteTables", 48 | "ec2:DescribeSecurityGroupRules", 49 | "ec2:DescribeSecurityGroups", 50 | "ec2:DescribeSubnets", 51 | "ec2:DescribeTags", 52 | "ec2:DescribeVolumes", 53 | "ec2:DescribeVpcAttribute", 54 | "ec2:DescribeVpcs", 55 | "ec2:ModifyVpcAttribute", 56 | "ec2:RevokeSecurityGroupEgress", 57 | "ec2:RevokeSecurityGroupIngress", 58 | "ec2:RunInstances", 59 | "efs:CreateFileSystem", 60 | "efs:CreateMountTarget", 61 | "efs:DescribeFileSystemPolicy", 62 | "efs:DescribeFileSystems", 63 | "efs:DescribeLifecycleConfiguration", 64 | "efs:DescribeMountTargetSecurityGroups", 65 | "efs:DescribeMountTargets", 66 | "efs:PutFileSystemPolicy", 67 | "eks:CreateAddon", 68 | "eks:CreateCluster", 69 | "eks:CreateNodegroup", 70 | "eks:DescribeAddon", 71 | "eks:DescribeAddonVersions", 72 | "eks:DescribeCluster", 73 | "eks:DescribeNodegroup", 74 | "iam:AddRoleToInstanceProfile", 75 | "iam:AttachRolePolicy", 76 | "iam:CreateInstanceProfile", 77 | "iam:CreateOpenIDConnectProvider", 78 | "iam:CreatePolicy", 79 | "iam:CreateRole", 80 | "iam:GetInstanceProfile", 81 | "iam:GetOpenIDConnectProvider", 82 | "iam:GetPolicy", 83 | "iam:GetPolicyVersion", 84 | "iam:GetRole", 85 | "iam:GetRolePolicy", 86 | "iam:ListAttachedRolePolicies", 87 | "iam:ListRolePolicies", 88 | "iam:PutRolePolicy", 89 | "kms:CreateAlias", 90 | "kms:CreateKey", 91 | "kms:DescribeKey", 92 | "kms:EnableKeyRotation", 93 | "kms:GetKeyPolicy", 94 | "kms:GetKeyRotationStatus", 95 | "kms:ListAliases", 96 | "kms:ListResourceTags", 97 | "logs:CreateLogGroup", 98 | "logs:DescribeLogGroups", 99 | "logs:ListTagsForResource", 100 | "logs:PutRetentionPolicy", 101 | "opensearch:CreateDomain", 102 | "opensearch:DescribeDomain", 103 | "opensearch:DescribeDomainConfig", 104 | "opensearch:GetCompatibleVersions", 105 | "opensearch:ListTags", 106 | "rds:CreateDBInstance", 107 | "rds:CreateDBSubnetGroup", 108 | "rds:DescribeDBInstances", 109 | "rds:DescribeDBSubnetGroups", 110 | "rds:ListTagsForResource", 111 | "s3:CreateBucket", 112 | "s3:GetBucketAccelerateConfiguration", 113 | "s3:GetBucketAcl", 114 | "s3:GetBucketCors", 115 | "s3:GetBucketEncryption", 116 | "s3:GetBucketLifecycleConfiguration", 117 | "s3:GetBucketLogging", 118 | "s3:GetBucketPolicy", 119 | "s3:GetBucketReplication", 120 | "s3:GetBucketRequestPayment", 121 | "s3:GetBucketTagging", 122 | "s3:GetBucketVersioning", 123 | "s3:GetBucketWebsite", 124 | "s3:GetObject", 125 | "s3:GetObjectLockConfiguration", 126 | "s3:GetPublicAccessBlock", 127 | "s3:HeadBucket", 128 | "s3:HeadObject", 129 | "s3:ListObjectsV2", 130 | "s3:PutBucketEncryption", 131 | "s3:PutBucketLogging", 132 | "s3:PutBucketPolicy", 133 | "s3:PutBucketTagging", 134 | "s3:PutBucketVersioning", 135 | "s3:PutObject", 136 | "s3:PutPublicAccessBlock", 137 | "secretsmanager:DescribeSecret", 138 | "secretsmanager:GetResourcePolicy", 139 | "secretsmanager:GetSecretValue", 140 | "ssm:CreateMaintenanceWindow", 141 | "ssm:CreatePatchBaseline", 142 | "ssm:DescribeMaintenanceWindowTargets", 143 | "ssm:DescribePatchGroups", 144 | "ssm:GetMaintenanceWindow", 145 | "ssm:GetMaintenanceWindowTask", 146 | "ssm:GetPatchBaseline", 147 | "ssm:ListTagsForResource", 148 | "ssm:RegisterPatchBaselineForPatchGroup", 149 | "ssm:RegisterTargetWithMaintenanceWindow", 150 | "ssm:RegisterTaskWithMaintenanceWindow", 151 | "sso:GetRoleCredentials", 152 | "sts:GetCallerIdentity" 153 | ], 154 | "Resource": "*" 155 | } 156 | ] 157 | } 158 | -------------------------------------------------------------------------------- /modules/simphera_aws_instance/postgresql.tf: -------------------------------------------------------------------------------- 1 | resource "aws_db_subnet_group" "database" { 2 | name = "${local.instancename}-vpc" 3 | subnet_ids = var.private_subnets 4 | tags = var.tags 5 | } 6 | 7 | resource "aws_db_instance" "simphera" { 8 | apply_immediately = var.postgresqlApplyImmediately 9 | allocated_storage = var.postgresqlStorage 10 | max_allocated_storage = var.postgresqlMaxStorage 11 | auto_minor_version_upgrade = true # [RDS.13] RDS automatic minor version upgrades should be enabled 12 | allow_major_version_upgrade = true 13 | engine = "postgres" 14 | engine_version = var.postgresqlVersion 15 | instance_class = var.db_instance_type_simphera 16 | identifier = local.db_simphera_id 17 | db_name = "simphera" 18 | username = local.secret_postgres_username 19 | password = local.secrets["postgresql_password"] 20 | multi_az = true # [RDS.5] RDS DB instances should be configured with multiple Availability Zones 21 | enabled_cloudwatch_logs_exports = ["postgresql", "upgrade"] 22 | monitoring_interval = 60 23 | monitoring_role_arn = aws_iam_role.rds_enhanced_monitoring_role.arn # [RDS.9] Database logging should be enabled 24 | deletion_protection = var.enable_deletion_protection # [RDS.7] RDS clusters should have deletion protection enabled 25 | skip_final_snapshot = !var.enable_deletion_protection 26 | final_snapshot_identifier = "${local.db_simphera_id}-final-snapshot" 27 | iam_database_authentication_enabled = true # [RDS.10] IAM authentication should be configured for RDS instances 28 | copy_tags_to_snapshot = true 29 | storage_encrypted = true # [RDS.3] RDS DB instances should have encryption at rest enabled 30 | db_subnet_group_name = aws_db_subnet_group.database.name 31 | vpc_security_group_ids = [var.postgresql_security_group_id] 32 | tags = var.tags 33 | depends_on = [ 34 | aws_cloudwatch_log_group.db_simphera 35 | ] 36 | timeouts { 37 | create = "2h" 38 | delete = "2h" 39 | update = "2h" 40 | } 41 | } 42 | 43 | resource "aws_db_instance" "keycloak" { 44 | count = var.enableKeycloak ? 1 : 0 45 | apply_immediately = var.postgresqlApplyImmediately 46 | allocated_storage = var.postgresqlStorageKeycloak 47 | max_allocated_storage = var.postgresqlMaxStorageKeycloak 48 | auto_minor_version_upgrade = true # [RDS.13] RDS automatic minor version upgrades should be enabled 49 | allow_major_version_upgrade = true 50 | engine = "postgres" 51 | engine_version = var.postgresqlVersion 52 | instance_class = var.db_instance_type_keycloak 53 | identifier = local.db_keycloak_id 54 | db_name = "keycloak" 55 | username = local.secret_postgres_username 56 | password = local.secrets["postgresql_password"] 57 | multi_az = true # [RDS.5] RDS DB instances should be configured with multiple Availability Zones 58 | enabled_cloudwatch_logs_exports = ["postgresql", "upgrade"] 59 | monitoring_interval = 60 60 | monitoring_role_arn = aws_iam_role.rds_enhanced_monitoring_role.arn # [RDS.9] Database logging should be enabled 61 | deletion_protection = var.enable_deletion_protection # [RDS.7] RDS clusters should have deletion protection enabled 62 | skip_final_snapshot = !var.enable_deletion_protection 63 | final_snapshot_identifier = "${local.db_keycloak_id}-final-snapshot" 64 | iam_database_authentication_enabled = true # [RDS.10] IAM authentication should be configured for RDS instances 65 | copy_tags_to_snapshot = true 66 | storage_encrypted = true # [RDS.3] RDS DB instances should have encryption at rest enabled 67 | db_subnet_group_name = aws_db_subnet_group.database.name 68 | vpc_security_group_ids = [var.postgresql_security_group_id] 69 | tags = var.tags 70 | depends_on = [ 71 | aws_cloudwatch_log_group.db_keycloak 72 | ] 73 | timeouts { 74 | create = "2h" 75 | delete = "2h" 76 | update = "2h" 77 | } 78 | } 79 | 80 | data "http" "aws_tls_certificate" { 81 | url = "https://truststore.pki.rds.amazonaws.com/${var.region}/${var.region}-bundle.pem" 82 | } 83 | 84 | resource "kubernetes_secret" "aws_tls_certificate" { 85 | metadata { 86 | name = "customsslrootcertificate" 87 | namespace = kubernetes_namespace.k8s_namespace.metadata[0].name 88 | } 89 | data = { 90 | "databaseCertificates.pem" = data.http.aws_tls_certificate.response_body 91 | } 92 | type = "Opaque" 93 | } 94 | 95 | resource "aws_iam_role" "rds_enhanced_monitoring_role" { 96 | name = "${var.name}-rds-enhanced-monitoring" 97 | description = "AWS AM role that permits RDS to send enhanced monitoring metrics to CloudWatch Logs." 98 | assume_role_policy = jsonencode( 99 | { 100 | "Version" : "2012-10-17", 101 | "Statement" : [ 102 | { 103 | "Sid" : "", 104 | "Effect" : "Allow", 105 | "Principal" : { 106 | "Service" : [ 107 | "monitoring.rds.amazonaws.com" 108 | ] 109 | }, 110 | "Action" : [ 111 | "sts:AssumeRole" 112 | ] 113 | } 114 | ] 115 | } 116 | ) 117 | 118 | tags = var.tags 119 | } 120 | 121 | # [RDS.6] Enhanced monitoring should be configured for RDS DB instances and clusters 122 | resource "aws_iam_role_policy_attachment" "rds_enhanced_monitoring_policy" { 123 | role = aws_iam_role.rds_enhanced_monitoring_role.name 124 | policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole" 125 | } 126 | 127 | resource "aws_cloudwatch_log_group" "db_simphera" { 128 | name = "/aws/rds/instance/${local.db_simphera_id}/postgresql" # CAUTION: the name is predetermined by AWS RDS. Do not change it. Otherwise AWS will create a new log group without retention and encryption. 129 | retention_in_days = var.cloudwatch_retention 130 | kms_key_id = var.kms_key_cloudwatch 131 | tags = var.tags 132 | } 133 | 134 | resource "aws_cloudwatch_log_group" "db_keycloak" { 135 | count = var.enableKeycloak ? 1 : 0 136 | name = "/aws/rds/instance/${local.db_keycloak_id}/postgresql" # CAUTION: the name is predetermined by AWS RDS. Do not change it. Otherwise AWS will create a new log group without retention and encryption. 137 | retention_in_days = var.cloudwatch_retention 138 | kms_key_id = var.kms_key_cloudwatch 139 | tags = var.tags 140 | } 141 | --------------------------------------------------------------------------------