From 1dbf90749cc53bbf58cc8f1f6b5eaa9025bd73f0 Mon Sep 17 00:00:00 2001 From: abalcobia Date: Thu, 7 Apr 2022 16:32:39 +0100 Subject: [PATCH 1/6] added github app support --- README.md | 5 +++ examples/github-complete/README.md | 23 +++++++++-- examples/github-complete/main.tf | 24 +++++++++--- .../github-complete/terraform.tfvars.sample | 3 ++ examples/github-complete/variables.tf | 19 +++++++--- examples/github-repository-webhook/README.md | 1 - examples/github-repository-webhook/main.tf | 2 +- .../github-repository-webhook/variables.tf | 5 --- main.tf | 38 ++++++++++++++----- variables.tf | 24 ++++++++++++ 10 files changed, 114 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 5b0e4c01..0b567e2b 100644 --- a/README.md +++ b/README.md @@ -273,6 +273,7 @@ allow_github_webhooks = true | [aws_route53_record.atlantis](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource | | [aws_route53_record.atlantis_aaaa](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource | | [aws_ssm_parameter.atlantis_bitbucket_user_token](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | +| [aws_ssm_parameter.atlantis_github_app_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | | [aws_ssm_parameter.atlantis_github_user_token](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | | [aws_ssm_parameter.atlantis_gitlab_user_token](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | | [aws_ssm_parameter.webhook](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | @@ -313,6 +314,9 @@ allow_github_webhooks = true | [atlantis\_bitbucket\_user\_token](#input\_atlantis\_bitbucket\_user\_token) | Bitbucket token of the user that is running the Atlantis command | `string` | `""` | no | | [atlantis\_bitbucket\_user\_token\_ssm\_parameter\_name](#input\_atlantis\_bitbucket\_user\_token\_ssm\_parameter\_name) | Name of SSM parameter to keep atlantis\_bitbucket\_user\_token | `string` | `"/atlantis/bitbucket/user/token"` | no | | [atlantis\_fqdn](#input\_atlantis\_fqdn) | FQDN of Atlantis to use. Set this only to override Route53 and ALB's DNS name. | `string` | `null` | no | +| [atlantis\_github\_app\_id](#input\_atlantis\_github\_app\_id) | GitHub App ID that is running the Atlantis command | `string` | `""` | no | +| [atlantis\_github\_app\_key](#input\_atlantis\_github\_app\_key) | GitHub App private key that is running the Atlantis command | `string` | `""` | no | +| [atlantis\_github\_app\_key\_ssm\_parameter\_name](#input\_atlantis\_github\_app\_key\_ssm\_parameter\_name) | Name of SSM parameter to keep atlantis\_github\_app\_key | `string` | `"/atlantis/github/app/key"` | no | | [atlantis\_github\_user](#input\_atlantis\_github\_user) | GitHub username that is running the Atlantis command | `string` | `""` | no | | [atlantis\_github\_user\_token](#input\_atlantis\_github\_user\_token) | GitHub token of the user that is running the Atlantis command | `string` | `""` | no | | [atlantis\_github\_user\_token\_ssm\_parameter\_name](#input\_atlantis\_github\_user\_token\_ssm\_parameter\_name) | Name of SSM parameter to keep atlantis\_github\_user\_token | `string` | `"/atlantis/github/user/token"` | no | @@ -328,6 +332,7 @@ allow_github_webhooks = true | [atlantis\_repo\_allowlist](#input\_atlantis\_repo\_allowlist) | List of allowed repositories Atlantis can be used with | `list(string)` | n/a | yes | | [atlantis\_security\_group\_tags](#input\_atlantis\_security\_group\_tags) | Additional tags to put on the atlantis security group | `map(string)` | `{}` | no | | [atlantis\_version](#input\_atlantis\_version) | Verion of Atlantis to run. If not specified latest will be used | `string` | `"latest"` | no | +| [atlantis\_write\_git\_creds](#input\_atlantis\_write\_git\_creds) | Write out a .git-credentials file with the provider user and token to allow cloning private modules over HTTPS or SSH | `string` | `"true"` | no | | [azs](#input\_azs) | A list of availability zones in the region | `list(string)` | `[]` | no | | [certificate\_arn](#input\_certificate\_arn) | ARN of certificate issued by AWS ACM. If empty, a new ACM certificate will be created and validated using Route53 DNS | `string` | `""` | no | | [cidr](#input\_cidr) | The CIDR block for the VPC which will be created if `vpc_id` is not specified | `string` | `""` | no | diff --git a/examples/github-complete/README.md b/examples/github-complete/README.md index 329e9011..b6619e5b 100644 --- a/examples/github-complete/README.md +++ b/examples/github-complete/README.md @@ -1,10 +1,25 @@ -# Complete Atlantis example with GitHub Webhooks +# Complete Atlantis example with GitHub App and Webhooks Configuration in this directory creates the necessary infrastructure and resources for running Atlantis on Fargate plus GitHub repository webhooks configured to Atlantis URL. An existing Route53 hosted zone and domain is required to deploy this example. -GitHub's personal access token can be generated at https://github.com/settings/tokens +The GitHub App can be generated using multiple methods: + +- You can follow Atlantis instructions depicted [here](https://www.runatlantis.io/docs/access-credentials.html#github-app). The Atlantis method mostly automates the GitHub App generation using [GitHub App Manifest](https://docs.github.com/en/developers/apps/building-github-apps/creating-a-github-app-from-a-manifest), but you need an exposed endpoint to complete the process. +- The other method is to manually create the GitHub App as instructed [here](https://docs.github.com/en/developers/apps/building-github-apps/creating-a-github-app). + +Once you have your GitHub App registered you will be able to access/manage the required parameters: + +- `atlantis_github_app_id` to identify the GitHub app. +- `atlantis_github_app_key` to interact with GitHub. +- `atlantis_github_webhook_secret` to receive and validate incoming webhook invocations from GitHub. + +## GitHub Personal Access Token (PAT) is no longer recommended + +While still supported, the use of GitHub Personal Access Token (PAT) is no longer the recommended method in favor of GitHub App. + +[GitHub Apps](https://docs.github.com/en/developers/apps/getting-started-with-apps/about-apps) provide more control over repository access/permissions and does not require the use of bot accounts. ## Usage @@ -62,10 +77,12 @@ Go to https://eu-west-1.console.aws.amazon.com/ecs/home?region=eu-west-1#/settin |------|-------------|------|---------|:--------:| | [alb\_ingress\_cidr\_blocks](#input\_alb\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules of the ALB - use your personal IP in the form of `x.x.x.x/32` for restricted testing | `list(string)` | n/a | yes | | [domain](#input\_domain) | Route53 domain name to use for ACM certificate. Route53 zone for this domain should be created in advance | `string` | n/a | yes | +| [github\_app\_id](#input\_github\_app\_id) | GitHub App ID that is running the Atlantis command | `string` | n/a | yes | +| [github\_app\_key](#input\_github\_app\_key) | The PEM encoded private key for the GitHub App | `string` | n/a | yes | | [github\_owner](#input\_github\_owner) | Github owner | `string` | n/a | yes | | [github\_repo\_names](#input\_github\_repo\_names) | List of Github repositories that should be monitored by Atlantis | `list(string)` | n/a | yes | -| [github\_token](#input\_github\_token) | Github token | `string` | n/a | yes | | [github\_user](#input\_github\_user) | Github user for Atlantis to utilize when performing Github activities | `string` | n/a | yes | +| [github\_webhook\_secret](#input\_github\_webhook\_secret) | Webhook secret | `string` | n/a | yes | ## Outputs diff --git a/examples/github-complete/main.tf b/examples/github-complete/main.tf index d65aa460..7eed5d53 100644 --- a/examples/github-complete/main.tf +++ b/examples/github-complete/main.tf @@ -82,10 +82,24 @@ module "atlantis" { permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/cloud/developer-boundary-policy" path = "/delegatedadmin/developer/" - # Atlantis - atlantis_github_user = var.github_user - atlantis_github_user_token = var.github_token - atlantis_repo_allowlist = [for repo in var.github_repo_names : "github.com/${var.github_owner}/${repo}"] + # Atlantis w/ GitHub user + + atlantis_github_user = var.github_user + atlantis_repo_allowlist = [for repo in var.github_repo_names : "github.com/${var.github_owner}/${repo}"] + + # Atlantis w/ GitHub app + + ################################################################################ + # Suggestion: instead of allocating the values of the atlantis_github_app_key + # and atlantis_github_webhook_secret in the tfvars file,it is suggested to + # upload the values in the AWS Parameter Store of the atlantis account and + # call the values via the data source function + # (e.g. data.aws_ssm_parameter.ghapp_key.value) for security reasons. + ################################################################################ + + atlantis_github_app_id = var.github_app_id + atlantis_github_app_key = var.github_app_key + atlantis_github_webhook_secret = var.github_webhook_secret # webhook secret associated to GitHub app # ALB access alb_ingress_cidr_blocks = var.alb_ingress_cidr_blocks @@ -133,7 +147,7 @@ module "github_repository_webhook" { source = "../../modules/github-repository-webhook" github_owner = var.github_owner - github_token = var.github_token + atlantis_repo_allowlist = var.github_repo_names diff --git a/examples/github-complete/terraform.tfvars.sample b/examples/github-complete/terraform.tfvars.sample index a9bec091..e2530601 100644 --- a/examples/github-complete/terraform.tfvars.sample +++ b/examples/github-complete/terraform.tfvars.sample @@ -4,3 +4,6 @@ github_owner = "myorg" github_user = "atlantis" github_token = "mygithubpersonalaccesstokenforatlantis" github_repo_names = ["mycoolrepo1", "mycoolrepo2"] +github_app_id = "mygithubappid" +github_app_key = "-----BEGIN RSA PRIVATE KEY-----(...)" +webhook_secret = "mywebhooksecretforatlantis" diff --git a/examples/github-complete/variables.tf b/examples/github-complete/variables.tf index c71f1806..d8ca4602 100644 --- a/examples/github-complete/variables.tf +++ b/examples/github-complete/variables.tf @@ -8,11 +8,6 @@ variable "alb_ingress_cidr_blocks" { type = list(string) } -variable "github_token" { - description = "Github token" - type = string -} - variable "github_owner" { description = "Github owner" type = string @@ -28,3 +23,17 @@ variable "github_repo_names" { type = list(string) } +variable "github_app_id" { + type = string + description = "GitHub App ID that is running the Atlantis command" +} + +variable "github_app_key" { + description = "The PEM encoded private key for the GitHub App" + type = string +} + +variable "github_webhook_secret" { + description = "Webhook secret" + type = string +} diff --git a/examples/github-repository-webhook/README.md b/examples/github-repository-webhook/README.md index fc9c3373..3b6764c5 100644 --- a/examples/github-repository-webhook/README.md +++ b/examples/github-repository-webhook/README.md @@ -48,7 +48,6 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [github\_owner](#input\_github\_owner) | Github owner | `string` | n/a | yes | -| [github\_token](#input\_github\_token) | Github token | `string` | n/a | yes | ## Outputs diff --git a/examples/github-repository-webhook/main.tf b/examples/github-repository-webhook/main.tf index a863c67a..153b8570 100644 --- a/examples/github-repository-webhook/main.tf +++ b/examples/github-repository-webhook/main.tf @@ -11,7 +11,7 @@ module "github_repository_webhook" { create_github_repository_webhook = true - github_token = var.github_token + github_owner = var.github_owner # Fetching these attributes from created already Atlantis Terraform state file diff --git a/examples/github-repository-webhook/variables.tf b/examples/github-repository-webhook/variables.tf index a5b5666c..5e4fac82 100644 --- a/examples/github-repository-webhook/variables.tf +++ b/examples/github-repository-webhook/variables.tf @@ -1,8 +1,3 @@ -variable "github_token" { - description = "Github token" - type = string -} - variable "github_owner" { description = "Github owner" type = string diff --git a/main.tf b/main.tf index 7b66568c..0bafc514 100644 --- a/main.tf +++ b/main.tf @@ -14,15 +14,15 @@ locals { )}" atlantis_url_events = "${local.atlantis_url}/events" - # Include only one group of secrets - for github, gitlab or bitbucket - has_secrets = var.atlantis_gitlab_user_token != "" || var.atlantis_github_user_token != "" || var.atlantis_bitbucket_user_token != "" + # Include only one group of secrets - for github, github app, gitlab or bitbucket + has_secrets = try(coalesce(var.atlantis_gitlab_user_token, var.atlantis_github_user_token, var.atlantis_github_app_key, var.atlantis_bitbucket_user_token) != "", false) - # token - secret_name_key = local.has_secrets ? var.atlantis_gitlab_user_token != "" ? "ATLANTIS_GITLAB_TOKEN" : var.atlantis_github_user_token != "" ? "ATLANTIS_GH_TOKEN" : "ATLANTIS_BITBUCKET_TOKEN" : "" - secret_name_value_from = local.has_secrets ? var.atlantis_gitlab_user_token != "" ? var.atlantis_gitlab_user_token_ssm_parameter_name : var.atlantis_github_user_token != "" ? var.atlantis_github_user_token_ssm_parameter_name : var.atlantis_bitbucket_user_token_ssm_parameter_name : "" + # token/key + secret_name_key = local.has_secrets ? var.atlantis_gitlab_user_token != "" ? "ATLANTIS_GITLAB_TOKEN" : var.atlantis_github_user_token != "" ? "ATLANTIS_GH_TOKEN" : var.atlantis_github_app_key != "" ? "ATLANTIS_GH_APP_KEY" : "ATLANTIS_BITBUCKET_TOKEN" : "" + secret_name_value_from = local.has_secrets ? var.atlantis_gitlab_user_token != "" ? var.atlantis_gitlab_user_token_ssm_parameter_name : var.atlantis_github_user_token != "" ? var.atlantis_github_user_token_ssm_parameter_name : var.atlantis_github_app_key != "" ? var.atlantis_github_app_key_ssm_parameter_name : var.atlantis_bitbucket_user_token_ssm_parameter_name : "" # webhook - secret_webhook_key = local.has_secrets || var.atlantis_github_webhook_secret != "" ? var.atlantis_gitlab_user_token != "" ? "ATLANTIS_GITLAB_WEBHOOK_SECRET" : var.atlantis_github_user_token != "" || var.atlantis_github_webhook_secret != "" ? "ATLANTIS_GH_WEBHOOK_SECRET" : "ATLANTIS_BITBUCKET_WEBHOOK_SECRET" : "" + secret_webhook_key = local.has_secrets || var.atlantis_github_webhook_secret != "" ? var.atlantis_gitlab_user_token != "" ? "ATLANTIS_GITLAB_WEBHOOK_SECRET" : var.atlantis_github_user_token != "" || var.atlantis_github_webhook_secret != "" ? "ATLANTIS_GH_WEBHOOK_SECRET" : var.atlantis_github_app_key != "" || var.atlantis_github_webhook_secret != "" ? "ATLANTIS_GH_WEBHOOK_SECRET" : "ATLANTIS_BITBUCKET_WEBHOOK_SECRET" : "" # determine if the alb has authentication enabled, otherwise forward the traffic unauthenticated alb_authentication_method = length(keys(var.alb_authenticate_oidc)) > 0 ? "authenticate-oidc" : length(keys(var.alb_authenticate_cognito)) > 0 ? "authenticate-cognito" : "forward" @@ -78,6 +78,14 @@ locals { name = "ATLANTIS_HIDE_PREV_PLAN_COMMENTS" value = var.atlantis_hide_prev_plan_comments }, + { + name = "ATLANTIS_GH_APP_ID" + value = var.atlantis_github_app_id + }, + { + name = "ATLANTIS_WRITE_GIT_CREDS" + value = var.atlantis_write_git_creds + } ] # ECS task definition @@ -186,6 +194,16 @@ resource "aws_ssm_parameter" "atlantis_bitbucket_user_token" { tags = local.tags } +resource "aws_ssm_parameter" "atlantis_github_app_key" { + count = var.atlantis_github_app_key != "" ? 1 : 0 + + name = var.atlantis_github_app_key_ssm_parameter_name + type = "SecureString" + value = var.atlantis_github_app_key + + tags = local.tags +} + ################################################################################ # VPC ################################################################################ @@ -347,8 +365,7 @@ module "alb_http_sg" { ingress_cidr_blocks = sort(compact(concat(var.allow_github_webhooks ? var.github_webhooks_cidr_blocks : [], var.alb_ingress_cidr_blocks))) ingress_ipv6_cidr_blocks = sort(compact(concat(var.allow_github_webhooks ? var.github_webhooks_ipv6_cidr_blocks : [], var.alb_ingress_ipv6_cidr_blocks))) - - tags = merge(local.tags, var.alb_http_security_group_tags) + tags = merge(local.tags, var.alb_http_security_group_tags) } module "atlantis_sg" { @@ -555,7 +572,8 @@ data "aws_iam_policy_document" "ecs_task_access_secrets" { aws_ssm_parameter.webhook.*.arn, aws_ssm_parameter.atlantis_github_user_token.*.arn, aws_ssm_parameter.atlantis_gitlab_user_token.*.arn, - aws_ssm_parameter.atlantis_bitbucket_user_token.*.arn + aws_ssm_parameter.atlantis_bitbucket_user_token.*.arn, + aws_ssm_parameter.atlantis_github_app_key.*.arn ]) actions = [ @@ -579,7 +597,7 @@ data "aws_iam_policy_document" "ecs_task_access_secrets_with_kms" { } resource "aws_iam_role_policy" "ecs_task_access_secrets" { - count = var.atlantis_github_user_token != "" || var.atlantis_gitlab_user_token != "" || var.atlantis_bitbucket_user_token != "" ? 1 : 0 + count = local.has_secrets ? 1 : 0 name = "ECSTaskAccessSecretsPolicy" diff --git a/variables.tf b/variables.tf index 1e5e5017..64201f66 100644 --- a/variables.tf +++ b/variables.tf @@ -286,6 +286,12 @@ variable "atlantis_bitbucket_user_token_ssm_parameter_name" { default = "/atlantis/bitbucket/user/token" } +variable "atlantis_github_app_key_ssm_parameter_name" { + description = "Name of SSM parameter to keep atlantis_github_app_key" + type = string + default = "/atlantis/github/app/key" +} + variable "ssm_kms_key_arn" { description = "ARN of KMS key to use for encryption and decryption of SSM Parameters. Required only if your key uses a custom KMS key and not the default key" type = string @@ -575,6 +581,12 @@ variable "atlantis_hide_prev_plan_comments" { default = "false" } +variable "atlantis_write_git_creds" { + description = "Write out a .git-credentials file with the provider user and token to allow cloning private modules over HTTPS or SSH" + type = string + default = "true" +} + # Github variable "atlantis_github_user" { description = "GitHub username that is running the Atlantis command" @@ -582,6 +594,18 @@ variable "atlantis_github_user" { default = "" } +variable "atlantis_github_app_id" { + description = "GitHub App ID that is running the Atlantis command" + type = string + default = "" +} + +variable "atlantis_github_app_key" { + description = "GitHub App private key that is running the Atlantis command" + type = string + default = "" +} + variable "atlantis_github_user_token" { description = "GitHub token of the user that is running the Atlantis command" type = string From ef78a5b50fbb910e5552738ac5aee1488b261b05 Mon Sep 17 00:00:00 2001 From: Carlos Alexandre Date: Thu, 25 Aug 2022 21:47:42 +0100 Subject: [PATCH 2/6] added github_token variable for webhook --- examples/github-complete/README.md | 1 + examples/github-complete/main.tf | 1 + examples/github-complete/variables.tf | 5 +++++ examples/github-repository-webhook/README.md | 1 + examples/github-repository-webhook/main.tf | 2 +- examples/github-repository-webhook/variables.tf | 5 +++++ 6 files changed, 14 insertions(+), 1 deletion(-) diff --git a/examples/github-complete/README.md b/examples/github-complete/README.md index b6619e5b..2d9b93dc 100644 --- a/examples/github-complete/README.md +++ b/examples/github-complete/README.md @@ -81,6 +81,7 @@ Go to https://eu-west-1.console.aws.amazon.com/ecs/home?region=eu-west-1#/settin | [github\_app\_key](#input\_github\_app\_key) | The PEM encoded private key for the GitHub App | `string` | n/a | yes | | [github\_owner](#input\_github\_owner) | Github owner | `string` | n/a | yes | | [github\_repo\_names](#input\_github\_repo\_names) | List of Github repositories that should be monitored by Atlantis | `list(string)` | n/a | yes | +| [github\_token](#input\_github\_token) | Github token | `string` | n/a | yes | | [github\_user](#input\_github\_user) | Github user for Atlantis to utilize when performing Github activities | `string` | n/a | yes | | [github\_webhook\_secret](#input\_github\_webhook\_secret) | Webhook secret | `string` | n/a | yes | diff --git a/examples/github-complete/main.tf b/examples/github-complete/main.tf index 7eed5d53..abe5e546 100644 --- a/examples/github-complete/main.tf +++ b/examples/github-complete/main.tf @@ -147,6 +147,7 @@ module "github_repository_webhook" { source = "../../modules/github-repository-webhook" github_owner = var.github_owner + github_token = var.github_token atlantis_repo_allowlist = var.github_repo_names diff --git a/examples/github-complete/variables.tf b/examples/github-complete/variables.tf index d8ca4602..cc816bb1 100644 --- a/examples/github-complete/variables.tf +++ b/examples/github-complete/variables.tf @@ -8,6 +8,11 @@ variable "alb_ingress_cidr_blocks" { type = list(string) } +variable "github_token" { + description = "Github token" + type = string +} + variable "github_owner" { description = "Github owner" type = string diff --git a/examples/github-repository-webhook/README.md b/examples/github-repository-webhook/README.md index 3b6764c5..fc9c3373 100644 --- a/examples/github-repository-webhook/README.md +++ b/examples/github-repository-webhook/README.md @@ -48,6 +48,7 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [github\_owner](#input\_github\_owner) | Github owner | `string` | n/a | yes | +| [github\_token](#input\_github\_token) | Github token | `string` | n/a | yes | ## Outputs diff --git a/examples/github-repository-webhook/main.tf b/examples/github-repository-webhook/main.tf index 153b8570..a863c67a 100644 --- a/examples/github-repository-webhook/main.tf +++ b/examples/github-repository-webhook/main.tf @@ -11,7 +11,7 @@ module "github_repository_webhook" { create_github_repository_webhook = true - + github_token = var.github_token github_owner = var.github_owner # Fetching these attributes from created already Atlantis Terraform state file diff --git a/examples/github-repository-webhook/variables.tf b/examples/github-repository-webhook/variables.tf index 5e4fac82..a5b5666c 100644 --- a/examples/github-repository-webhook/variables.tf +++ b/examples/github-repository-webhook/variables.tf @@ -1,3 +1,8 @@ +variable "github_token" { + description = "Github token" + type = string +} + variable "github_owner" { description = "Github owner" type = string From 2a2caa9be1278e4ffb562c16447f807cb00bec53 Mon Sep 17 00:00:00 2001 From: Carlos Alexandre Date: Fri, 26 Aug 2022 14:30:08 +0100 Subject: [PATCH 3/6] updated example --- examples/github-complete/README.md | 6 +----- examples/github-complete/main.tf | 17 ----------------- examples/github-complete/outputs.tf | 12 ------------ .../github-complete/terraform.tfvars.sample | 1 - examples/github-complete/variables.tf | 5 ----- 5 files changed, 1 insertion(+), 40 deletions(-) diff --git a/examples/github-complete/README.md b/examples/github-complete/README.md index 2d9b93dc..a1d11b78 100644 --- a/examples/github-complete/README.md +++ b/examples/github-complete/README.md @@ -23,7 +23,7 @@ While still supported, the use of GitHub Personal Access Token (PAT) is no longe ## Usage -To run this code you need to copy `terraform.tfvars.sample` into `terraform.tfvars` and update the values locally or specify them using environment variables (`TF_VAR_github_token=xxx`, `TF_VAR_github_owner=xxx`, etc.). Once ready, execute: +To run this code you need to copy `terraform.tfvars.sample` into `terraform.tfvars` and update the values locally or specify them using environment variables (`TF_VAR_github_app_id=xxx`, `TF_VAR_github_owner=xxx`, etc.). Once ready, execute: ```bash $ terraform init @@ -60,7 +60,6 @@ Go to https://eu-west-1.console.aws.amazon.com/ecs/home?region=eu-west-1#/settin |------|--------|---------| | [atlantis](#module\_atlantis) | ../../ | n/a | | [atlantis\_access\_log\_bucket](#module\_atlantis\_access\_log\_bucket) | terraform-aws-modules/s3-bucket/aws | ~> 3.0 | -| [github\_repository\_webhook](#module\_github\_repository\_webhook) | ../../modules/github-repository-webhook | n/a | ## Resources @@ -81,7 +80,6 @@ Go to https://eu-west-1.console.aws.amazon.com/ecs/home?region=eu-west-1#/settin | [github\_app\_key](#input\_github\_app\_key) | The PEM encoded private key for the GitHub App | `string` | n/a | yes | | [github\_owner](#input\_github\_owner) | Github owner | `string` | n/a | yes | | [github\_repo\_names](#input\_github\_repo\_names) | List of Github repositories that should be monitored by Atlantis | `list(string)` | n/a | yes | -| [github\_token](#input\_github\_token) | Github token | `string` | n/a | yes | | [github\_user](#input\_github\_user) | Github user for Atlantis to utilize when performing Github activities | `string` | n/a | yes | | [github\_webhook\_secret](#input\_github\_webhook\_secret) | Webhook secret | `string` | n/a | yes | @@ -92,7 +90,5 @@ Go to https://eu-west-1.console.aws.amazon.com/ecs/home?region=eu-west-1#/settin | [atlantis\_repo\_allowlist](#output\_atlantis\_repo\_allowlist) | Git repositories where webhook should be created | | [atlantis\_url](#output\_atlantis\_url) | URL of Atlantis | | [ecs\_task\_definition](#output\_ecs\_task\_definition) | Task definition for ECS service (used for external triggers) | -| [github\_webhook\_secret](#output\_github\_webhook\_secret) | Github webhook secret | -| [github\_webhook\_urls](#output\_github\_webhook\_urls) | Github webhook URL | | [task\_role\_arn](#output\_task\_role\_arn) | The Atlantis ECS task role arn | diff --git a/examples/github-complete/main.tf b/examples/github-complete/main.tf index abe5e546..116c8487 100644 --- a/examples/github-complete/main.tf +++ b/examples/github-complete/main.tf @@ -139,23 +139,6 @@ module "atlantis" { tags = local.tags } -################################################################################ -# GitHub Webhooks -################################################################################ - -module "github_repository_webhook" { - source = "../../modules/github-repository-webhook" - - github_owner = var.github_owner - github_token = var.github_token - - - atlantis_repo_allowlist = var.github_repo_names - - webhook_url = module.atlantis.atlantis_url_events - webhook_secret = module.atlantis.webhook_secret -} - ################################################################################ # ALB Access Log Bucket + Policy ################################################################################ diff --git a/examples/github-complete/outputs.tf b/examples/github-complete/outputs.tf index 59959e1f..4a77d356 100644 --- a/examples/github-complete/outputs.tf +++ b/examples/github-complete/outputs.tf @@ -18,15 +18,3 @@ output "ecs_task_definition" { description = "Task definition for ECS service (used for external triggers)" value = module.atlantis.ecs_task_definition } - -# Webhooks -output "github_webhook_urls" { - description = "Github webhook URL" - value = module.github_repository_webhook.repository_webhook_urls -} - -output "github_webhook_secret" { - description = "Github webhook secret" - value = module.github_repository_webhook.repository_webhook_secret - sensitive = true -} diff --git a/examples/github-complete/terraform.tfvars.sample b/examples/github-complete/terraform.tfvars.sample index e2530601..a40302b0 100644 --- a/examples/github-complete/terraform.tfvars.sample +++ b/examples/github-complete/terraform.tfvars.sample @@ -2,7 +2,6 @@ domain = "mydomain.com" alb_ingress_cidr_blocks = ["x.x.x.x/32"] github_owner = "myorg" github_user = "atlantis" -github_token = "mygithubpersonalaccesstokenforatlantis" github_repo_names = ["mycoolrepo1", "mycoolrepo2"] github_app_id = "mygithubappid" github_app_key = "-----BEGIN RSA PRIVATE KEY-----(...)" diff --git a/examples/github-complete/variables.tf b/examples/github-complete/variables.tf index cc816bb1..d8ca4602 100644 --- a/examples/github-complete/variables.tf +++ b/examples/github-complete/variables.tf @@ -8,11 +8,6 @@ variable "alb_ingress_cidr_blocks" { type = list(string) } -variable "github_token" { - description = "Github token" - type = string -} - variable "github_owner" { description = "Github owner" type = string From eb99e45be81b7e78f8510fb667fc0cbeb404887d Mon Sep 17 00:00:00 2001 From: Michael Kania Date: Wed, 26 Oct 2022 13:54:45 -0700 Subject: [PATCH 4/6] Add bootstrap option for github-complete example and move Github App docs to the main README --- README.md | 37 ++++++++++++++++++- examples/github-complete/README.md | 30 ++++++--------- examples/github-complete/main.tf | 16 ++++---- examples/github-complete/outputs.tf | 5 +++ .../github-complete/terraform.tfvars.sample | 8 ++-- examples/github-complete/variables.tf | 10 ++--- 6 files changed, 69 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 0b567e2b..5c194160 100644 --- a/README.md +++ b/README.md @@ -74,8 +74,8 @@ module "atlantis" { certificate_arn = "arn:aws:acm:eu-west-1:135367859851:certificate/70e008e1-c0e1-4c7e-9670-7bb5bd4f5a84" # Atlantis - atlantis_github_user = "atlantis-bot" - atlantis_github_user_token = "examplegithubtoken" + atlantis_github_app_id = "1234567" + atlantis_github_app_key = "-----BEGIN RSA PRIVATE KEY-----(...)" atlantis_repo_allowlist = ["github.com/terraform-aws-modules/*"] } ``` @@ -129,6 +129,39 @@ Make sure that both private and public subnets were created in the same set of a If all provided subnets are public (no NAT gateway) then `ecs_service_assign_public_ip` should be set to `true`. +### Using GitHub App +An Atlantis GitHub App can be generated using multiple methods: + +- You can follow Atlantis instructions depicted [here](https://www.runatlantis.io/docs/access-credentials.html#github-app). The Atlantis method mostly automates the GitHub App generation using [GitHub App Manifest](https://docs.github.com/en/developers/apps/building-github-apps/creating-a-github-app-from-a-manifest), but you need an exposed endpoint to complete the process. +- The other method is to manually create the GitHub App as instructed [here](https://docs.github.com/en/developers/apps/building-github-apps/creating-a-github-app). +1. You create a GitHub App and give it a name - that name must be unique across the world (you can change it later). +2. Provide a valid Homepage URL (this can be the atlantis server url, for instance https://atlantis.mydomain.com) +3. Provide a valid Webhook URL. The Atlantis webhook server path is located by default at https://atlantis.mydomain.com/events +4. Generate a Webhook Secret - this is used for Atlantis to trust the deliveries. This is your github_webhook_secret. +5. Generate a Private Key - this is your github_app_key +6. On the App's settings page (at the top) you find the App ID. This is your github_app_id +7. On the Permissions & Events you need to setup all the permissions and events according to Atlantis documentation + +Now you need to install the App on your organization. + +A self-provisioned GitHub App usually has two parts: the App and the Installation. + +The App part is the first step and its where you setup all the requirements, such as authentication, webhook, permissions, etc... +The Installation part is where you add the created App to an organization/personal-account. It is on the installation page where you setup which repositories the application can access and receive events from. + +Once you have your GitHub App registered you will be able to access/manage the required parameters: + +- `atlantis_github_app_id` to identify the GitHub app. +- `atlantis_github_app_key` to interact with GitHub. +- `atlantis_github_webhook_secret` to receive and validate incoming webhook invocations from GitHub. + +#### GitHub Personal Access Token (PAT) is no longer recommended + +While still supported, the use of GitHub Personal Access Token (PAT) is no longer the recommended method in favor of GitHub App. + +[GitHub Apps](https://docs.github.com/en/developers/apps/getting-started-with-apps/about-apps) provide more control over repository access/permissions and does not require the use of bot accounts. + + ### Secure Atlantis with ALB Built-in Authentication #### OpenID Connect (OIDC) diff --git a/examples/github-complete/README.md b/examples/github-complete/README.md index a1d11b78..ccfe50e3 100644 --- a/examples/github-complete/README.md +++ b/examples/github-complete/README.md @@ -4,26 +4,9 @@ Configuration in this directory creates the necessary infrastructure and resourc An existing Route53 hosted zone and domain is required to deploy this example. -The GitHub App can be generated using multiple methods: - -- You can follow Atlantis instructions depicted [here](https://www.runatlantis.io/docs/access-credentials.html#github-app). The Atlantis method mostly automates the GitHub App generation using [GitHub App Manifest](https://docs.github.com/en/developers/apps/building-github-apps/creating-a-github-app-from-a-manifest), but you need an exposed endpoint to complete the process. -- The other method is to manually create the GitHub App as instructed [here](https://docs.github.com/en/developers/apps/building-github-apps/creating-a-github-app). - -Once you have your GitHub App registered you will be able to access/manage the required parameters: - -- `atlantis_github_app_id` to identify the GitHub app. -- `atlantis_github_app_key` to interact with GitHub. -- `atlantis_github_webhook_secret` to receive and validate incoming webhook invocations from GitHub. - -## GitHub Personal Access Token (PAT) is no longer recommended - -While still supported, the use of GitHub Personal Access Token (PAT) is no longer the recommended method in favor of GitHub App. - -[GitHub Apps](https://docs.github.com/en/developers/apps/getting-started-with-apps/about-apps) provide more control over repository access/permissions and does not require the use of bot accounts. - ## Usage -To run this code you need to copy `terraform.tfvars.sample` into `terraform.tfvars` and update the values locally or specify them using environment variables (`TF_VAR_github_app_id=xxx`, `TF_VAR_github_owner=xxx`, etc.). Once ready, execute: +To run this code you need to copy `terraform.tfvars.sample` into `terraform.tfvars` and update the values locally or specify them using environment variables (`TF_VAR_github_app_id=xxx`, `TF_VAR_github_owner=xxx`, etc.). Ensure that bootstrap_GitHub_app is `true`. Once ready, execute: ```bash $ terraform init @@ -31,6 +14,14 @@ $ terraform plan $ terraform apply ``` +Terraform will output a URL to setup a new Github App via Atlantis, which should look something like https://$ATLANTIS_HOST/github-app/setup. Open that URL and go through the setup process. Before closing the window, click the link to install the new GitHub App on you repositories and copy the values `github_app_id`, `github_app_key`, and `github_webhook_secret` into `terraform.tfvars`. You should also set `bootstrap_github_app` to `false` . Now execute: + +```bash +$ terraform plan +$ terraform apply + +``` + Note - if you receive the following error when running apply: `Error: InvalidParameterException: The new ARN and resource ID format must be enabled to add tags to the service. Opt in to the new format and try again. "atlantiscomplete"` @@ -75,18 +66,19 @@ Go to https://eu-west-1.console.aws.amazon.com/ecs/home?region=eu-west-1#/settin | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [alb\_ingress\_cidr\_blocks](#input\_alb\_ingress\_cidr\_blocks) | List of IPv4 CIDR ranges to use on all ingress rules of the ALB - use your personal IP in the form of `x.x.x.x/32` for restricted testing | `list(string)` | n/a | yes | +| [bootstrap\_github\_app](#input\_bootstrap\_github\_app) | Flag to configure Atlantis to bootstrap a new Github App | `bool` | n/a | yes | | [domain](#input\_domain) | Route53 domain name to use for ACM certificate. Route53 zone for this domain should be created in advance | `string` | n/a | yes | | [github\_app\_id](#input\_github\_app\_id) | GitHub App ID that is running the Atlantis command | `string` | n/a | yes | | [github\_app\_key](#input\_github\_app\_key) | The PEM encoded private key for the GitHub App | `string` | n/a | yes | | [github\_owner](#input\_github\_owner) | Github owner | `string` | n/a | yes | | [github\_repo\_names](#input\_github\_repo\_names) | List of Github repositories that should be monitored by Atlantis | `list(string)` | n/a | yes | -| [github\_user](#input\_github\_user) | Github user for Atlantis to utilize when performing Github activities | `string` | n/a | yes | | [github\_webhook\_secret](#input\_github\_webhook\_secret) | Webhook secret | `string` | n/a | yes | ## Outputs | Name | Description | |------|-------------| +| [atlantis\_github\_app\_setup\_url](#output\_atlantis\_github\_app\_setup\_url) | URL to create a new Github App with Atlantis | | [atlantis\_repo\_allowlist](#output\_atlantis\_repo\_allowlist) | Git repositories where webhook should be created | | [atlantis\_url](#output\_atlantis\_url) | URL of Atlantis | | [ecs\_task\_definition](#output\_ecs\_task\_definition) | Task definition for ECS service (used for external triggers) | diff --git a/examples/github-complete/main.tf b/examples/github-complete/main.tf index 116c8487..b93dd52e 100644 --- a/examples/github-complete/main.tf +++ b/examples/github-complete/main.tf @@ -82,13 +82,11 @@ module "atlantis" { permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/cloud/developer-boundary-policy" path = "/delegatedadmin/developer/" - # Atlantis w/ GitHub user - - atlantis_github_user = var.github_user - atlantis_repo_allowlist = [for repo in var.github_repo_names : "github.com/${var.github_owner}/${repo}"] + # Bootstrapping a new Github App + atlantis_github_user = var.bootstrap_github_app ? "fake" : "" + atlantis_github_user_token = var.bootstrap_github_app ? "fake" : "" # Atlantis w/ GitHub app - ################################################################################ # Suggestion: instead of allocating the values of the atlantis_github_app_key # and atlantis_github_webhook_secret in the tfvars file,it is suggested to @@ -97,9 +95,10 @@ module "atlantis" { # (e.g. data.aws_ssm_parameter.ghapp_key.value) for security reasons. ################################################################################ - atlantis_github_app_id = var.github_app_id - atlantis_github_app_key = var.github_app_key - atlantis_github_webhook_secret = var.github_webhook_secret # webhook secret associated to GitHub app + atlantis_github_app_id = var.bootstrap_github_app ? "" : var.github_app_id + atlantis_github_app_key = var.bootstrap_github_app ? "" : var.github_app_key + atlantis_github_webhook_secret = var.bootstrap_github_app ? "" : var.github_webhook_secret + atlantis_repo_allowlist = [for repo in var.github_repo_names : "github.com/${var.github_owner}/${repo}"] # ALB access alb_ingress_cidr_blocks = var.alb_ingress_cidr_blocks @@ -253,3 +252,4 @@ data "aws_iam_policy_document" "atlantis_access_log_bucket_policy" { } } } + diff --git a/examples/github-complete/outputs.tf b/examples/github-complete/outputs.tf index 4a77d356..f512bff1 100644 --- a/examples/github-complete/outputs.tf +++ b/examples/github-complete/outputs.tf @@ -4,6 +4,11 @@ output "atlantis_url" { value = module.atlantis.atlantis_url } +output "atlantis_github_app_setup_url" { + description = "URL to create a new Github App with Atlantis" + value = "${module.atlantis.atlantis_url}/github-app/setup" +} + output "atlantis_repo_allowlist" { description = "Git repositories where webhook should be created" value = module.atlantis.atlantis_repo_allowlist diff --git a/examples/github-complete/terraform.tfvars.sample b/examples/github-complete/terraform.tfvars.sample index a40302b0..ca86b85e 100644 --- a/examples/github-complete/terraform.tfvars.sample +++ b/examples/github-complete/terraform.tfvars.sample @@ -1,8 +1,10 @@ domain = "mydomain.com" alb_ingress_cidr_blocks = ["x.x.x.x/32"] github_owner = "myorg" -github_user = "atlantis" github_repo_names = ["mycoolrepo1", "mycoolrepo2"] +bootstrap_github_app = true github_app_id = "mygithubappid" -github_app_key = "-----BEGIN RSA PRIVATE KEY-----(...)" -webhook_secret = "mywebhooksecretforatlantis" +github_app_key = <<-EOL +-----BEGIN RSA PRIVATE KEY-----(...) +EOL +github_webhook_secret = "mywebhooksecretforatlantis" diff --git a/examples/github-complete/variables.tf b/examples/github-complete/variables.tf index d8ca4602..81222e40 100644 --- a/examples/github-complete/variables.tf +++ b/examples/github-complete/variables.tf @@ -13,11 +13,6 @@ variable "github_owner" { type = string } -variable "github_user" { - description = "Github user for Atlantis to utilize when performing Github activities" - type = string -} - variable "github_repo_names" { description = "List of Github repositories that should be monitored by Atlantis" type = list(string) @@ -37,3 +32,8 @@ variable "github_webhook_secret" { description = "Webhook secret" type = string } + +variable "bootstrap_github_app" { + description = "Flag to configure Atlantis to bootstrap a new Github App" + type = bool +} From 00e2b8f6e112589024478d1416e05b0d6d7867d5 Mon Sep 17 00:00:00 2001 From: Carlos Alexandre Date: Thu, 27 Oct 2022 21:53:53 +0100 Subject: [PATCH 5/6] Update examples/github-complete/README.md Co-authored-by: Anton Babenko --- examples/github-complete/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/github-complete/README.md b/examples/github-complete/README.md index ccfe50e3..f5011f0c 100644 --- a/examples/github-complete/README.md +++ b/examples/github-complete/README.md @@ -6,7 +6,7 @@ An existing Route53 hosted zone and domain is required to deploy this example. ## Usage -To run this code you need to copy `terraform.tfvars.sample` into `terraform.tfvars` and update the values locally or specify them using environment variables (`TF_VAR_github_app_id=xxx`, `TF_VAR_github_owner=xxx`, etc.). Ensure that bootstrap_GitHub_app is `true`. Once ready, execute: +To run this code you need to copy `terraform.tfvars.sample` into `terraform.tfvars` and update the values locally or specify them using environment variables (`TF_VAR_github_app_id=xxx`, `TF_VAR_github_owner=xxx`, etc.). Ensure that `bootstrap_github_app` is `true`. Once ready, execute: ```bash $ terraform init From 10be7f5b4f3a2335ff09eb0b62e88c4aa7ee6cc4 Mon Sep 17 00:00:00 2001 From: Michael Kania Date: Thu, 27 Oct 2022 15:29:14 -0700 Subject: [PATCH 6/6] fix terraform_deprecated_index warnings from tflint --- main.tf | 20 ++++++++++---------- modules/github-repository-webhook/outputs.tf | 2 +- modules/gitlab-repository-webhook/outputs.tf | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/main.tf b/main.tf index 0bafc514..43a7dd89 100644 --- a/main.tf +++ b/main.tf @@ -8,7 +8,7 @@ locals { atlantis_image = var.atlantis_image == "" ? "ghcr.io/runatlantis/atlantis:${var.atlantis_version}" : var.atlantis_image atlantis_url = "https://${coalesce( var.atlantis_fqdn, - element(concat(aws_route53_record.atlantis.*.fqdn, [""]), 0), + element(concat(aws_route53_record.atlantis[*].fqdn, [""]), 0), module.alb.lb_dns_name, "_" )}" @@ -159,7 +159,7 @@ resource "aws_ssm_parameter" "webhook" { name = var.webhook_ssm_parameter_name type = "SecureString" - value = coalesce(var.atlantis_github_webhook_secret, join("", random_id.webhook.*.hex)) + value = coalesce(var.atlantis_github_webhook_secret, join("", random_id.webhook[*].hex)) tags = local.tags } @@ -420,7 +420,7 @@ module "acm" { domain_name = var.acm_certificate_domain_name == "" ? join(".", [var.name, var.route53_zone_name]) : var.acm_certificate_domain_name - zone_id = var.certificate_arn == "" ? element(concat(data.aws_route53_zone.this.*.id, [""]), 0) : "" + zone_id = var.certificate_arn == "" ? element(concat(data.aws_route53_zone.this[*].id, [""]), 0) : "" tags = local.tags } @@ -569,11 +569,11 @@ data "aws_iam_policy_document" "ecs_task_access_secrets" { effect = "Allow" resources = flatten([ - aws_ssm_parameter.webhook.*.arn, - aws_ssm_parameter.atlantis_github_user_token.*.arn, - aws_ssm_parameter.atlantis_gitlab_user_token.*.arn, - aws_ssm_parameter.atlantis_bitbucket_user_token.*.arn, - aws_ssm_parameter.atlantis_github_app_key.*.arn + aws_ssm_parameter.webhook[*].arn, + aws_ssm_parameter.atlantis_github_user_token[*].arn, + aws_ssm_parameter.atlantis_gitlab_user_token[*].arn, + aws_ssm_parameter.atlantis_bitbucket_user_token[*].arn, + aws_ssm_parameter.atlantis_github_app_key[*].arn ]) actions = [ @@ -606,8 +606,8 @@ resource "aws_iam_role_policy" "ecs_task_access_secrets" { policy = element( compact( concat( - data.aws_iam_policy_document.ecs_task_access_secrets_with_kms.*.json, - data.aws_iam_policy_document.ecs_task_access_secrets.*.json, + data.aws_iam_policy_document.ecs_task_access_secrets_with_kms[*].json, + data.aws_iam_policy_document.ecs_task_access_secrets[*].json, ), ), 0, diff --git a/modules/github-repository-webhook/outputs.tf b/modules/github-repository-webhook/outputs.tf index e2b4aba3..1ee5be53 100644 --- a/modules/github-repository-webhook/outputs.tf +++ b/modules/github-repository-webhook/outputs.tf @@ -1,6 +1,6 @@ output "repository_webhook_urls" { description = "Webhook URL" - value = github_repository_webhook.this.*.url + value = github_repository_webhook.this[*].url } output "repository_webhook_secret" { diff --git a/modules/gitlab-repository-webhook/outputs.tf b/modules/gitlab-repository-webhook/outputs.tf index 0674f68c..faa2d5db 100644 --- a/modules/gitlab-repository-webhook/outputs.tf +++ b/modules/gitlab-repository-webhook/outputs.tf @@ -1,6 +1,6 @@ output "repository_webhook_urls" { description = "Webhook URL" - value = gitlab_project_hook.this.*.url + value = gitlab_project_hook.this[*].url } output "repository_webhook_secret" {