diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..58ab73c --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @silinternational/tf-devs diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..12de581 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,21 @@ +paste_backlog_issue_link_here + +--- + +### Added +- + +### Changed +- + +### Deprecated +- + +### Removed +- + +### Fixed +- + +### Security +- diff --git a/.github/workflows/terraform.yml b/.github/workflows/terraform.yml new file mode 100644 index 0000000..a688fa7 --- /dev/null +++ b/.github/workflows/terraform.yml @@ -0,0 +1,12 @@ +name: Terraform + +on: + push: + branches: [ '**' ] + +jobs: + build: + uses: silinternational/workflows/.github/workflows/terraform.yml@main + with: + # validate with the earliest version allowed by required_version in versions.tf + terraform-version: '~> 1.1' diff --git a/.gitignore b/.gitignore index c929d92..db7b2a0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ *.tfstate.backup .terraform/ tfc-remote.tf +*.tfvars diff --git a/.terraform.lock.hcl b/.terraform.lock.hcl index 7420430..e9e9235 100644 --- a/.terraform.lock.hcl +++ b/.terraform.lock.hcl @@ -2,58 +2,87 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/cloudflare/cloudflare" { - version = "3.7.0" - constraints = "~> 3.7" + version = "4.38.0" + constraints = ">= 2.0.0, >= 3.0.0, >= 3.7.0, < 4.39.0, < 5.0.0" hashes = [ - "h1:UOv3pBFyMxg4n/4LhxYcnItYjXhTe2XXVXoyvHAEWOU=", - "zh:0461136530c8e8e9db339c92cf1b2dff28814e4c0930cced96c4597b6b6a9657", - "zh:09d97a2c0d0ea4f0f58147ea7651f4ed3ceab32a398afa930b90d2e9f105fdd5", - "zh:0a4431931840fb9d5eae4ad76bd95d7df9d3f4ce45f18c079c29a688b2b6d3b0", - "zh:15cd395ae642d90699f5012429431f2c45f650951d0d8319547d7b72c6d61b29", - "zh:30c332f736926376a2e7f0f82ab0d7d682aaefb3917e78faa4a12964cd850822", - "zh:33d9c4745c00034781c135f75543814858098c019dba8b586d3dc72fbe29b02b", - "zh:4029f0d0b53e98a92f3a64f1681dd8d2eca1a6ef534290330fba418a479ae156", - "zh:4d6aa87862debbf709c81d9687222111b7ef22ab1038fa99d84374aee675ec86", - "zh:865f52bcfcede282b2d64d91b6984bd33a75fc000e0d92a5dd027aca9fc3b48b", - "zh:8a04190962b7e78aa1356df75f699d1a96ba319d71dcbfbbc5cbe9afc71d849c", - "zh:9ce9c264c74b4a23bb67799356a82a409d39fbf752f83d29f38b8d866fa71a40", - "zh:d8866223c56e574efede5f1704194e158452b6ee7404addb91d0cc4be48586a5", - "zh:f6daad59ffad2741c84665fa7cbd7ae6d76931b21beff7b7a8be336cebbbfb26", - "zh:fc71c04b466b24d456bf6c0a434324e7ed6508c7c439e2ceb361bf961a077b97", + "h1:HTomuzocukpNLwtWzeSF3yteCVsyVKbwKmN66u9iPac=", + "zh:171ab67cccceead4514fafb2d39e4e708a90cce79000aaf3c29aab7ed4457071", + "zh:18aa7228447baaaefc49a43e8eff970817a7491a63d8937e796357a3829dd979", + "zh:2cbaab6092e81ba6f41fa60a50f14e980c8ec327ee11d0b21f16a478be4b7567", + "zh:53b8e49c06f5b31a8c681f8c0669cf43e78abe71657b8182a221d096bb514965", + "zh:6037cfc60b4b647aabae155fcb46d649ed7c650e0287f05db52b2068f1e27c8a", + "zh:62460982ce1a869eebfca675603fbbd50416cf6b69459fb855bfbe5ae2b97607", + "zh:65f6f3a8470917b6398baa5eb4f74b3932b213eac7c0202798bfad6fd1ee17df", + "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", + "zh:8b5cebe64bf04105a49178a165b6a8800a9a33bae6767143a47fe4977755f805", + "zh:a5596635db0993ee3c3060fbc2227d91b239466e96d2d82642625a5aa2486988", + "zh:b3a9c63038441f13c311fd4b2c7e69e571445e5a7365a20c7cc9046b7e6c8aba", + "zh:b585e7e4d7648a540b14b9182819214896ca9337729eeb1f2034833b17db754d", + "zh:d2c3c545318ac8542369e9fc8228e29ee585febdf203a450fad3e0eded71ce02", + "zh:e95dd2d6c3525073af47d47b763cb81b6a51b20cabf76f789c69328922da9ecf", + "zh:eee6e590b36d6c6168a7daae8afa74a8721fd7aa9f62a710f04a311975100722", ] } provider "registry.terraform.io/hashicorp/aws" { - version = "3.73.0" - constraints = "~> 3.73" + version = "5.72.1" + constraints = ">= 3.0.0, >= 4.0.0, >= 5.0.0, < 6.0.0" hashes = [ - "h1:7MhSIGQifUsSWUqYjDbBHwuemsKbgFYvPxHQ92JoSRE=", - "zh:04a2203758c8992ef4b70d5a0163294063fd3ddd9570db1101150717c274e7d9", - "zh:2f9f1b02a7f8a5aa01fbef5d155125658b62f60ec35bcae64f4314ef4a920922", - "zh:3f5befc947e76cd39eb52c66a3be5a916f24c814995c4364fa9394739efd5388", - "zh:416d352241369a9ed14a919e56bcf45de0e4759c2af7efd58b41a5669908f4ea", - "zh:4ed34ac4bf9894917a7a0e7e3886a9971e995e1b67762eb8a4bebeb848a88ad0", - "zh:553c6979620eb31aba29c669c46c3f8d0f6e55aa5c5b786e9dbe44e1868e8fca", - "zh:604add93fd89dd1ff388057a2ecd9bcc9325a14b28d4e04b1f97a498ac9caf1f", - "zh:7399cee513a6ef15ae97c17f5d09e1bb56f37d11dbbb330abb6c726abd2c6052", - "zh:915d132dc624fe0a141e4774ec853522d1ff355770e97ff5f2e7ad3c4295a8aa", - "zh:9e70a50d797308f0de4c2c1929a54893790b911a4cae33073aadc698004ea733", - "zh:f0a40289b271548d676e1e8b00eaf483b114f93d939e6ccf653d583cddc5961d", + "h1:BkYfMmqLJIqLkLLz9sDRWJR5+7GCXTocNPN4pIHkhQo=", + "zh:0dea6843836e926d33469b48b948744079023816d16a2ff7666bcfb6aa3522d4", + "zh:195fa9513f75800a0d62797ebec75ee73e9b8c28d713fe9b63d3b1d1eec129b3", + "zh:1ed92f3961715bf0e024bcde3c12dfbdc50b00c1f8a43cc00802cfc45a256208", + "zh:2ac687e3a52606466cae4a6813e81d923042488df88d2424e28d3f8530f091bb", + "zh:32e7ca75f9314557daada3c44628fe1f3bf964a4f833bfb4b2295d833fe64b6f", + "zh:374ee0e6b4327cc6ef666908ce5d6450a3a56e90cd2b785e83c2bcfc100021d2", + "zh:5500fd6fdac44f96411fcf9c6d01691159ec35455ed127eb4c3a498e1cc92a64", + "zh:723a2dc4b064c12e7ee62ad4fbfd72fa5e025206ea47b735994ef53f3c373152", + "zh:89d97b87605f1d734f27e642567cbecf785b521af8ea81dac55c77ccde876221", + "zh:951ee1e5731e8d65d521d71b95927e55055b3c4656eef6d46fa580a63328befc", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:9b2b362470b64ec227b2da64762ab8bc4111c6b80365fd9d82fc5e1e33f44038", + "zh:aa6e57d0cb974ff0da5dee5d43ad2745cbbc4a2b507d4c799839b9fa96daf688", + "zh:ba0d14c4a6b7aa844a830d47c0bf995b632e37f0795394b5b60c638b62b7fc03", + "zh:c9764065a9c5d324db0b02bd201b9e3a2118e49c4960884acdeea377173302e9", ] } -provider "registry.terraform.io/hashicorp/random" { - version = "3.1.0" - constraints = "~> 3.1" +provider "registry.terraform.io/hashicorp/http" { + version = "2.2.0" + constraints = ">= 2.0.0, < 3.0.0" hashes = [ - "h1:BZMEPucF+pbu9gsPk0G0BHx7YP04+tKdq2MrRDF1EDM=", + "h1:syLdPUKrNIJ7mF7+ijSSUot8VIuFL/45kbN5UcHEIvU=", + "zh:159add5739a597c08439318f67c440a90ce8444a009e7b8aabbcb9279da9191f", + "zh:1e5fbe9a4b8d3d9f167effc03bd5324ad6ef721c23a174e98c7eb2e8b85e34e8", + "zh:4b150790ac5948ceec4f97df4deaff835e4798049d858c20413cbdff6e610c4d", + "zh:4f85c6130249f45ff0dccdcfe78296382c930c288e2f8ec844d73fa48ab3c4ef", + "zh:74a1270db30043d9601ed70fecea568693552758f912a37b423dec1530a6f390", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:8125231d0b16283130bac7cb4b0c4972a596af10e1a7348ff905dcb02ea61dc9", + "zh:83f78277025d276ee6f4fd1ae93cf69b748c6601e7cfc7e30f07beca9ce4dfdd", + "zh:abca2c2e14ce0984a1353f03fd13e9dc19312ab7844f64129ec09628e3d5d472", + "zh:b5d0f58057c730aab9a0bf348a9143e8a0ae18f2a3ddfb9f56c603aa62212601", + "zh:bc6054404263c1d7faaffe4c27d1f93dd5a9848d515a6eae42d43828c3e10447", + "zh:cb9d4a0aeebd25cbbae5b7c726deb285c007079191bc43a6a8d6b951b7ef928a", ] } -provider "registry.terraform.io/hashicorp/template" { - version = "2.2.0" - constraints = "~> 2.2" +provider "registry.terraform.io/hashicorp/random" { + version = "3.6.3" + constraints = "~> 3.0, ~> 3.1" hashes = [ - "h1:94qn780bi1qjrbC3uQtjJh3Wkfwd5+tTtJHOb7KTg9w=", + "h1:Fnaec9vA8sZ8BXVlN3Xn9Jz3zghSETIKg7ch8oXhxno=", + "zh:04ceb65210251339f07cd4611885d242cd4d0c7306e86dda9785396807c00451", + "zh:448f56199f3e99ff75d5c0afacae867ee795e4dfda6cb5f8e3b2a72ec3583dd8", + "zh:4b4c11ccfba7319e901df2dac836b1ae8f12185e37249e8d870ee10bb87a13fe", + "zh:4fa45c44c0de582c2edb8a2e054f55124520c16a39b2dfc0355929063b6395b1", + "zh:588508280501a06259e023b0695f6a18149a3816d259655c424d068982cbdd36", + "zh:737c4d99a87d2a4d1ac0a54a73d2cb62974ccb2edbd234f333abd079a32ebc9e", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:a357ab512e5ebc6d1fda1382503109766e21bbfdfaa9ccda43d313c122069b30", + "zh:c51bfb15e7d52cc1a2eaec2a903ac2aff15d162c172b1b4c17675190e8147615", + "zh:e0951ee6fa9df90433728b96381fb867e3db98f66f735e0c3e24f8f16903f0ad", + "zh:e3cdcb4e73740621dabd82ee6a37d6cfce7fee2a03d8074df65086760f5cf556", + "zh:eff58323099f1bd9a0bec7cb04f717e7f1b2774c7d612bf7581797e1622613a0", ] } diff --git a/README.md b/README.md new file mode 100644 index 0000000..948f3c5 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# idp-hub-terraform + +This is a Terraform root module defining a SimpleSAMLphp "hub". It is based on +[ssp-base](https://github.com/silinternational/ssp-base) which utilizes several custom SimpleSAMLphp +modules, providing a menu of Identity Provider (IdP) choices for a user to choose from. The hub acts +as an IdP to a number of Service Providers (SP) and as a SP to the chosen IDP. + +This root module creates and manages: + +- VPC (Virtual Private Cloud) +- ASG (Autoscaling Group) +- ALB (Application Load Balancer) +- ECS (Elastic Container Service) Cluster +- CD (Continuous Deployment) IAM user +- RDS (Relational Database Service) MariaDB database for session storage +- Cloudwatch log group and optional dashboard +- ECR (Elastic Container Registry) with optional replication policy +- Optional Cloudflare DNS record +- Cloudtrail logging (audit logs) + +## Using Terraform CLI + +This repository includes a `cloud.tf` file to connect to the Terraform Cloud workspace that uses this repository. +That allows for using the Terraform CLI to do plan-only runs, i.e. `terraform plan`. To begin with, you would need +to run `terraform init` after cloning this repository. You will also need to supply provider credentials, +which can be provided in environment variables. To make this more convenient and less susceptible to unsafe handling +of credentials, you can use the included `op.env` file to automatically pull in the credentials from 1Password. + +## Using 1Password CLI + +1. Install the [1Password CLI](https://developer.1password.com/docs/cli/get-started#install). +2. Run `op signin` and enter your 1Password password when prompted. +3. Prefix any Terraform command with `op run --env-file=op.env`, e.g. `op run --env-file=op.env terraform plan` diff --git a/cloud.tf b/cloud.tf new file mode 100644 index 0000000..d2aa2e3 --- /dev/null +++ b/cloud.tf @@ -0,0 +1,8 @@ +terraform { + cloud { + organization = "gtis" + workspaces { + tags = ["app:idp-hub"] + } + } +} diff --git a/main.tf b/main.tf index ea71795..dffaa9c 100644 --- a/main.tf +++ b/main.tf @@ -1,130 +1,93 @@ locals { - app_name_and_env = "${var.app_name}-${local.app_env}" - app_env = data.terraform_remote_state.common.outputs.app_env - app_environment = data.terraform_remote_state.common.outputs.app_environment - name_tag_suffix = "${var.app_name}-${var.customer}-${local.app_environment}" -} - -/* - * Create ECR repo - */ -module "ecr" { - source = "github.com/silinternational/terraform-modules//aws/ecr?ref=3.5.0" - repo_name = local.app_name_and_env - ecsInstanceRole_arn = data.terraform_remote_state.common.outputs.ecsInstanceRole_arn - ecsServiceRole_arn = data.terraform_remote_state.common.outputs.ecsServiceRole_arn - cd_user_arn = data.terraform_remote_state.common.outputs.codeship_arn -} - -/* - * Create Cloudwatch log group - */ -resource "aws_cloudwatch_log_group" "logs" { - name = local.app_name_and_env - retention_in_days = 30 - + app_name_and_env = "${var.app_name}-${local.app_env}" + app_env = var.app_env + app_environment = var.app_environment + ecr_repo_name = local.app_name_and_env + is_multiregion = var.aws_region_secondary != "" + is_multiregion_primary = local.is_multiregion && var.aws_region != var.aws_region_secondary + create_cd_user = !local.is_multiregion || local.is_multiregion_primary + mysql_database = "session" + mysql_user = "root" tags = { - name = "cloudwatch_log_group-${local.name_tag_suffix}" + managed_by = "terraform" + workspace = terraform.workspace + itse_app_customer = var.customer + itse_app_env = local.app_environment + itse_app_name = "idp-hub" } } -/* - * Create target group for ALB - */ -resource "aws_alb_target_group" "tg" { - name = substr("tg-${local.app_name_and_env}", 0, 32) - port = "80" - protocol = "HTTP" - vpc_id = data.terraform_remote_state.common.outputs.vpc_id - deregistration_delay = "30" - - stickiness { - type = "lb_cookie" - } - - health_check { +module "app" { + source = "silinternational/ecs-app/aws" + version = "~> 0.10.0" + + app_env = local.app_env + app_name = var.app_name + domain_name = var.cloudflare_domain + container_def_json = local.task_def_hub + create_dns_record = false + create_cd_user = local.create_cd_user + database_name = local.mysql_database + database_user = local.mysql_user + desired_count = var.desired_count + subdomain = var.subdomain + create_dashboard = var.create_dashboard + asg_min_size = var.asg_min_size + asg_max_size = var.asg_max_size + instance_type = var.instance_type + alarm_actions_enabled = var.alarm_actions_enabled + ssh_key_name = var.ssh_key_name + aws_zones = var.aws_zones + default_cert_domain_name = "*.${var.cloudflare_domain}" + create_adminer = true + enable_adminer = var.enable_adminer + rds_ca_cert_identifier = "rds-ca-rsa2048-g1" + log_retention_in_days = 60 + asg_tags = local.tags + disable_public_ipv4 = true + enable_ipv6 = true + health_check = { + matcher = "302,303" path = "/" - matcher = "302" - } - - tags = { - name = "alb_target_group-${local.name_tag_suffix}" } } -/* - * Create listener rule for hostname routing to new target group - */ -resource "aws_alb_listener_rule" "tg" { - listener_arn = data.terraform_remote_state.common.outputs.alb_https_listener_arn - priority = "217" - - action { - type = "forward" - target_group_arn = aws_alb_target_group.tg.arn - } - - condition { - host_header { - values = ["${var.subdomain}.${var.cloudflare_domain}"] - } - } - - tags = { - name = "alb_listener_rule-${local.name_tag_suffix}" - } -} /* - * Create cloudwatch dashboard for service + * Create intermediate DNS record using Cloudflare (e.g. hub-us-east-2.example.com) */ -module "ecs-service-cloudwatch-dashboard" { - count = var.create_dashboard ? 1 : 0 - - source = "silinternational/ecs-service-cloudwatch-dashboard/aws" - version = "~> 2.0.0" - - cluster_name = data.terraform_remote_state.common.outputs.ecs_cluster_name - dashboard_name = local.app_name_and_env - service_names = [var.app_name] - aws_region = var.aws_region +resource "cloudflare_record" "intermediate" { + zone_id = data.cloudflare_zone.this.id + name = "${var.subdomain}-${var.aws_region}" + value = module.app.alb_dns_name + type = "CNAME" + comment = "intermediate record - DO NOT change this" + proxied = true } /* - * Create Elasticache subnet group + * Create public DNS record using Cloudflare (e.g. hub.example.com) */ -resource "aws_elasticache_subnet_group" "memcache_subnet_group" { - name = local.app_name_and_env - subnet_ids = data.terraform_remote_state.common.outputs.private_subnet_ids +resource "cloudflare_record" "public" { + count = local.is_multiregion_primary || !local.is_multiregion ? 1 : 0 - tags = { - name = "elasticache_subnet_group-${local.name_tag_suffix}" - } + zone_id = data.cloudflare_zone.this.id + name = var.subdomain + value = cloudflare_record.intermediate.hostname + type = "CNAME" + comment = "public record - this can be changed for failover" + proxied = true } -/* - * Create Cluster - */ -resource "aws_elasticache_cluster" "memcache" { - cluster_id = local.app_name_and_env - engine = "memcached" - node_type = var.memcache_node_type - port = var.memcache_port - num_cache_nodes = var.memcache_num_cache_nodes - parameter_group_name = var.memcache_parameter_group_name - security_group_ids = [data.terraform_remote_state.common.outputs.vpc_default_sg_id] - subnet_group_name = aws_elasticache_subnet_group.memcache_subnet_group.name - az_mode = var.memcache_az_mode - - - tags = { - name = "elasticache_cluster-${local.name_tag_suffix}" - } +data "cloudflare_zone" "this" { + name = var.cloudflare_domain } + /* - * Create required passwords + * Create passwords required for SimpleSAMLphp */ + resource "random_id" "ssp_admin_pass" { byte_length = 32 } @@ -136,10 +99,8 @@ resource "random_id" "ssp_secret_salt" { /* * Create task definition template */ -data "template_file" "task_def_hub" { - template = file("${path.module}/task-def-hub.json") - - vars = { +locals { + task_def_hub = templatefile("${path.module}/task-def-hub.json", { admin_email = var.admin_email admin_name = var.admin_name admin_pass = sensitive(random_id.ssp_admin_pass.hex) @@ -147,56 +108,114 @@ data "template_file" "task_def_hub" { app_env = local.app_env app_name = var.app_name aws_region = var.aws_region - cloudwatch_log_group_name = aws_cloudwatch_log_group.logs.name + cloudwatch_log_group_name = module.app.cloudwatch_log_group_name cloudflare_domain = var.cloudflare_domain cpu = var.cpu docker_image = module.ecr.repo_url docker_tag = var.docker_tag + dynamo_access_key_id = aws_iam_access_key.user_login_logger.id + dynamo_secret_access_key = aws_iam_access_key.user_login_logger.secret + enable_debug = var.enable_debug help_center_url = var.help_center_url - idp_display_name = var.idp_display_name - idp_name = var.idp_name - memcache_host1 = aws_elasticache_cluster.memcache.cache_nodes[0].address - memcache_host2 = aws_elasticache_cluster.memcache.cache_nodes[1].address + logging_level = upper(var.logging_level) memory = var.memory + mysql_host = module.app.database_host + mysql_database = local.mysql_database + mysql_user = local.mysql_user + mysql_password = module.app.database_password secret_salt = random_id.ssp_secret_salt.hex - session_store_type = var.session_store_type + session_store_type = "sql" show_saml_errors = var.show_saml_errors subdomain = var.subdomain - } + theme_color_scheme = var.theme_color_scheme + }) } /* - * Create new ecs service + * Create user for dynamo permissions */ -module "ecs" { - source = "github.com/silinternational/terraform-modules//aws/ecs/service-only?ref=3.5.0" - cluster_id = data.terraform_remote_state.common.outputs.ecs_cluster_id - service_name = var.app_name - service_env = local.app_env - container_def_json = data.template_file.task_def_hub.rendered - desired_count = var.desired_count - tg_arn = aws_alb_target_group.tg.arn - lb_container_name = "hub" - lb_container_port = "80" - ecsServiceRole_arn = data.terraform_remote_state.common.outputs.ecsServiceRole_arn +resource "aws_iam_user" "user_login_logger" { + name = "idp_hub_user_login_logger-${local.app_name_and_env}-${var.aws_region}" } /* - * Create Cloudflare DNS record + * Create key for dynamo permissions */ -resource "cloudflare_record" "dns" { - count = var.create_dns_entry - zone_id = data.cloudflare_zones.domain.zones[0].id - name = var.subdomain - value = data.terraform_remote_state.common.outputs.alb_dns_name - type = "CNAME" - proxied = true +resource "aws_iam_access_key" "user_login_logger" { + user = aws_iam_user.user_login_logger.name } -data "cloudflare_zones" "domain" { - filter { - name = var.cloudflare_domain - lookup_type = "exact" - status = "active" +/* + * Allow user_login_logger user to write to Dynamodb + */ +resource "aws_iam_user_policy" "dynamodb-logger-policy" { + name = "dynamodb_user_login_logger_policy-${local.app_name_and_env}" + user = aws_iam_user.user_login_logger.name + + policy = jsonencode({ + "Version" : "2012-10-17", + "Statement" : [ + { + "Effect" : "Allow", + "Action" : ["dynamodb:PutItem"], + "Resource" : aws_dynamodb_table.logger.arn + } + ] + }) +} + +/* + * Create ECR repo + */ +module "ecr" { + source = "github.com/silinternational/terraform-modules//aws/ecr?ref=8.13.3" + repo_name = local.ecr_repo_name + ecsInstanceRole_arn = module.app.ecsInstanceRole_arn + ecsServiceRole_arn = module.app.ecsServiceRole_arn + cd_user_arn = local.create_cd_user ? module.app.cd_user_arn : var.cd_user_arn + image_retention_count = 20 + image_retention_tags = ["latest", "develop"] +} + + +/* + * DynamoDB table for user login activity logging + */ + +resource "aws_dynamodb_table" "logger" { + name = "${local.app_name_and_env}-user-log" + billing_mode = "PAY_PER_REQUEST" + attribute { + name = "ID" + type = "S" } + hash_key = "ID" + ttl { + enabled = true + attribute_name = "ExpiresAt" + } +} + + +/* + * AWS backup + */ +module "aws_backup" { + count = var.enable_aws_backup ? 1 : 0 + + source = "silinternational/backup/aws" + version = "~> 0.2.2" + + app_name = "${var.app_name}-${var.aws_region}" + app_env = var.app_env + source_arns = [ + module.app.database_arn, + aws_dynamodb_table.logger.arn + ] + backup_schedule = "cron(${var.aws_backup_cron_schedule})" + notification_events = var.aws_backup_notification_events + sns_topic_name = "${local.app_name_and_env}-backup-vault-events" + sns_email_subscription = var.backup_sns_email + cold_storage_after = 0 + delete_after = var.delete_recovery_point_after_days } diff --git a/op.env b/op.env new file mode 100644 index 0000000..913f666 --- /dev/null +++ b/op.env @@ -0,0 +1,4 @@ +CLOUDFLARE_API_TOKEN="op://Apps Dev/Cloudflare DNS read for iidp/credential" +TF_VAR_cloudflare_domain=iidp.net +AWS_ACCESS_KEY_ID="op://Apps Dev/Terraform Enterprise - AWS IdP Account/username" +AWS_SECRET_ACCESS_KEY="op://Apps Dev/Terraform Enterprise - AWS IdP Account/password" diff --git a/outputs.tf b/outputs.tf index 89956f0..1cbc93c 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,6 +1,3 @@ -output "ecr_repo_url" { - value = module.ecr.repo_url -} output "ssp_admin_pass" { value = random_id.ssp_admin_pass.hex @@ -8,9 +5,31 @@ output "ssp_admin_pass" { } output "ssp_secret_salt" { - value = random_id.ssp_secret_salt.hex + value = random_id.ssp_secret_salt.hex + sensitive = true } output "url" { value = "https://${var.subdomain}.${var.cloudflare_domain}" } + +output "cd_user_access_key_id" { + value = module.app.cd_user_access_key_id +} + +output "cd_user_secret_access_key_id" { + value = module.app.cd_user_secret_access_key_id + sensitive = true +} + +output "cd_user_arn" { + value = local.create_cd_user ? module.app.cd_user_arn : var.cd_user_arn +} + +output "user_log_table" { + value = aws_dynamodb_table.logger.name +} + +output "alb_dns_name" { + value = module.app.alb_dns_name +} diff --git a/providers.tf b/providers.tf index 66a24ad..fef420e 100644 --- a/providers.tf +++ b/providers.tf @@ -1,16 +1,10 @@ provider "aws" { region = var.aws_region - access_key = var.aws_access_key - secret_key = var.aws_secret_key + access_key = var.aws_access_key_id + secret_key = var.aws_secret_access_key default_tags { - tags = { - managed_by = "terraform" - workspace = terraform.workspace - itse_app_customer = var.customer - itse_app_env = local.app_environment - itse_app_name = "idp-hub" - } + tags = local.tags } } diff --git a/remote.tf b/remote.tf deleted file mode 100644 index 030ecbe..0000000 --- a/remote.tf +++ /dev/null @@ -1,10 +0,0 @@ -data "terraform_remote_state" "common" { - backend = "remote" - - config = { - organization = split("/", var.tf_remote_common)[0] - workspaces = { - name = split("/", var.tf_remote_common)[1] - } - } -} diff --git a/task-def-hub.json b/task-def-hub.json index ca9fc68..391568d 100644 --- a/task-def-hub.json +++ b/task-def-hub.json @@ -42,8 +42,12 @@ "value": "https://${subdomain}.${cloudflare_domain}/" }, { - "name": "ENABLE_HUB_AUTHPROCS", - "value": "true" + "name": "DYNAMO_ACCESS_KEY_ID", + "value": "${dynamo_access_key_id}" + }, + { + "name": "DYNAMO_SECRET_ACCESS_KEY", + "value": "${dynamo_secret_access_key}" }, { "name": "HELP_CENTER_URL", @@ -54,20 +58,24 @@ "value": "true" }, { - "name": "IDP_DISPLAY_NAME", - "value": "${idp_display_name}" + "name": "LOGGING_LEVEL", + "value": "${logging_level}" + }, + { + "name": "MYSQL_HOST", + "value": "${mysql_host}" }, { - "name": "IDP_NAME", - "value": "${idp_name}" + "name": "MYSQL_DATABASE", + "value": "${mysql_database}" }, { - "name": "MEMCACHE_HOST1", - "value": "${memcache_host1}" + "name": "MYSQL_USER", + "value": "${mysql_user}" }, { - "name": "MEMCACHE_HOST2", - "value": "${memcache_host2}" + "name": "MYSQL_PASSWORD", + "value": "${mysql_password}" }, { "name": "SECRET_SALT", @@ -80,6 +88,14 @@ { "name": "SHOW_SAML_ERRORS", "value": "${show_saml_errors}" + }, + { + "name": "ENABLE_DEBUG", + "value": "${enable_debug}" + }, + { + "name": "THEME_COLOR_SCHEME", + "value": "${theme_color_scheme}" } ], "ulimits": null, diff --git a/terraform.tfvars.example b/terraform.tfvars.example new file mode 100644 index 0000000..ea37c28 --- /dev/null +++ b/terraform.tfvars.example @@ -0,0 +1,5 @@ +analytics_id = "G-" +aws_access_key = "" +aws_secret_key = "" +cloudflare_domain = "example.com" +subdomain = "hub" diff --git a/vars.tf b/vars.tf index dd2f598..99edd8d 100644 --- a/vars.tf +++ b/vars.tf @@ -1,115 +1,256 @@ -variable "admin_email" { - default = "info@insitehome.org" + +/* + * General config + */ + +variable "app_name" { + description = "A name to be used, combined with \"app_env\", for naming resources. Should be unique in the AWS account." + type = string + default = "idp-hub" } -variable "admin_name" { - default = "Insite Admin" +variable "app_env" { + description = "The abbreviated version of the environment used for naming resources, typically either stg or prod" + type = string + default = "dev" } -variable "analytics_id" { +variable "app_environment" { + description = "the full, unabbreviated environment used for AWS tags, typically either staging or production" + type = string + default = "development" } -variable "app_name" { - default = "idp-hub" +variable "customer" { + description = "Customer name, used in AWS tags" + type = string + default = "shared" } -variable "aws_access_key" { + +/* + * AWS configuration + */ + +variable "aws_access_key_id" { + description = "The AWS IAM access key ID for a user with permission to manage all of the resources defined in this module. Can be specified in environment variable AWS_ACCESS_KEY_ID." + type = string + default = null } -variable "aws_region" { - default = "us-east-1" +variable "aws_secret_access_key" { + description = "The AWS IAM secret access key for a user with permission to manage all of the resources defined in this module. Can be specified in environment variable AWS_SECRET_ACCESS_KEY." + type = string + default = null } -variable "aws_secret_key" { +variable "aws_region" { + description = "AWS region in which to create all resources" + type = string + default = "us-east-1" } -variable "cloudflare_domain" { +variable "aws_region_secondary" { + description = "AWS region in which to create ECR replicas. Must be specified in both the primary and secondary hub workspaces. Leave empty for a single-region setup." + type = string + default = "" } -variable "cloudflare_token" { - description = "The Cloudflare API token with permissions on `cloudflare_domain`." +variable "cd_user_arn" { + description = "ARN of the Continuous Deployment (CD) user created by the primary hub in a multiregion configuration. Ignored in a single-region configuration." + type = string default = "" } -variable "cpu" { - default = "128" +variable "docker_tag" { + description = "Docker tag to use in the task definition. Must match the tag name defined in the instance repo's `push_latest` step." + type = string + default = "latest" } -variable "create_dns_entry" { - description = "Set to 1 to create Cloudflare entry, 0 to not create entry" - default = 1 + +/* + * Task definition configuration + */ + +variable "admin_email" { + description = "SAML technical contact email. This information will be available in the generated metadata." + type = string } -variable "desired_count" { - default = 2 +variable "admin_name" { + description = "SAML technical contact name. This information will be available in the generated metadata." + type = string } -variable "docker_tag" { - default = "latest" +variable "analytics_id" { + description = "Google Analytics measurement ID" + type = string +} + +variable "cpu" { + description = "The hard limit of CPU units to present for the task, expressed as an integer using CPU units, e.g. 512 = 0.5 vCPU" + type = string + default = "128" +} + +variable "enable_debug" { + description = "Enables debug for SimpleSAMLphp 'saml' and 'validatexml' modes. CAUTION: may log decrypted SAML messages." + type = string + default = "false" } variable "help_center_url" { - description = "Appears at the top of the IDP selection page" + description = "The URL for the \"Help\" link at the top of the IDP selection page" type = string default = "" } -variable "idp_display_name" { +variable "logging_level" { + description = "Log level for log filter, may be one of: ERR, WARNING, NOTICE, INFO, DEBUG" + type = string + default = "NOTICE" } -variable "idp_name" { +variable "memory" { + description = "The hard limit of memory (in MiB) to present to the task, expressed as an integer" + type = string + default = "128" } -variable "memcache_az_mode" { - type = string - default = "cross-az" +variable "show_saml_errors" { + description = "Used for SimpleSAMLphp `showerrors` config option. When enabled, all error messages and stack traces will be output to the browser." + type = string + default = "false" } -variable "memcache_node_type" { - default = "cache.t2.micro" +variable "theme_color_scheme" { + description = "Set the color scheme for the material theme. Use one of: indigo-purple, blue_grey-teal, red-teal, orange-light_blue, brown-orange, teal-blue" + type = string + default = "indigo-purple" } -variable "memcache_num_cache_nodes" { - type = string - default = 2 + +/* + * DNS configuration + */ + +variable "cloudflare_domain" { + description = "The domain name on which to host the app. Combined with \"subdomain\" to create an ALB listener rule. Also used for the optional DNS record." + type = string } -variable "memcache_parameter_group_name" { - type = string - default = "default.memcached1.5" +variable "cloudflare_token" { + description = "The Cloudflare API token with permissions on the zone identified by `cloudflare_domain`." + type = string + default = null } -variable "memcache_port" { - type = string - default = "11211" +variable "subdomain" { + description = "The subdomain on which to host the app. Combined with \"cloudflare_domain\" to create an ALB listener rule. Also used for the optional DNS record." + type = string + default = "hub" } -variable "memory" { - default = "128" +/* + * ECS and ASG configuration + */ + +variable "asg_min_size" { + description = "minimum number of EC2 instances in the autoscaling group" + type = number + default = 2 } -variable "session_store_type" { - default = "memcache" +variable "asg_max_size" { + description = "maximum number of EC2 instances in the autoscaling group" + type = number + default = 2 } -variable "show_saml_errors" { - default = "false" +variable "alarm_actions_enabled" { + description = "True/false enable auto-scaling events and actions" + type = bool + default = false } -variable "subdomain" { +variable "desired_count" { + description = "Number of tasks to place and keep running." + type = number + default = 2 } -variable "tf_remote_common" { - description = "Path to the Common remote, in `org/workspace` syntax." +variable "instance_type" { + description = "See: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-types.html#AvailableInstanceTypes" + default = "t2.micro" + type = string } -variable "customer" { - description = "Customer name, used in AWS tags" +variable "ssh_key_name" { + description = "Name of SSH key pair to use as default (ec2-user) user key. Set in the launch template" type = string + default = "" } + +/* + * VPC configuration + */ + +variable "aws_zones" { + description = "The VPC availability zone list" + type = list(string) + default = ["us-east-1c", "us-east-1d", "us-east-1e"] +} + + +/* + * Optional features + */ + variable "create_dashboard" { - description = "Set to true to create a CloudWatch dashboard" + description = "Set to true to create a CloudWatch dashboard for monitoring ECS memory and CPU utilization" + type = bool + default = true +} + +variable "enable_adminer" { + description = "Control the creation of a DNS record for Adminer and the desired_count for the Adminer ECS service" + type = bool + default = false +} + + +/* + * AWS Backup + */ + +variable "enable_aws_backup" { + description = "enable backup using AWS Backup service" type = bool default = true } + +variable "aws_backup_cron_schedule" { + description = "cron-type schedule for AWS Backup" + type = string + default = "5 14 * * ? *" # Every day at 3:05 UTC +} + +variable "aws_backup_notification_events" { + description = "The names of the backup events that should trigger an email notification" + type = list(string) + default = ["BACKUP_JOB_FAILED"] +} + +variable "backup_sns_email" { + description = "Optional: email address to receive backup event notifications" + type = string + default = "" +} + +variable "delete_recovery_point_after_days" { + description = "Number of days after which AWS Backup recovery points are deleted" + type = number + default = 30 +} diff --git a/versions.tf b/versions.tf index 126f42d..7ffed99 100644 --- a/versions.tf +++ b/versions.tf @@ -1,22 +1,18 @@ terraform { - required_version = ">= 0.14" + required_version = ">= 1.1" required_providers { aws = { - version = "~> 3.73" + version = ">= 4.0.0, < 6.0.0" source = "hashicorp/aws" } cloudflare = { - version = "~> 3.7" + version = ">= 3.7.0, < 5.0.0" source = "cloudflare/cloudflare" } random = { version = "~> 3.1" source = "hashicorp/random" } - template = { - version = "~> 2.2" - source = "hashicorp/template" - } } }