├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md └── terraform ├── data.tf ├── main.tf ├── manifest └── keycloak.yml ├── modules ├── cluster-autoscaler │ ├── helm.tf │ ├── iam.tf │ └── variables.tf ├── cluster │ ├── main.tf │ ├── outputs.tf │ ├── templates │ │ ├── aws-auth-cm.tftpl │ │ └── external-dns-rbac.tftpl │ └── variables.tf ├── database │ ├── main.tf │ ├── outputs.tf │ └── variables.tf ├── iam │ ├── dns-update-policy.json │ └── worker-policy.json └── metrics-server │ └── components.yml ├── template └── external-dns-rbac.yml ├── terraform.tfvars └── variables.tf /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT No Attribution 2 | 3 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 13 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 14 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 15 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 16 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | 18 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 3 | # software and associated documentation files (the "Software"), to deal in the Software 4 | # without restriction, including without limitation the rights to use, copy, modify, 5 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 6 | # permit persons to whom the Software is furnished to do so. 7 | # 8 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 9 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 11 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 12 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 13 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | 15 | SHELL := /usr/bin/env bash 16 | 17 | # COLORS 18 | RED=$(shell echo -e "\033[0;31m") 19 | GRE=$(shell echo -e "\033[0;32m") 20 | NC=$(shell echo -e "\033[0m") 21 | 22 | # TERRAFORM INSTALL 23 | version ?= "1.0.10" 24 | os ?= $(shell uname|tr A-Z a-z) 25 | ifeq ($(shell uname -m),x86_64) 26 | arch ?= "amd64" 27 | endif 28 | ifeq ($(shell uname -m),i686) 29 | arch ?= "386" 30 | endif 31 | ifeq ($(shell uname -m),aarch64) 32 | arch ?= "arm" 33 | endif 34 | 35 | # CHECK TERRAFORM VERSION 36 | TERRAFORM := $(shell command -v terraform 2> /dev/null) 37 | USER_HOME_DIRECTORY := $(HOME) 38 | TERRAFORM_VERSION := $(shell terraform --version 2> /dev/null) 39 | REGION := $(shell aws configure get region) 40 | 41 | all: local plan apply git-private configure-auth upload configure-external-dns configure-keycloak destroy clean 42 | @echo "$(GRE) INFO: Applying all options" 43 | 44 | .PHONY: apply clean destroy configure-auth plan upload 45 | local: 46 | @terraform --version 47 | ifdef TERRAFORM 48 | @echo "$(GRE) INFO: The local Terraform version is $(TERRAFORM_VERSION)" 49 | else 50 | @echo "$(RED) ERROR: Terraform is not installed" 51 | endif 52 | 53 | clean: 54 | @echo "$(RED) INFO: Removing local Terraform generated files" 55 | @rm -rf .terraform* terraform.tfs* 56 | 57 | plan: 58 | @echo "$(GRE) INFO: Initialize the working directory and planning" 59 | cd terraform/ && \ 60 | terraform init -reconfigure && \ 61 | terraform fmt && \ 62 | terraform validate && \ 63 | terraform plan 64 | 65 | apply: 66 | @echo "$(GRE) INFO: Applying planned resources" 67 | cd terraform/ \ 68 | @terraform init -reconfigure && \ 69 | terraform validate && \ 70 | terraform apply --auto-approve -var-file=terraform.tfvars 71 | 72 | update-kube-config: 73 | @echo "$(GRE) INFO: Configuring Kube config." 74 | set -ex 75 | aws eks update-kubeconfig --name keycloak-demo --region $(REGION) 76 | 77 | deploy-keycloak: 78 | @echo "$(GRE) INFO: Deploying Keycloak to EKS." 79 | set -ex 80 | cd terraform/ && \ 81 | kubectl apply -f manifest/keycloak.yml 82 | 83 | destroy: 84 | @echo "$(RED) INFO: Removing all Terraform created resources" 85 | set -ex 86 | cd terraform/ && \ 87 | kubectl delete -f manifest/keycloak.yml && \ 88 | terraform init -reconfigure && \ 89 | terraform validate && \ 90 | terraform destroy --auto-approve -var-file=terraform.tfvars 91 | 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # configure-keycloak-on-eks-using-terraform 2 | This repository contains Terraform code to provision AWS infrastructure deploy Keycloak to Elastic Kubernetes Service (EKS). 3 | 4 | ### To setup and preview resources with Terraform 5 | Run Command 6 | ```shell 7 | make plan 8 | ``` 9 | 10 | ### To deploy the AWS Services with Terraform 11 | Run command 12 | ```shell 13 | make apply 14 | 15 | ``` 16 | ### To update kube-config 17 | Run command 18 | ```shell 19 | make update-kube-config 20 | ``` 21 | 22 | ### To deploy Keycloak to EKS 23 | Run command 24 | ```shell 25 | make deploy-keycloak 26 | ``` 27 | 28 | ### To delete all resource with Terraform 29 | Run command 30 | ```shell 31 | make destroy 32 | ``` -------------------------------------------------------------------------------- /terraform/data.tf: -------------------------------------------------------------------------------- 1 | data "aws_region" "current" {} 2 | -------------------------------------------------------------------------------- /terraform/main.tf: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 3 | # software and associated documentation files (the "Software"), to deal in the Software 4 | # without restriction, including without limitation the rights to use, copy, modify, 5 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 6 | # permit persons to whom the Software is furnished to do so. 7 | # 8 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 9 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 11 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 12 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 13 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | 15 | locals { 16 | region = data.aws_region.current.name 17 | } 18 | 19 | output "db_hostname" { 20 | value = module.dev_database.db_hostname 21 | } 22 | 23 | output "cert_arn" { 24 | value = var.cert_arn 25 | } 26 | 27 | module "dev_cluster" { 28 | source = "./modules/cluster" 29 | route53_zone_id = var.route53_zone_id 30 | route53_zone_name = var.route53_zone_name 31 | cert_arn = var.cert_arn 32 | environment = var.environment 33 | cluster_version = var.cluster_version 34 | region = local.region 35 | } 36 | 37 | module "dev_autoscaler" { 38 | source = "./modules/cluster-autoscaler" 39 | cluster_identity_oidc_issuer = module.dev_cluster.cluster_identity_oidc_issuer 40 | cluster_identity_oidc_issuer_arn = module.dev_cluster.cluster_identity_oidc_issuer_arn 41 | cluster_endpoint = module.dev_cluster.cluster_endpoint 42 | cluster_certificate_authority_data = module.dev_cluster.cluster_certificate_authority_data 43 | } 44 | 45 | module "dev_database" { 46 | source = "./modules/database" 47 | db_username = var.db_username 48 | db_password = var.db_password 49 | database_name = var.database_name 50 | vpc_id = module.dev_cluster.vpc_id 51 | database_subnets = module.dev_cluster.database_subnets 52 | database_subnets_cidr_blocks = module.dev_cluster.database_subnets_cidr_blocks 53 | cluster_sg_id = module.dev_cluster.cluster_sg_id 54 | } -------------------------------------------------------------------------------- /terraform/manifest/keycloak.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 3 | # software and associated documentation files (the "Software"), to deal in the Software 4 | # without restriction, including without limitation the rights to use, copy, modify, 5 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 6 | # permit persons to whom the Software is furnished to do so. 7 | # 8 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 9 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 11 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 12 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 13 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | 15 | apiVersion: v1 16 | kind: Service 17 | metadata: 18 | name: keycloak 19 | labels: 20 | app: keycloak 21 | spec: 22 | ports: 23 | - name: http 24 | port: 8080 25 | targetPort: 8080 26 | selector: 27 | app: keycloak 28 | type: NodePort 29 | --- 30 | apiVersion: apps/v1 31 | kind: Deployment 32 | metadata: 33 | name: keycloak 34 | labels: 35 | app: keycloak 36 | spec: 37 | replicas: 1 38 | selector: 39 | matchLabels: 40 | app: keycloak 41 | template: 42 | metadata: 43 | labels: 44 | app: keycloak 45 | spec: 46 | containers: 47 | - name: keycloak 48 | image: quay.io/keycloak/keycloak:20.0.3 49 | args: ["start-dev"] 50 | env: 51 | - name: KEYCLOAK_ADMIN 52 | value: "admin" 53 | - name: KEYCLOAK_ADMIN_PASSWORD 54 | value: "" 55 | - name: DB_VENDOR 56 | value: mysql 57 | - name: DB_ADDR 58 | value: "" 59 | - name: DB_USERNAME 60 | value: "auroraserverless" 61 | - name: DB_PASSWORD 62 | value: "" 63 | - name: DB_PORT 64 | value: "3306" 65 | - name: DB_DATABASE 66 | value: "keycloakdemo" 67 | - name: KC_PROXY 68 | value: "edge" 69 | ports: 70 | - name: http 71 | containerPort: 8080 72 | readinessProbe: 73 | httpGet: 74 | path: /realms/master 75 | port: 8080 76 | initialDelaySeconds: 5 77 | periodSeconds: 3 78 | --- 79 | apiVersion: networking.k8s.io/v1 80 | kind: Ingress 81 | metadata: 82 | namespace: default 83 | name: ingress-keycloak 84 | annotations: 85 | alb.ingress.kubernetes.io/scheme: internet-facing 86 | alb.ingress.kubernetes.io/target-type: instance 87 | alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTP": 8080}, {"HTTPS": 443}]' 88 | alb.ingress.kubernetes.io/tags: environment=demo,team=builder 89 | alb.ingress.kubernetes.io/certificate-arn: 90 | labels: 91 | app: app-keycloak 92 | spec: 93 | ingressClassName: alb 94 | tls: 95 | - hosts: 96 | - keycloak..com 97 | rules: 98 | - host: keycloak..com 99 | http: 100 | paths: 101 | - path: / 102 | pathType: Prefix 103 | backend: 104 | service: 105 | name: keycloak 106 | port: 107 | number: 8080 -------------------------------------------------------------------------------- /terraform/modules/cluster-autoscaler/helm.tf: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 3 | # software and associated documentation files (the "Software"), to deal in the Software 4 | # without restriction, including without limitation the rights to use, copy, modify, 5 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 6 | # permit persons to whom the Software is furnished to do so. 7 | # 8 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 9 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 11 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 12 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 13 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | 15 | provider "helm" { 16 | kubernetes { 17 | host = var.cluster_endpoint 18 | cluster_ca_certificate = base64decode(var.cluster_certificate_authority_data) 19 | 20 | exec { 21 | api_version = "client.authentication.k8s.io/v1beta1" 22 | command = "aws" 23 | args = ["eks", "get-token", "--cluster-name", var.cluster_name] 24 | } 25 | } 26 | } 27 | 28 | 29 | resource "kubernetes_namespace" "cluster_autoscaler" { 30 | count = (var.enabled && var.create_namespace && var.namespace != "kube-system") ? 1 : 0 31 | 32 | metadata { 33 | name = var.namespace 34 | } 35 | } 36 | 37 | resource "helm_release" "cluster_autoscaler" { 38 | count = var.enabled ? 1 : 0 39 | 40 | name = "cluster-autoscaler" 41 | chart = "cluster-autoscaler" 42 | repository = "https://kubernetes.github.io/autoscaler" 43 | version = "9.25.0" 44 | namespace = var.namespace 45 | 46 | set { 47 | name = "fullnameOverride" 48 | value = "aws-cluster-autoscaler" 49 | } 50 | 51 | set { 52 | name = "autoDiscovery.clusterName" 53 | value = var.cluster_name 54 | } 55 | 56 | set { 57 | name = "awsRegion" 58 | value = var.aws_region 59 | } 60 | 61 | set { 62 | name = "rbac.serviceAccount.name" 63 | value = "cluster-autoscaler" 64 | } 65 | 66 | set { 67 | name = "rbac.serviceAccount.annotations.eks\\.amazonaws\\.com/role-arn" 68 | value = aws_iam_role.kubernetes_cluster_autoscaler[0].arn 69 | } 70 | 71 | values = [ 72 | yamlencode(var.settings) 73 | ] 74 | 75 | } -------------------------------------------------------------------------------- /terraform/modules/cluster-autoscaler/iam.tf: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 3 | # software and associated documentation files (the "Software"), to deal in the Software 4 | # without restriction, including without limitation the rights to use, copy, modify, 5 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 6 | # permit persons to whom the Software is furnished to do so. 7 | # 8 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 9 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 11 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 12 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 13 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | 15 | data "aws_iam_policy_document" "kubernetes_cluster_autoscaler" { 16 | count = var.enabled ? 1 : 0 17 | 18 | statement { 19 | actions = [ 20 | "autoscaling:DescribeAutoScalingGroups", 21 | "autoscaling:DescribeAutoScalingInstances", 22 | "autoscaling:DescribeLaunchConfigurations", 23 | "autoscaling:DescribeTags", 24 | "autoscaling:SetDesiredCapacity", 25 | "autoscaling:TerminateInstanceInAutoScalingGroup", 26 | "ec2:DescribeLaunchTemplateVersions", 27 | "ec2:DescribeInstanceTypes" 28 | ] 29 | resources = [ 30 | "*", 31 | ] 32 | effect = "Allow" 33 | } 34 | 35 | } 36 | 37 | resource "aws_iam_policy" "kubernetes_cluster_autoscaler" { 38 | count = var.enabled ? 1 : 0 39 | name = "${var.cluster_name}-cluster-autoscaler" 40 | path = "/" 41 | description = "Policy for cluster autoscaler service" 42 | 43 | policy = data.aws_iam_policy_document.kubernetes_cluster_autoscaler[0].json 44 | } 45 | 46 | # Role 47 | data "aws_iam_policy_document" "kubernetes_cluster_autoscaler_assume" { 48 | count = var.enabled ? 1 : 0 49 | 50 | statement { 51 | actions = ["sts:AssumeRoleWithWebIdentity"] 52 | 53 | principals { 54 | type = "Federated" 55 | identifiers = [var.cluster_identity_oidc_issuer_arn] 56 | } 57 | 58 | condition { 59 | test = "StringEquals" 60 | variable = "${replace(var.cluster_identity_oidc_issuer, "https://", "")}:sub" 61 | 62 | values = [ 63 | "system:serviceaccount:${var.namespace}:${var.service_account_name}", 64 | ] 65 | } 66 | 67 | effect = "Allow" 68 | } 69 | } 70 | 71 | resource "aws_iam_role" "kubernetes_cluster_autoscaler" { 72 | count = var.enabled ? 1 : 0 73 | name = "${var.cluster_name}-cluster-autoscaler" 74 | assume_role_policy = data.aws_iam_policy_document.kubernetes_cluster_autoscaler_assume[0].json 75 | } 76 | 77 | resource "aws_iam_role_policy_attachment" "kubernetes_cluster_autoscaler" { 78 | count = var.enabled ? 1 : 0 79 | role = aws_iam_role.kubernetes_cluster_autoscaler[0].name 80 | policy_arn = aws_iam_policy.kubernetes_cluster_autoscaler[0].arn 81 | } -------------------------------------------------------------------------------- /terraform/modules/cluster-autoscaler/variables.tf: -------------------------------------------------------------------------------- 1 | variable "enabled" { 2 | type = bool 3 | default = true 4 | description = "Variable indicating whether deployment is enabled." 5 | } 6 | 7 | variable "cluster_name" { 8 | type = string 9 | description = "The name of the cluster." 10 | default = "keycloak-demo" 11 | } 12 | 13 | variable "aws_region" { 14 | type = string 15 | description = "AWS region where secrets are stored." 16 | default = "us-east-1" 17 | } 18 | 19 | variable "cluster_identity_oidc_issuer" { 20 | type = string 21 | description = "The OIDC Identity issuer for the cluster." 22 | } 23 | 24 | variable "cluster_identity_oidc_issuer_arn" { 25 | type = string 26 | description = "The OIDC Identity issuer ARN for the cluster that can be used to associate IAM roles with a service account." 27 | } 28 | 29 | variable "settings" { 30 | default = {} 31 | description = "Additional settings which will be passed to the Helm chart values." 32 | } 33 | 34 | variable "service_account_name" { 35 | type = string 36 | default = "cluster-autoscaler" 37 | description = "Cluster Autoscaler service account name." 38 | } 39 | 40 | variable "namespace" { 41 | type = string 42 | default = "kube-system" 43 | description = "Kubernetes namespace to deploy Cluster Autoscaler Helm chart." 44 | } 45 | 46 | variable "create_namespace" { 47 | type = bool 48 | default = true 49 | description = "Whether to create Kubernetes namespace with name defined by `namespace`." 50 | } 51 | 52 | variable "cluster_endpoint" { 53 | type = string 54 | description = "Endpoint for your Kubernetes API server." 55 | } 56 | 57 | variable "cluster_certificate_authority_data" { 58 | type = string 59 | description = "Base64 encoded certificate data required to communicate with the cluster." 60 | } -------------------------------------------------------------------------------- /terraform/modules/cluster/main.tf: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 3 | # software and associated documentation files (the "Software"), to deal in the Software 4 | # without restriction, including without limitation the rights to use, copy, modify, 5 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 6 | # permit persons to whom the Software is furnished to do so. 7 | # 8 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 9 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 11 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 12 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 13 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | 15 | provider "aws" { 16 | region = "us-east-1" 17 | default_tags { 18 | tags = { 19 | Environment = "dev" 20 | Name = "terraform keycloak demo provider tag" 21 | } 22 | } 23 | } 24 | 25 | provider "kubernetes" { 26 | host = data.aws_eks_cluster.cluster.endpoint 27 | cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data) 28 | token = data.aws_eks_cluster_auth.cluster.token 29 | 30 | } 31 | 32 | provider "helm" { 33 | kubernetes { 34 | host = data.aws_eks_cluster.cluster.endpoint 35 | cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data) 36 | token = data.aws_eks_cluster_auth.cluster.token 37 | } 38 | } 39 | 40 | 41 | data "aws_availability_zones" "available" {} 42 | 43 | data "aws_caller_identity" "current" {} 44 | 45 | data "aws_region" "current" {} 46 | 47 | data "aws_eks_cluster" "cluster" { 48 | name = module.eks.cluster_id 49 | } 50 | 51 | data "aws_eks_cluster_auth" "cluster" { 52 | name = module.eks.cluster_id 53 | } 54 | 55 | locals { 56 | account_id = data.aws_caller_identity.current.account_id 57 | } 58 | 59 | module "eks-kubeconfig" { 60 | source = "hyperbadger/eks-kubeconfig/aws" 61 | version = "1.0.0" 62 | 63 | depends_on = [module.eks] 64 | cluster_id = module.eks.cluster_id 65 | } 66 | 67 | resource "local_file" "kubeconfig" { 68 | content = module.eks-kubeconfig.kubeconfig 69 | filename = "kubeconfig_${var.cluster_name}" 70 | } 71 | 72 | module "vpc" { 73 | source = "terraform-aws-modules/vpc/aws" 74 | version = "5.1.0" 75 | 76 | name = var.cluster_name 77 | cidr = "172.16.0.0/16" 78 | azs = data.aws_availability_zones.available.names 79 | private_subnets = ["172.16.1.0/24", "172.16.2.0/24", "172.16.3.0/24"] 80 | public_subnets = ["172.16.4.0/24", "172.16.5.0/24", "172.16.6.0/24"] 81 | database_subnets = ["172.16.10.0/24", "172.16.11.0/24", "172.16.12.0/24"] 82 | enable_nat_gateway = true 83 | single_nat_gateway = true 84 | enable_dns_hostnames = true 85 | map_public_ip_on_launch = false 86 | create_flow_log_cloudwatch_log_group = true 87 | flow_log_cloudwatch_iam_role_arn = aws_iam_role.cw_role.arn 88 | flow_log_cloudwatch_log_group_retention_in_days = 30 89 | flow_log_destination_type = "cloud-watch-logs" 90 | flow_log_file_format = "plain-text" 91 | flow_log_traffic_type = "ALL" 92 | 93 | public_subnet_tags = { 94 | "kubernetes.io/cluster/${var.cluster_name}" = "shared" 95 | "kubernetes.io/role/elb" = "1" 96 | "k8s.io/cluster-autoscaler/${var.cluster_name}" = "owned" 97 | "kubernetes.io/role/internal-elb" = "true" 98 | } 99 | 100 | private_subnet_tags = { 101 | "kubernetes.io/cluster/${var.cluster_name}" = "shared" 102 | "kubernetes.io/role/internal-elb" = "1" 103 | "k8s.io/cluster-autoscaler/${var.cluster_name}" = "owned" 104 | "kubernetes.io/role/internal-elb" = "true" 105 | } 106 | 107 | database_subnet_tags = { 108 | "kubernetes.io/cluster/${var.cluster_name}" = "shared" 109 | "kubernetes.io/role/internal-elb" = "1" 110 | } 111 | } 112 | 113 | data "aws_iam_policy_document" "assume_role" { 114 | statement { 115 | effect = "Allow" 116 | 117 | principals { 118 | type = "Service" 119 | identifiers = ["vpc-flow-logs.amazonaws.com"] 120 | } 121 | 122 | actions = ["sts:AssumeRole"] 123 | } 124 | } 125 | 126 | resource "aws_iam_role" "cw_role" { 127 | name = "cw_role" 128 | assume_role_policy = data.aws_iam_policy_document.assume_role.json 129 | } 130 | 131 | data "aws_iam_policy_document" "cw_logs" { 132 | statement { 133 | effect = "Allow" 134 | 135 | actions = [ 136 | "logs:CreateLogGroup", 137 | "logs:CreateLogStream", 138 | "logs:PutLogEvents", 139 | "logs:DescribeLogGroups", 140 | "logs:DescribeLogStreams", 141 | ] 142 | 143 | resources = ["*"] 144 | } 145 | } 146 | 147 | resource "aws_iam_role_policy" "example" { 148 | name = "example" 149 | role = aws_iam_role.cw_role.id 150 | policy = data.aws_iam_policy_document.cw_logs.json 151 | } 152 | 153 | resource "aws_default_security_group" "this" { 154 | vpc_id = module.vpc.vpc_id 155 | } 156 | 157 | data "aws_iam_policy_document" "key" { 158 | 159 | statement { 160 | effect = "Allow" 161 | actions = ["kms:*"] 162 | resources = ["*"] 163 | principals { 164 | type = "AWS" 165 | identifiers = ["arn:aws:iam::${local.account_id}:root"] 166 | } 167 | } 168 | 169 | statement { 170 | effect = "Allow" 171 | actions = [ 172 | "kms:Encrypt", 173 | "kms:Decrypt", 174 | "kms:ReEncrypt*", 175 | "kms:GenerateDataKey*", 176 | "kms:DescribeKey", 177 | ] 178 | resources = ["*"] 179 | 180 | principals { 181 | type = "Service" 182 | identifiers = ["delivery.logs.amazonaws.com"] 183 | } 184 | } 185 | 186 | statement { 187 | effect = "Allow" 188 | actions = [ 189 | "kms:Encrypt", 190 | "kms:Decrypt", 191 | "kms:ReEncrypt*", 192 | "kms:GenerateDataKey*", 193 | "kms:DescribeKey", 194 | ] 195 | resources = ["*"] 196 | 197 | principals { 198 | type = "Service" 199 | identifiers = ["logs.${var.region}.amazonaws.com"] 200 | } 201 | } 202 | } 203 | 204 | resource "aws_kms_key" "this" { 205 | deletion_window_in_days = 7 206 | description = "VPC Flow Log Encryption Key" 207 | enable_key_rotation = true 208 | policy = data.aws_iam_policy_document.key.json 209 | tags = merge( 210 | { 211 | "Name" = "vpcflowlog-key" 212 | } 213 | ) 214 | } 215 | 216 | resource "aws_kms_alias" "this" { 217 | name = "alias/${var.kms_alias}" 218 | target_key_id = aws_kms_key.this.key_id 219 | } 220 | 221 | resource "aws_cloudwatch_log_group" "flow_logs" { 222 | depends_on = [ 223 | aws_kms_key.this 224 | ] 225 | name = "vpc-flow-logs" 226 | kms_key_id = aws_kms_key.this.arn 227 | } 228 | 229 | module "eks" { 230 | source = "terraform-aws-modules/eks/aws" 231 | version = "18.31.2" 232 | 233 | cluster_name = var.cluster_name 234 | cluster_version = var.cluster_version 235 | subnet_ids = module.vpc.private_subnets 236 | vpc_id = module.vpc.vpc_id 237 | cluster_endpoint_public_access = true 238 | create_cloudwatch_log_group = true 239 | cluster_enabled_log_types = ["api", "audit", "authenticator","controllerManager","scheduler"] 240 | cloudwatch_log_group_kms_key_id = aws_kms_key.this.arn 241 | create_kms_key = true 242 | cluster_encryption_config = [{ 243 | resources = ["secrets"] 244 | provider_key_arn = aws_kms_key.this.arn 245 | }] 246 | 247 | kms_key_description = "KMS Secrets encryption for EKS cluster." 248 | kms_key_enable_default_policy = true 249 | 250 | eks_managed_node_groups = { 251 | first = { 252 | desired_capacity = 1 253 | max_capacity = 10 254 | min_capacity = 1 255 | 256 | instance_type = var.instance_type 257 | } 258 | } 259 | node_security_group_additional_rules = { 260 | ingress_allow_access_from_control_plane_webhook = { 261 | type = "ingress" 262 | protocol = "tcp" 263 | from_port = 9443 264 | to_port = 9443 265 | source_cluster_security_group = true 266 | description = "Allow access from control plane to webhook port of AWS load balancer controller" 267 | }, 268 | ingress_allow_access_from_control_plane_metrics = { 269 | type = "ingress" 270 | protocol = "tcp" 271 | from_port = 4443 272 | to_port = 4443 273 | source_cluster_security_group = true 274 | description = "Allow access from control plane to metrics server AWS load balancer controller" 275 | } 276 | } 277 | } 278 | 279 | resource "aws_iam_policy" "worker_policy" { 280 | name = "worker-policy-${var.cluster_name}" 281 | description = "Worker policy for the ALB Ingress" 282 | 283 | policy = file("modules/iam/worker-policy.json") 284 | } 285 | 286 | resource "aws_iam_policy" "dnsupdate_policy" { 287 | name = "dnsupdate-policy-${var.cluster_name}" 288 | description = "DNS update policy for Route53 Resource Record Sets and Hosted Zones" 289 | 290 | policy = file("modules/iam/dns-update-policy.json") 291 | } 292 | 293 | 294 | resource "aws_iam_role_policy_attachment" "workerpolicy" { 295 | for_each = module.eks.eks_managed_node_groups 296 | 297 | policy_arn = aws_iam_policy.worker_policy.arn 298 | role = each.value.iam_role_name 299 | } 300 | 301 | resource "aws_iam_role_policy_attachment" "dnsupdatepolicy" { 302 | for_each = module.eks.eks_managed_node_groups 303 | 304 | policy_arn = aws_iam_policy.dnsupdate_policy.arn 305 | role = each.value.iam_role_name 306 | } 307 | 308 | resource "helm_release" "ingress" { 309 | name = "demo" 310 | chart = "aws-load-balancer-controller" 311 | repository = "https://aws.github.io/eks-charts" 312 | version = "1.4.7" 313 | 314 | set { 315 | name = "autoDiscoverAwsRegion" 316 | value = "true" 317 | } 318 | set { 319 | name = "autoDiscoverAwsVpcID" 320 | value = "true" 321 | } 322 | set { 323 | name = "clusterName" 324 | value = var.cluster_name 325 | } 326 | set { 327 | name = "SubnetsClusterTagCheck" 328 | value = "false" 329 | } 330 | } 331 | 332 | resource "aws_security_group" "lb_security_group" { 333 | name = "${var.cluster_name}-lb-sg" 334 | vpc_id = module.vpc.vpc_id 335 | description = "Security group for Ingress ALB" 336 | 337 | ingress { 338 | from_port = 443 339 | to_port = 443 340 | protocol = "tcp" 341 | cidr_blocks = ["52.94.133.131/32"] 342 | } 343 | 344 | egress { 345 | from_port = 0 346 | to_port = 0 347 | protocol = "-1" 348 | security_groups = [module.eks.node_security_group_id] 349 | } 350 | 351 | tags = merge({ 352 | Name = "${var.cluster_name}-lb-sg" 353 | "kubernetes.io/cluster/${var.cluster_name}" = "owned" 354 | "kubernetes:application" = "kube-ingress-aws-controller" 355 | }) 356 | } 357 | 358 | # ============= auth configmap / external DNS config ============= # 359 | data "kubectl_path_documents" "kube_configs" { 360 | pattern = "${path.module}/templates/*.tftpl" 361 | vars = { 362 | account_number = local.account_id 363 | nodegroup_role_name = module.eks.eks_managed_node_groups.first.iam_role_name 364 | route53_zone_id = var.route53_zone_id 365 | region = var.region 366 | domain_name = var.route53_zone_name 367 | } 368 | } 369 | # Resource to get around Terraform count bug. We use this so that Terraform provider is aware of the number of vars at runtime. This resource should mimic the above resource. 370 | # Link to bug: https://github.com/gavinbunney/terraform-provider-kubectl/issues/58 371 | data "kubectl_path_documents" "kube_config_count" { 372 | pattern = "${path.module}/templates/*.tftpl" 373 | vars = { 374 | account_number = "" 375 | nodegroup_role_name = "" 376 | route53_zone_id = "" 377 | region = "" 378 | domain_name = "" 379 | } 380 | } 381 | 382 | resource "kubectl_manifest" "configs_apply" { 383 | count = length(data.kubectl_path_documents.kube_config_count.documents) 384 | yaml_body = element(data.kubectl_path_documents.kube_configs.documents, count.index) 385 | } 386 | 387 | # ============= metric server ============= # 388 | terraform { 389 | required_version = "~>1.3.9" 390 | 391 | required_providers { 392 | kubectl = { 393 | source = "gavinbunney/kubectl" 394 | version = "1.14.0" 395 | } 396 | } 397 | } 398 | 399 | provider "kubectl" { 400 | host = data.aws_eks_cluster.cluster.endpoint 401 | cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data) 402 | token = data.aws_eks_cluster_auth.cluster.token 403 | load_config_file = false 404 | } 405 | 406 | 407 | data "kubectl_file_documents" "docs" { 408 | content = file("modules/metrics-server/components.yml") 409 | } 410 | 411 | resource "kubectl_manifest" "metrics-apply" { 412 | for_each = data.kubectl_file_documents.docs.manifests 413 | yaml_body = each.value 414 | } -------------------------------------------------------------------------------- /terraform/modules/cluster/outputs.tf: -------------------------------------------------------------------------------- 1 | output "vpc_id" { 2 | description = "The ID of the VPC" 3 | value = module.vpc.vpc_id 4 | } 5 | 6 | output "database_subnets" { 7 | description = "List of IDs of private subnets" 8 | value = module.vpc.database_subnets 9 | } 10 | 11 | output "database_subnets_cidr_blocks" { 12 | description = "List of cidr_blocks of database subnets" 13 | value = module.vpc.database_subnets_cidr_blocks 14 | } 15 | 16 | output "cluster_identity_oidc_issuer" { 17 | description = "Issuer URL for the OpenID Connect identity provider" 18 | value = module.eks.cluster_oidc_issuer_url 19 | } 20 | 21 | output "cluster_identity_oidc_issuer_arn" { 22 | description = "The OIDC Identity issuer ARN for the cluster that can be used to associate IAM roles with a service account" 23 | value = module.eks.oidc_provider_arn 24 | } 25 | 26 | output "cluster_endpoint" { 27 | description = "Endpoint for your Kubernetes API server" 28 | value = module.eks.cluster_endpoint 29 | } 30 | 31 | output "cluster_sg_id" { 32 | description = "EKS Cluster Security Group ID" 33 | value = module.eks.node_security_group_id 34 | } 35 | 36 | output "cluster_certificate_authority_data" { 37 | description = "Base64 encoded certificate data required to communicate with the cluster" 38 | value = module.eks.cluster_certificate_authority_data 39 | } -------------------------------------------------------------------------------- /terraform/modules/cluster/templates/aws-auth-cm.tftpl: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: aws-auth 5 | namespace: kube-system 6 | data: 7 | mapRoles: | 8 | - rolearn: arn:aws:iam::${account_number}:role/${nodegroup_role_name} 9 | username: system:node:{{EC2PrivateDNSName}} 10 | groups: 11 | - system:bootstrappers 12 | - system:nodes 13 | - rolearn: arn:aws:iam::${account_number}:role/admin 14 | username: admin 15 | groups: 16 | - system:masters -------------------------------------------------------------------------------- /terraform/modules/cluster/templates/external-dns-rbac.tftpl: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: external-dns 5 | labels: 6 | app.kubernetes.io/name: external-dns 7 | --- 8 | apiVersion: rbac.authorization.k8s.io/v1 9 | kind: ClusterRole 10 | metadata: 11 | name: external-dns 12 | labels: 13 | app.kubernetes.io/name: external-dns 14 | rules: 15 | - apiGroups: [""] 16 | resources: ["services","endpoints","pods","nodes"] 17 | verbs: ["get","watch","list"] 18 | - apiGroups: ["extensions","networking.k8s.io"] 19 | resources: ["ingresses"] 20 | verbs: ["get","watch","list"] 21 | --- 22 | apiVersion: rbac.authorization.k8s.io/v1 23 | kind: ClusterRoleBinding 24 | metadata: 25 | name: external-dns-viewer 26 | labels: 27 | app.kubernetes.io/name: external-dns 28 | roleRef: 29 | apiGroup: rbac.authorization.k8s.io 30 | kind: ClusterRole 31 | name: external-dns 32 | subjects: 33 | - kind: ServiceAccount 34 | name: external-dns 35 | namespace: default # change to desired namespace: externaldns, kube-addons 36 | --- 37 | apiVersion: apps/v1 38 | kind: Deployment 39 | metadata: 40 | name: external-dns 41 | labels: 42 | app.kubernetes.io/name: external-dns 43 | spec: 44 | strategy: 45 | type: Recreate 46 | selector: 47 | matchLabels: 48 | app.kubernetes.io/name: external-dns 49 | template: 50 | metadata: 51 | labels: 52 | app.kubernetes.io/name: external-dns 53 | spec: 54 | serviceAccountName: external-dns 55 | containers: 56 | - name: external-dns 57 | image: registry.k8s.io/external-dns/external-dns:v0.13.2 58 | args: 59 | - --source=service 60 | - --source=ingress 61 | - --domain-filter=${domain_name} # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones 62 | - --provider=aws 63 | - --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization 64 | - --aws-zone-type=public # only look at public hosted zones (valid values are public, private or no value for both) 65 | - --registry=txt 66 | - --txt-owner-id=${route53_zone_id} 67 | env: 68 | - name: AWS_DEFAULT_REGION 69 | value: ${region} # change to region where EKS is installed -------------------------------------------------------------------------------- /terraform/modules/cluster/variables.tf: -------------------------------------------------------------------------------- 1 | variable "route53_zone_id" { 2 | type = string 3 | description = "Route53 Zone ID" 4 | } 5 | 6 | variable "route53_zone_name" { 7 | type = string 8 | description = "Route53 Zone Name" 9 | } 10 | 11 | variable "region" { 12 | type = string 13 | description = "Region Name" 14 | } 15 | 16 | variable "cert_arn" { 17 | type = string 18 | description = "Route53 Hosted Zone ID AWS Certificate Manager ARN" 19 | } 20 | 21 | variable "environment" { 22 | type = string 23 | description = "Environment workspace" 24 | } 25 | 26 | variable "cluster_name" { 27 | type = string 28 | description = "EKS cluster name" 29 | default = "keycloak-demo" 30 | } 31 | 32 | variable "cluster_version" { 33 | type = string 34 | description = "EKS cluster version" 35 | } 36 | 37 | variable "instance_type" { 38 | type = string 39 | description = "EC2 Instance Type" 40 | default = "t3.large" 41 | } 42 | 43 | variable "kms_alias" { 44 | default = "vpcflowlog_key" 45 | description = "KMS Key Alias for VPC flow log key" 46 | type = string 47 | } -------------------------------------------------------------------------------- /terraform/modules/database/main.tf: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 3 | # software and associated documentation files (the "Software"), to deal in the Software 4 | # without restriction, including without limitation the rights to use, copy, modify, 5 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 6 | # permit persons to whom the Software is furnished to do so. 7 | # 8 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 9 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 11 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 12 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 13 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | 15 | module "aurora_mysql" { 16 | source = "terraform-aws-modules/rds-aurora/aws" 17 | version = "7.6.2" 18 | 19 | name = var.database_name 20 | engine = "aurora-mysql" 21 | engine_mode = "serverless" 22 | storage_encrypted = true 23 | 24 | vpc_id = var.vpc_id 25 | subnets = var.database_subnets 26 | create_security_group = true 27 | allowed_cidr_blocks = var.database_subnets_cidr_blocks 28 | 29 | monitoring_interval = 60 30 | 31 | apply_immediately = true 32 | skip_final_snapshot = true 33 | 34 | allowed_security_groups = ["${var.cluster_sg_id}"] 35 | 36 | db_parameter_group_name = aws_db_parameter_group.mysql.id 37 | db_cluster_parameter_group_name = aws_rds_cluster_parameter_group.mysql.id 38 | 39 | scaling_configuration = { 40 | auto_pause = true 41 | min_capacity = 1 42 | max_capacity = 1 43 | seconds_until_auto_pause = 300 44 | timeout_action = "ForceApplyCapacityChange" 45 | } 46 | 47 | enable_http_endpoint = true 48 | database_name = var.database_name 49 | 50 | create_random_password = false 51 | master_username = var.db_username 52 | master_password = var.db_password 53 | } 54 | 55 | resource "aws_db_parameter_group" "mysql" { 56 | name = "${var.database_name}-aurora-db-mysql-parameter-group" 57 | family = "aurora-mysql5.7" 58 | description = "${var.database_name}-aurora-db-mysql-parameter-group" 59 | } 60 | 61 | resource "aws_rds_cluster_parameter_group" "mysql" { 62 | name = "${var.database_name}-aurora-mysql-cluster-parameter-group" 63 | family = "aurora-mysql5.7" 64 | description = "${var.database_name}-aurora-mysql-cluster-parameter-group" 65 | } -------------------------------------------------------------------------------- /terraform/modules/database/outputs.tf: -------------------------------------------------------------------------------- 1 | output "db_hostname" { 2 | value = module.aurora_mysql.cluster_endpoint 3 | } -------------------------------------------------------------------------------- /terraform/modules/database/variables.tf: -------------------------------------------------------------------------------- 1 | variable "db_password" { 2 | type = string 3 | description = "DB administrator password" 4 | sensitive = true 5 | } 6 | 7 | variable "db_username" { 8 | type = string 9 | description = "DB username" 10 | } 11 | 12 | variable "database_name" { 13 | type = string 14 | description = "DB Name" 15 | } 16 | 17 | variable "cluster_sg_id" { 18 | type = string 19 | description = "EKS Cluster SG ID" 20 | } 21 | 22 | variable "vpc_id" { 23 | type = string 24 | description = "Keycloak VPC ID from the EKS module" 25 | } 26 | 27 | variable "database_subnets" { 28 | type = list 29 | description = "Keycloak VPC database subnets" 30 | } 31 | 32 | variable "database_subnets_cidr_blocks" { 33 | type = list 34 | description = "Keycloak VPC cidr_blocks of database subnets" 35 | } -------------------------------------------------------------------------------- /terraform/modules/iam/dns-update-policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": [ 7 | "route53:ChangeResourceRecordSets" 8 | ], 9 | "Resource": [ 10 | "arn:aws:route53:::hostedzone/*" 11 | ] 12 | }, 13 | { 14 | "Effect": "Allow", 15 | "Action": [ 16 | "route53:ListHostedZones", 17 | "route53:ListResourceRecordSets" 18 | ], 19 | "Resource": [ 20 | "*" 21 | ] 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /terraform/modules/iam/worker-policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": [ 7 | "iam:CreateServiceLinkedRole" 8 | ], 9 | "Resource": "*", 10 | "Condition": { 11 | "StringEquals": { 12 | "iam:AWSServiceName": "elasticloadbalancing.amazonaws.com" 13 | } 14 | } 15 | }, 16 | { 17 | "Effect": "Allow", 18 | "Action": [ 19 | "ec2:DescribeAccountAttributes", 20 | "ec2:DescribeAddresses", 21 | "ec2:DescribeAvailabilityZones", 22 | "ec2:DescribeInternetGateways", 23 | "ec2:DescribeVpcs", 24 | "ec2:DescribeVpcPeeringConnections", 25 | "ec2:DescribeSubnets", 26 | "ec2:DescribeSecurityGroups", 27 | "ec2:DescribeInstances", 28 | "ec2:DescribeNetworkInterfaces", 29 | "ec2:DescribeTags", 30 | "ec2:GetCoipPoolUsage", 31 | "ec2:DescribeCoipPools", 32 | "elasticloadbalancing:DescribeLoadBalancers", 33 | "elasticloadbalancing:DescribeLoadBalancerAttributes", 34 | "elasticloadbalancing:DescribeListeners", 35 | "elasticloadbalancing:DescribeListenerCertificates", 36 | "elasticloadbalancing:DescribeSSLPolicies", 37 | "elasticloadbalancing:DescribeRules", 38 | "elasticloadbalancing:DescribeTargetGroups", 39 | "elasticloadbalancing:DescribeTargetGroupAttributes", 40 | "elasticloadbalancing:DescribeTargetHealth", 41 | "elasticloadbalancing:DescribeTags" 42 | ], 43 | "Resource": "*" 44 | }, 45 | { 46 | "Effect": "Allow", 47 | "Action": [ 48 | "cognito-idp:DescribeUserPoolClient", 49 | "acm:ListCertificates", 50 | "acm:DescribeCertificate", 51 | "iam:ListServerCertificates", 52 | "iam:GetServerCertificate", 53 | "waf-regional:GetWebACL", 54 | "waf-regional:GetWebACLForResource", 55 | "waf-regional:AssociateWebACL", 56 | "waf-regional:DisassociateWebACL", 57 | "wafv2:GetWebACL", 58 | "wafv2:GetWebACLForResource", 59 | "wafv2:AssociateWebACL", 60 | "wafv2:DisassociateWebACL", 61 | "shield:GetSubscriptionState", 62 | "shield:DescribeProtection", 63 | "shield:CreateProtection", 64 | "shield:DeleteProtection" 65 | ], 66 | "Resource": "*" 67 | }, 68 | { 69 | "Effect": "Allow", 70 | "Action": [ 71 | "ec2:AuthorizeSecurityGroupIngress", 72 | "ec2:RevokeSecurityGroupIngress" 73 | ], 74 | "Resource": "*" 75 | }, 76 | { 77 | "Effect": "Allow", 78 | "Action": [ 79 | "ec2:CreateSecurityGroup" 80 | ], 81 | "Resource": "*" 82 | }, 83 | { 84 | "Effect": "Allow", 85 | "Action": [ 86 | "ec2:CreateTags" 87 | ], 88 | "Resource": "arn:aws:ec2:*:*:security-group/*", 89 | "Condition": { 90 | "StringEquals": { 91 | "ec2:CreateAction": "CreateSecurityGroup" 92 | }, 93 | "Null": { 94 | "aws:RequestTag/elbv2.k8s.aws/cluster": "false" 95 | } 96 | } 97 | }, 98 | { 99 | "Effect": "Allow", 100 | "Action": [ 101 | "ec2:CreateTags", 102 | "ec2:DeleteTags" 103 | ], 104 | "Resource": "arn:aws:ec2:*:*:security-group/*", 105 | "Condition": { 106 | "Null": { 107 | "aws:RequestTag/elbv2.k8s.aws/cluster": "true", 108 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 109 | } 110 | } 111 | }, 112 | { 113 | "Effect": "Allow", 114 | "Action": [ 115 | "ec2:AuthorizeSecurityGroupIngress", 116 | "ec2:RevokeSecurityGroupIngress", 117 | "ec2:DeleteSecurityGroup" 118 | ], 119 | "Resource": "*", 120 | "Condition": { 121 | "Null": { 122 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 123 | } 124 | } 125 | }, 126 | { 127 | "Effect": "Allow", 128 | "Action": [ 129 | "elasticloadbalancing:CreateLoadBalancer", 130 | "elasticloadbalancing:CreateTargetGroup" 131 | ], 132 | "Resource": "*", 133 | "Condition": { 134 | "Null": { 135 | "aws:RequestTag/elbv2.k8s.aws/cluster": "false" 136 | } 137 | } 138 | }, 139 | { 140 | "Effect": "Allow", 141 | "Action": [ 142 | "elasticloadbalancing:CreateListener", 143 | "elasticloadbalancing:DeleteListener", 144 | "elasticloadbalancing:CreateRule", 145 | "elasticloadbalancing:DeleteRule" 146 | ], 147 | "Resource": "*" 148 | }, 149 | { 150 | "Effect": "Allow", 151 | "Action": [ 152 | "elasticloadbalancing:AddTags", 153 | "elasticloadbalancing:RemoveTags" 154 | ], 155 | "Resource": [ 156 | "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*", 157 | "arn:aws:elasticloadbalancing:*:*:loadbalancer/net/*/*", 158 | "arn:aws:elasticloadbalancing:*:*:loadbalancer/app/*/*" 159 | ], 160 | "Condition": { 161 | "Null": { 162 | "aws:RequestTag/elbv2.k8s.aws/cluster": "true", 163 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 164 | } 165 | } 166 | }, 167 | { 168 | "Effect": "Allow", 169 | "Action": [ 170 | "elasticloadbalancing:AddTags", 171 | "elasticloadbalancing:RemoveTags" 172 | ], 173 | "Resource": [ 174 | "arn:aws:elasticloadbalancing:*:*:listener/net/*/*/*", 175 | "arn:aws:elasticloadbalancing:*:*:listener/app/*/*/*", 176 | "arn:aws:elasticloadbalancing:*:*:listener-rule/net/*/*/*", 177 | "arn:aws:elasticloadbalancing:*:*:listener-rule/app/*/*/*" 178 | ] 179 | }, 180 | { 181 | "Effect": "Allow", 182 | "Action": [ 183 | "elasticloadbalancing:ModifyLoadBalancerAttributes", 184 | "elasticloadbalancing:SetIpAddressType", 185 | "elasticloadbalancing:SetSecurityGroups", 186 | "elasticloadbalancing:SetSubnets", 187 | "elasticloadbalancing:DeleteLoadBalancer", 188 | "elasticloadbalancing:ModifyTargetGroup", 189 | "elasticloadbalancing:ModifyTargetGroupAttributes", 190 | "elasticloadbalancing:DeleteTargetGroup" 191 | ], 192 | "Resource": "*", 193 | "Condition": { 194 | "Null": { 195 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 196 | } 197 | } 198 | }, 199 | { 200 | "Effect": "Allow", 201 | "Action": [ 202 | "elasticloadbalancing:RegisterTargets", 203 | "elasticloadbalancing:DeregisterTargets" 204 | ], 205 | "Resource": "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*" 206 | }, 207 | { 208 | "Effect": "Allow", 209 | "Action": [ 210 | "elasticloadbalancing:SetWebAcl", 211 | "elasticloadbalancing:ModifyListener", 212 | "elasticloadbalancing:AddListenerCertificates", 213 | "elasticloadbalancing:RemoveListenerCertificates", 214 | "elasticloadbalancing:ModifyRule" 215 | ], 216 | "Resource": "*" 217 | } 218 | ] 219 | } -------------------------------------------------------------------------------- /terraform/modules/metrics-server/components.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 3 | # software and associated documentation files (the "Software"), to deal in the Software 4 | # without restriction, including without limitation the rights to use, copy, modify, 5 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 6 | # permit persons to whom the Software is furnished to do so. 7 | # 8 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 9 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 11 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 12 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 13 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | apiVersion: v1 15 | kind: ServiceAccount 16 | metadata: 17 | labels: 18 | k8s-app: metrics-server 19 | name: metrics-server 20 | namespace: kube-system 21 | --- 22 | apiVersion: rbac.authorization.k8s.io/v1 23 | kind: ClusterRole 24 | metadata: 25 | labels: 26 | k8s-app: metrics-server 27 | rbac.authorization.k8s.io/aggregate-to-admin: "true" 28 | rbac.authorization.k8s.io/aggregate-to-edit: "true" 29 | rbac.authorization.k8s.io/aggregate-to-view: "true" 30 | name: system:aggregated-metrics-reader 31 | rules: 32 | - apiGroups: 33 | - metrics.k8s.io 34 | resources: 35 | - pods 36 | - nodes 37 | verbs: 38 | - get 39 | - list 40 | - watch 41 | --- 42 | apiVersion: rbac.authorization.k8s.io/v1 43 | kind: ClusterRole 44 | metadata: 45 | labels: 46 | k8s-app: metrics-server 47 | name: system:metrics-server 48 | rules: 49 | - apiGroups: 50 | - "" 51 | resources: 52 | - nodes/metrics 53 | verbs: 54 | - get 55 | - apiGroups: 56 | - "" 57 | resources: 58 | - pods 59 | - nodes 60 | verbs: 61 | - get 62 | - list 63 | - watch 64 | --- 65 | apiVersion: rbac.authorization.k8s.io/v1 66 | kind: RoleBinding 67 | metadata: 68 | labels: 69 | k8s-app: metrics-server 70 | name: metrics-server-auth-reader 71 | namespace: kube-system 72 | roleRef: 73 | apiGroup: rbac.authorization.k8s.io 74 | kind: Role 75 | name: extension-apiserver-authentication-reader 76 | subjects: 77 | - kind: ServiceAccount 78 | name: metrics-server 79 | namespace: kube-system 80 | --- 81 | apiVersion: rbac.authorization.k8s.io/v1 82 | kind: ClusterRoleBinding 83 | metadata: 84 | labels: 85 | k8s-app: metrics-server 86 | name: metrics-server:system:auth-delegator 87 | roleRef: 88 | apiGroup: rbac.authorization.k8s.io 89 | kind: ClusterRole 90 | name: system:auth-delegator 91 | subjects: 92 | - kind: ServiceAccount 93 | name: metrics-server 94 | namespace: kube-system 95 | --- 96 | apiVersion: rbac.authorization.k8s.io/v1 97 | kind: ClusterRoleBinding 98 | metadata: 99 | labels: 100 | k8s-app: metrics-server 101 | name: system:metrics-server 102 | roleRef: 103 | apiGroup: rbac.authorization.k8s.io 104 | kind: ClusterRole 105 | name: system:metrics-server 106 | subjects: 107 | - kind: ServiceAccount 108 | name: metrics-server 109 | namespace: kube-system 110 | --- 111 | apiVersion: v1 112 | kind: Service 113 | metadata: 114 | labels: 115 | k8s-app: metrics-server 116 | name: metrics-server 117 | namespace: kube-system 118 | spec: 119 | ports: 120 | - name: https 121 | port: 443 122 | protocol: TCP 123 | targetPort: https 124 | selector: 125 | k8s-app: metrics-server 126 | --- 127 | apiVersion: apps/v1 128 | kind: Deployment 129 | metadata: 130 | labels: 131 | k8s-app: metrics-server 132 | name: metrics-server 133 | namespace: kube-system 134 | spec: 135 | selector: 136 | matchLabels: 137 | k8s-app: metrics-server 138 | strategy: 139 | rollingUpdate: 140 | maxUnavailable: 0 141 | template: 142 | metadata: 143 | labels: 144 | k8s-app: metrics-server 145 | spec: 146 | containers: 147 | - args: 148 | - --cert-dir=/tmp 149 | - --secure-port=4443 150 | - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname 151 | - --kubelet-use-node-status-port 152 | - --metric-resolution=15s 153 | image: k8s.gcr.io/metrics-server/metrics-server:v0.6.2 154 | imagePullPolicy: IfNotPresent 155 | livenessProbe: 156 | failureThreshold: 3 157 | httpGet: 158 | path: /livez 159 | port: https 160 | scheme: HTTPS 161 | periodSeconds: 10 162 | name: metrics-server 163 | ports: 164 | - containerPort: 4443 165 | name: https 166 | protocol: TCP 167 | readinessProbe: 168 | failureThreshold: 3 169 | httpGet: 170 | path: /readyz 171 | port: https 172 | scheme: HTTPS 173 | initialDelaySeconds: 20 174 | periodSeconds: 10 175 | resources: 176 | requests: 177 | cpu: 100m 178 | memory: 200Mi 179 | securityContext: 180 | allowPrivilegeEscalation: false 181 | readOnlyRootFilesystem: true 182 | runAsNonRoot: true 183 | runAsUser: 1000 184 | volumeMounts: 185 | - mountPath: /tmp 186 | name: tmp-dir 187 | nodeSelector: 188 | kubernetes.io/os: linux 189 | priorityClassName: system-cluster-critical 190 | serviceAccountName: metrics-server 191 | volumes: 192 | - emptyDir: {} 193 | name: tmp-dir 194 | --- 195 | apiVersion: apiregistration.k8s.io/v1 196 | kind: APIService 197 | metadata: 198 | labels: 199 | k8s-app: metrics-server 200 | name: v1beta1.metrics.k8s.io 201 | spec: 202 | group: metrics.k8s.io 203 | groupPriorityMinimum: 100 204 | insecureSkipTLSVerify: true 205 | service: 206 | name: metrics-server 207 | namespace: kube-system 208 | version: v1beta1 209 | versionPriority: 100 210 | -------------------------------------------------------------------------------- /terraform/template/external-dns-rbac.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: 5 | labels: 6 | app.kubernetes.io/name: 7 | --- 8 | apiVersion: rbac.authorization.k8s.io/v1 9 | kind: ClusterRole 10 | metadata: 11 | name: 12 | labels: 13 | app.kubernetes.io/name: 14 | rules: 15 | - apiGroups: [""] 16 | resources: ["services","endpoints","pods","nodes"] 17 | verbs: ["get","watch","list"] 18 | - apiGroups: ["extensions","networking.k8s.io"] 19 | resources: ["ingresses"] 20 | verbs: ["get","watch","list"] 21 | --- 22 | apiVersion: rbac.authorization.k8s.io/v1 23 | kind: ClusterRoleBinding 24 | metadata: 25 | name: external-dns-viewer 26 | labels: 27 | app.kubernetes.io/name: 28 | roleRef: 29 | apiGroup: rbac.authorization.k8s.io 30 | kind: ClusterRole 31 | name: 32 | subjects: 33 | - kind: ServiceAccount 34 | name: 35 | namespace: default # change to desired namespace: externaldns, kube-addons 36 | --- 37 | apiVersion: apps/v1 38 | kind: Deployment 39 | metadata: 40 | name: 41 | labels: 42 | app.kubernetes.io/name: 43 | spec: 44 | strategy: 45 | type: Recreate 46 | selector: 47 | matchLabels: 48 | app.kubernetes.io/name: 49 | template: 50 | metadata: 51 | labels: 52 | app.kubernetes.io/name: 53 | spec: 54 | serviceAccountName: 55 | containers: 56 | - name: 57 | image: registry.k8s.io/external-dns/external-dns:v0.13.2 58 | args: 59 | - --source=service 60 | - --source=ingress 61 | - --domain-filter= # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones 62 | - --provider=aws 63 | - --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization 64 | - --aws-zone-type=public # only look at public hosted zones (valid values are public, private or no value for both) 65 | - --registry=txt 66 | - --txt-owner-id= 67 | env: 68 | - name: AWS_DEFAULT_REGION 69 | value: # change to region where EKS is installed 70 | # # Uncommend below if using static credentials 71 | # - name: AWS_SHARED_CREDENTIALS_FILE 72 | # value: /.aws/credentials 73 | # volumeMounts: 74 | # - name: aws-credentials 75 | # mountPath: /.aws 76 | # readOnly: true 77 | # volumes: 78 | # - name: aws-credentials 79 | # secret: 80 | # secretName: external-dns -------------------------------------------------------------------------------- /terraform/terraform.tfvars: -------------------------------------------------------------------------------- 1 | db_username = "auroraserverless" # Input Database username 2 | db_password = "" # Input Database password 3 | route53_zone_id = "" # Input your public hosted zone Route53 ID 4 | route53_zone_name = "" # Input your Route53 Zone Name 5 | keycloak_username = "demo_user" 6 | keycloak_password = "