From 0ea93d993fc612b60d4f11a49920b9e1328b8d1a Mon Sep 17 00:00:00 2001 From: Hadi Qureshi Date: Wed, 20 May 2026 16:03:37 +0100 Subject: [PATCH 1/6] feat: service IAM role assumable via SSO --- terraform/main.tf | 70 +++++++++----------------------------------- terraform/secrets.tf | 14 --------- 2 files changed, 14 insertions(+), 70 deletions(-) delete mode 100644 terraform/secrets.tf diff --git a/terraform/main.tf b/terraform/main.tf index ca6d394..20275c5 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -73,6 +73,20 @@ resource "aws_iam_role" "lambda_function_role" { Principal = { Service = "lambda.amazonaws.com" } + }, + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + AWS = "arn:aws:iam::${var.aws_account_id}:root" + } + Condition = { + ArnLike = { + "aws:PrincipalArn" = [ + "arn:aws:iam::${var.aws_account_id}:role/aws-reserved/sso.amazonaws.com/eu-west-2/AWSReservedSSO_Standard_Administrator_Access_*" + ] + } + } } ] }) @@ -138,59 +152,3 @@ resource "aws_cloudwatch_log_group" "loggroup" { name = "/aws/lambda/${aws_lambda_function.lambda_function.function_name}" retention_in_days = var.log_retention_days } - -# IAM User Group -resource "aws_iam_group" "group" { - name = "${var.env_name}-${var.lambda_name}-user-group" - path = "/" -} - -resource "aws_iam_group_policy_attachment" "group_vpc_permissions_attachment" { - group = aws_iam_group.group.name - policy_arn = aws_iam_policy.vpc_permissions.arn -} - -resource "aws_iam_group_policy_attachment" "group_lambda_logging_attachment" { - group = aws_iam_group.group.name - policy_arn = aws_iam_policy.lambda_logging.arn -} - -resource "aws_iam_group_policy_attachment" "group_lambda_s3_policy_attachment" { - group = aws_iam_group.group.name - policy_arn = aws_iam_policy.lambda_s3_policy.arn -} - -resource "aws_iam_group_policy_attachment" "group_lambda_secret_manager_policy_attachment" { - group = aws_iam_group.group.name - policy_arn = aws_iam_policy.lambda_secret_manager_policy.arn -} - -resource "aws_iam_group_policy_attachment" "group_lambda_eventbridge_policy_attachment" { - group = aws_iam_group.group.name - policy_arn = aws_iam_policy.lambda_eventbridge_policy.arn -} - -# IAM User -resource "aws_iam_user" "user" { - name = "${var.env_name}-${var.lambda_name}" - path = "/" -} - -# Assign IAM User to group -resource "aws_iam_user_group_membership" "user_group_attach" { - user = aws_iam_user.user.name - - groups = [ - aws_iam_group.group.name - ] -} - -# IAM Key Rotation Module -module "iam_key_rotation" { - source = "git::https://github.com/ONS-Innovation/keh-aws-iam-key-rotation.git?ref=v0.1.1" - - iam_username = aws_iam_user.user.name - access_key_secret_arn = aws_secretsmanager_secret.access_key.arn - secret_key_secret_arn = aws_secretsmanager_secret.secret_key.arn - rotation_in_days = 45 -} \ No newline at end of file diff --git a/terraform/secrets.tf b/terraform/secrets.tf deleted file mode 100644 index c0dd165..0000000 --- a/terraform/secrets.tf +++ /dev/null @@ -1,14 +0,0 @@ -# Secrets for rotated IAM user access keys -resource "aws_secretsmanager_secret" "access_key" { - name = "${var.env_name}-${var.lambda_name}-access-key" - description = "Access Key ID for copilot usage lambda IAM user" - recovery_window_in_days = 0 // Secret will be deleted immediately - force_overwrite_replica_secret = true // Allow overwriting the secret in case of changes -} - -resource "aws_secretsmanager_secret" "secret_key" { - name = "${var.env_name}-${var.lambda_name}-secret-key" - description = "Secret Access Key for copilot usage lambda IAM user" - recovery_window_in_days = 0 // Secret will be deleted immediately - force_overwrite_replica_secret = true // Allow overwriting the secret in case of changes -} From fb641ebaf5d88799472888d689dde0f8cd961820 Mon Sep 17 00:00:00 2001 From: Hadi Qureshi Date: Thu, 21 May 2026 11:09:57 +0100 Subject: [PATCH 2/6] chore: update README --- README.md | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 83343f8..3d3385c 100644 --- a/README.md +++ b/README.md @@ -95,24 +95,40 @@ To run the Lambda function outside of a container, we need to execute the `handl **Please Note:** If uncommenting the above in `main.py`, make sure you re-comment the code _before_ pushing back to GitHub. -2. Export the required environment variables: +2. Sign in with AWS SSO, and export the correct profile for this service: + + ```bash + aws sso login + + export AWS_PROFILE=github-copilot-usage-lambda + ``` + + This allows you to assume the AWS IAM role for service, enabling the most secure development experience. This also means you will have limited permissions until you exit out of the profile. + + **Note:** See the Developer Onboarding Guide on the "Using AWS SSO for Local Development" page on Confluence to set up service profile selection on your local machine. + +3. Export the required environment variables: ```bash - export AWS_ACCESS_KEY_ID= - export AWS_SECRET_ACCESS_KEY= export AWS_DEFAULT_REGION=eu-west-2 export AWS_SECRET_NAME= + export AWS_ACCOUNT_NAME= export GITHUB_ORG=ONSDigital export GITHUB_APP_CLIENT_ID= - export AWS_ACCOUNT_NAME= ``` -3. Run the script. +4. Run the script. ```bash python3 src/main.py ``` +5. To exit the profile: + + ```bash + unset AWS_PROFILE + ``` + ### Running in a container 1. Build a Docker Image From a4d39a4d177d3760cee9e411fb7314311709e428 Mon Sep 17 00:00:00 2001 From: Hadi Qureshi Date: Thu, 21 May 2026 12:32:20 +0100 Subject: [PATCH 3/6] chore: update README --- README.md | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 3d3385c..0afa556 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ To run the Lambda function outside of a container, we need to execute the `handl export AWS_PROFILE=github-copilot-usage-lambda ``` - This allows you to assume the AWS IAM role for service, enabling the most secure development experience. This also means you will have limited permissions until you exit out of the profile. + This allows you to assume the AWS IAM role for the service, enabling the most secure development experience. This also means you will have limited permissions until you exit out of the profile. **Note:** See the Developer Onboarding Guide on the "Using AWS SSO for Local Development" page on Confluence to set up service profile selection on your local machine. @@ -149,14 +149,20 @@ To run the Lambda function outside of a container, we need to execute the `handl | --------------------------- | ------ | ------------ | -------------- | ----- | | copilot-usage-lambda-script | latest | 0bbe73d9256f | 11 seconds ago | 224MB | -3. Run the image locally mapping local host port (9000) to container port (8080) and passing in AWS credentials to download a .pem file from the AWS Secrets Manager to the running container. These credentials will also be used to upload and download `historic_usage_data.json` to and from S3. +3. Sign in with AWS SSO: - The credentials used in the below command are for a user in AWS that has permissions to retrieve secrets from AWS Secrets Manager and upload and download files from AWS S3. + ```bash + aws sso login + ``` + + **Note:** See the Developer Onboarding Guide on the "Using AWS SSO for Local Development" page on Confluence to set up service profile selection on your local machine. This is essential as the `~/.aws` directory is mounted to the container, so it can use the SSO session for AWS authentication. + +4. Run the image locally mapping local host port (9000) to container port (8080). ```bash docker run --platform linux/amd64 -p 9000:8080 \ - -e AWS_ACCESS_KEY_ID= \ - -e AWS_SECRET_ACCESS_KEY= \ + -v ~/.aws:/root/.aws \ + -e AWS_PROFILE=github-copilot-usage-lambda \ -e AWS_DEFAULT_REGION=eu-west-2 \ -e AWS_SECRET_NAME= \ -e GITHUB_ORG=ONSDigital \ @@ -167,7 +173,7 @@ To run the Lambda function outside of a container, we need to execute the `handl Once the container is running, a local endpoint is created at `localhost:9000/2015-03-31/functions/function/invocations`. -4. Post to the endpoint to trigger the function +5. Post to the endpoint to trigger the function ```bash curl "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}' @@ -175,7 +181,7 @@ To run the Lambda function outside of a container, we need to execute the `handl This should return a message if successful. -5. Once testing is finished, stop the running container +6. Once testing is finished, stop the running container To check the container is running From 6813d3e016c65eabc14903fff4966430a48da9e7 Mon Sep 17 00:00:00 2001 From: Hadi Qureshi Date: Thu, 21 May 2026 14:40:10 +0100 Subject: [PATCH 4/6] chore: update dependencies --- poetry.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/poetry.lock b/poetry.lock index 72c12de..d649bce 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. [[package]] name = "astroid" @@ -632,18 +632,18 @@ colorama = ">=0.4" [[package]] name = "idna" -version = "3.10" +version = "3.15" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" groups = ["main", "docs"] files = [ - {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, - {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, + {file = "idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8"}, + {file = "idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc"}, ] [package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] +all = ["mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] [[package]] name = "iniconfig" @@ -1478,10 +1478,10 @@ files = [ ] [package.dependencies] -botocore = ">=1.37.4,<2.0a0" +botocore = ">=1.37.4,<2.0a.0" [package.extras] -crt = ["botocore[crt] (>=1.37.4,<2.0a0)"] +crt = ["botocore[crt] (>=1.37.4,<2.0a.0)"] [[package]] name = "six" From 5e06f1bc5a719501f49ba840491178b1f1973bc1 Mon Sep 17 00:00:00 2001 From: Hadi Qureshi Date: Thu, 21 May 2026 14:43:38 +0100 Subject: [PATCH 5/6] chore: markdown linting --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0afa556..db78a4e 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ To run the Lambda function outside of a container, we need to execute the `handl **Note:** See the Developer Onboarding Guide on the "Using AWS SSO for Local Development" page on Confluence to set up service profile selection on your local machine. -3. Export the required environment variables: +1. Export the required environment variables: ```bash export AWS_DEFAULT_REGION=eu-west-2 @@ -117,13 +117,13 @@ To run the Lambda function outside of a container, we need to execute the `handl export GITHUB_APP_CLIENT_ID= ``` -4. Run the script. +2. Run the script. ```bash python3 src/main.py ``` -5. To exit the profile: +3. To exit the profile: ```bash unset AWS_PROFILE From 29363b82fbc50b657d2daa1d295da1edb26a6cd1 Mon Sep 17 00:00:00 2001 From: Hadi Qureshi Date: Thu, 21 May 2026 16:12:32 +0100 Subject: [PATCH 6/6] chore: resolve further markdown lint errors --- README.md | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index db78a4e..f6f85f8 100644 --- a/README.md +++ b/README.md @@ -97,37 +97,37 @@ To run the Lambda function outside of a container, we need to execute the `handl 2. Sign in with AWS SSO, and export the correct profile for this service: - ```bash - aws sso login + ```bash + aws sso login - export AWS_PROFILE=github-copilot-usage-lambda - ``` + export AWS_PROFILE=github-copilot-usage-lambda + ``` - This allows you to assume the AWS IAM role for the service, enabling the most secure development experience. This also means you will have limited permissions until you exit out of the profile. + This allows you to assume the AWS IAM role for the service, enabling the most secure development experience. This also means you will have limited permissions until you exit out of the profile. - **Note:** See the Developer Onboarding Guide on the "Using AWS SSO for Local Development" page on Confluence to set up service profile selection on your local machine. + **Note:** See the Developer Onboarding Guide on the "Using AWS SSO for Local Development" page on Confluence to set up service profile selection on your local machine. -1. Export the required environment variables: +3. Export the required environment variables: - ```bash - export AWS_DEFAULT_REGION=eu-west-2 - export AWS_SECRET_NAME= - export AWS_ACCOUNT_NAME= - export GITHUB_ORG=ONSDigital - export GITHUB_APP_CLIENT_ID= - ``` + ```bash + export AWS_DEFAULT_REGION=eu-west-2 + export AWS_SECRET_NAME= + export AWS_ACCOUNT_NAME= + export GITHUB_ORG=ONSDigital + export GITHUB_APP_CLIENT_ID= + ``` -2. Run the script. +4. Run the script. - ```bash - python3 src/main.py - ``` + ```bash + python3 src/main.py + ``` -3. To exit the profile: +5. To exit the profile: - ```bash - unset AWS_PROFILE - ``` + ```bash + unset AWS_PROFILE + ``` ### Running in a container