From e6297e1b05669fad96c1f7213f0c6be075e0840f Mon Sep 17 00:00:00 2001 From: Sam Ainsworth Date: Fri, 6 Oct 2023 11:31:13 +0100 Subject: [PATCH 1/3] UML-3109 Remove build_admin feature flag from TF --- terraform/environment/admin_ecs.tf | 53 ++++++++++---- terraform/environment/admin_load_balancer.tf | 77 ++++++++++++++------ terraform/environment/cloudwatch_alarms.tf | 8 +- terraform/environment/cognito_client.tf | 10 ++- terraform/environment/config_file.tf | 2 +- terraform/environment/dns.tf | 10 ++- terraform/environment/locals.tf | 1 - terraform/environment/terraform.tf | 2 +- terraform/environment/terraform.tfvars.json | 4 - 9 files changed, 117 insertions(+), 50 deletions(-) diff --git a/terraform/environment/admin_ecs.tf b/terraform/environment/admin_ecs.tf index 47973e6587..5ef197d8d0 100644 --- a/terraform/environment/admin_ecs.tf +++ b/terraform/environment/admin_ecs.tf @@ -2,21 +2,20 @@ // admin ECS Service level config resource "aws_ecs_service" "admin" { - count = local.environment.build_admin ? 1 : 0 name = "admin-service" cluster = aws_ecs_cluster.use-an-lpa.id - task_definition = aws_ecs_task_definition.admin[0].arn + task_definition = aws_ecs_task_definition.admin.arn desired_count = 1 platform_version = "1.4.0" network_configuration { - security_groups = [aws_security_group.admin_ecs_service[0].id] + security_groups = [aws_security_group.admin_ecs_service.id] subnets = data.aws_subnets.private.ids assign_public_ip = false } load_balancer { - target_group_arn = aws_lb_target_group.admin[0].arn + target_group_arn = aws_lb_target_group.admin.arn container_name = "app" container_port = 80 } @@ -44,11 +43,16 @@ resource "aws_ecs_service" "admin" { depends_on = [aws_lb.admin] } + +moved { + from = aws_ecs_service.admin[0] + to = aws_ecs_service.admin +} + //---------------------------------- // The service's Security Groups resource "aws_security_group" "admin_ecs_service" { - count = local.environment.build_admin ? 1 : 0 name_prefix = "${local.environment_name}-admin-ecs-service" description = "Admin service security group" vpc_id = data.aws_vpc.default.id @@ -57,41 +61,53 @@ resource "aws_security_group" "admin_ecs_service" { } } +moved { + from = aws_security_group.admin_ecs_service[0] + to = aws_security_group.admin_ecs_service +} + // 80 in from the ELB resource "aws_security_group_rule" "admin_ecs_service_ingress" { - count = local.environment.build_admin ? 1 : 0 description = "Allow Port 80 ingress from the applciation load balancer" type = "ingress" from_port = 80 to_port = 80 protocol = "tcp" - security_group_id = aws_security_group.admin_ecs_service[0].id - source_security_group_id = aws_security_group.admin_loadbalancer[0].id + security_group_id = aws_security_group.admin_ecs_service.id + source_security_group_id = aws_security_group.admin_loadbalancer.id lifecycle { create_before_destroy = true } } +moved { + from = aws_security_group_rule.admin_ecs_service_ingress[0] + to = aws_security_group_rule.admin_ecs_service_ingress +} + // Anything out resource "aws_security_group_rule" "admin_ecs_service_egress" { - count = local.environment.build_admin ? 1 : 0 description = "Allow any egress from Use service" type = "egress" from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] #tfsec:ignore:AWS007 - open egress for ECR access - security_group_id = aws_security_group.admin_ecs_service[0].id + security_group_id = aws_security_group.admin_ecs_service.id lifecycle { create_before_destroy = true } } +moved { + from = aws_security_group_rule.admin_ecs_service_egress[0] + to = aws_security_group_rule.admin_ecs_service_egress +} + //-------------------------------------- // admin ECS Service Task level config resource "aws_ecs_task_definition" "admin" { - count = local.environment.build_admin ? 1 : 0 family = "${local.environment_name}-admin" requires_compatibilities = ["FARGATE"] network_mode = "awsvpc" @@ -102,13 +118,22 @@ resource "aws_ecs_task_definition" "admin" { execution_role_arn = module.iam.ecs_execution_role.arn } +moved { + from = aws_ecs_task_definition.admin[0] + to = aws_ecs_task_definition.admin +} + resource "aws_iam_role_policy" "admin_permissions_role" { - count = local.environment.build_admin ? 1 : 0 name = "${local.environment_name}-${local.policy_region_prefix}-adminApplicationPermissions" policy = data.aws_iam_policy_document.admin_permissions_role.json role = module.iam.ecs_task_roles.admin_task_role.id } +moved { + from = aws_iam_role_policy.admin_permissions_role[0] + to = aws_iam_role_policy.admin_permissions_role +} + /* Defines permissions that the application running within the task has. */ @@ -235,7 +260,7 @@ locals { }, { name = "ADMIN_CLIENT_ID", - value = "${aws_cognito_user_pool_client.use_a_lasting_power_of_attorney_admin[0].id}" + value = "${aws_cognito_user_pool_client.use_a_lasting_power_of_attorney_admin.id}" }, { name = "LPA_CODES_API_ENDPOINT", @@ -248,7 +273,7 @@ locals { } locals { - admin_domain = local.environment.build_admin ? "https://${aws_route53_record.admin_use_my_lpa[0].fqdn}" : "Not deployed" + admin_domain = "https://${aws_route53_record.admin_use_my_lpa.fqdn}" } output "admin_domain" { diff --git a/terraform/environment/admin_load_balancer.tf b/terraform/environment/admin_load_balancer.tf index f8c1e3f95e..b3d786d2d9 100644 --- a/terraform/environment/admin_load_balancer.tf +++ b/terraform/environment/admin_load_balancer.tf @@ -1,5 +1,4 @@ resource "aws_lb_target_group" "admin" { - count = local.environment.build_admin ? 1 : 0 name = "${local.environment_name}-admin" port = 80 protocol = "HTTP" @@ -12,11 +11,15 @@ resource "aws_lb_target_group" "admin" { path = "/helloworld" } - depends_on = [aws_lb.admin[0]] + depends_on = [aws_lb.admin] +} + +moved { + from = aws_lb_target_group.admin[0] + to = aws_lb_target_group.admin } resource "aws_lb" "admin" { - count = local.environment.build_admin ? 1 : 0 name = "${local.environment_name}-admin" internal = false #tfsec:ignore:AWS005 - public alb load_balancer_type = "application" @@ -25,7 +28,7 @@ resource "aws_lb" "admin" { enable_deletion_protection = local.environment.load_balancer_deletion_protection_enabled security_groups = [ - aws_security_group.admin_loadbalancer[0].id, + aws_security_group.admin_loadbalancer.id, ] access_logs { @@ -35,9 +38,13 @@ resource "aws_lb" "admin" { } } +moved { + from = aws_lb.admin[0] + to = aws_lb.admin +} + resource "aws_lb_listener" "admin_loadbalancer_http_redirect" { - count = local.environment.build_admin ? 1 : 0 - load_balancer_arn = aws_lb.admin[0].arn + load_balancer_arn = aws_lb.admin.arn port = "80" protocol = "HTTP" @@ -52,9 +59,13 @@ resource "aws_lb_listener" "admin_loadbalancer_http_redirect" { } } +moved { + from = aws_lb_listener.admin_loadbalancer_http_redirect[0] + to = aws_lb_listener.admin_loadbalancer_http_redirect +} + resource "aws_lb_listener" "admin_loadbalancer" { - count = local.environment.build_admin ? 1 : 0 - load_balancer_arn = aws_lb.admin[0].arn + load_balancer_arn = aws_lb.admin.arn port = "443" protocol = "HTTPS" ssl_policy = "ELBSecurityPolicy-FS-1-2-2019-08" @@ -66,33 +77,40 @@ resource "aws_lb_listener" "admin_loadbalancer" { authenticate_oidc { authentication_request_extra_params = {} authorization_endpoint = "${local.admin_cognito_user_pool_domain_name}/oauth2/authorize" - client_id = aws_cognito_user_pool_client.use_a_lasting_power_of_attorney_admin[0].id - client_secret = aws_cognito_user_pool_client.use_a_lasting_power_of_attorney_admin[0].client_secret + client_id = aws_cognito_user_pool_client.use_a_lasting_power_of_attorney_admin.id + client_secret = aws_cognito_user_pool_client.use_a_lasting_power_of_attorney_admin.client_secret issuer = "https://cognito-idp.eu-west-1.amazonaws.com/${local.admin_cognito_user_pool_id}" on_unauthenticated_request = "authenticate" scope = "openid" session_cookie_name = "AWSELBAuthSessionCookie" - session_timeout = aws_cognito_user_pool_client.use_a_lasting_power_of_attorney_admin[0].id_token_validity + session_timeout = aws_cognito_user_pool_client.use_a_lasting_power_of_attorney_admin.id_token_validity token_endpoint = "${local.admin_cognito_user_pool_domain_name}/oauth2/token" user_info_endpoint = "${local.admin_cognito_user_pool_domain_name}/oauth2/userInfo" } } default_action { - target_group_arn = aws_lb_target_group.admin[0].arn + target_group_arn = aws_lb_target_group.admin.arn type = "forward" } } +moved { + from = aws_lb_listener.admin_loadbalancer[0] + to = aws_lb_listener.admin_loadbalancer +} + resource "aws_lb_listener_certificate" "admin_loadbalancer_live_service_certificate" { - count = local.environment.build_admin ? 1 : 0 - listener_arn = aws_lb_listener.admin_loadbalancer[0].arn + listener_arn = aws_lb_listener.admin_loadbalancer.arn certificate_arn = data.aws_acm_certificate.public_facing_certificate_use.arn } +moved { + from = aws_lb_listener_certificate.admin_loadbalancer_live_service_certificate[0] + to = aws_lb_listener_certificate.admin_loadbalancer_live_service_certificate +} resource "aws_security_group" "admin_loadbalancer" { - count = local.environment.build_admin ? 1 : 0 name_prefix = "${local.environment_name}-admin-loadbalancer" description = "Admin service application load balancer" vpc_id = data.aws_vpc.default.id @@ -101,35 +119,52 @@ resource "aws_security_group" "admin_loadbalancer" { } } +moved { + from = aws_security_group.admin_loadbalancer[0] + to = aws_security_group.admin_loadbalancer +} + resource "aws_security_group_rule" "admin_loadbalancer_port_80_redirect_ingress" { - count = local.environment.build_admin ? 1 : 0 description = "Port 80 ingress for redirection to port 443" type = "ingress" from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = module.allow_list.moj_sites - security_group_id = aws_security_group.admin_loadbalancer[0].id + security_group_id = aws_security_group.admin_loadbalancer.id +} + +moved { + from = aws_security_group_rule.admin_loadbalancer_port_80_redirect_ingress[0] + to = aws_security_group_rule.admin_loadbalancer_port_80_redirect_ingress } resource "aws_security_group_rule" "admin_loadbalancer_ingress" { - count = local.environment.build_admin ? 1 : 0 description = "Port 443 ingress from the allow list to the application load balancer" type = "ingress" from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = module.allow_list.moj_sites - security_group_id = aws_security_group.admin_loadbalancer[0].id + security_group_id = aws_security_group.admin_loadbalancer.id +} + +moved { + from = aws_security_group_rule.admin_loadbalancer_ingress[0] + to = aws_security_group_rule.admin_loadbalancer_ingress } resource "aws_security_group_rule" "admin_loadbalancer_egress" { - count = local.environment.build_admin ? 1 : 0 description = "Allow any egress from Use service load balancer" type = "egress" from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] #tfsec:ignore:AWS007 - open egress for load balancers - security_group_id = aws_security_group.admin_loadbalancer[0].id + security_group_id = aws_security_group.admin_loadbalancer.id +} + +moved { + from = aws_security_group_rule.admin_loadbalancer_egress[0] + to = aws_security_group_rule.admin_loadbalancer_egress } diff --git a/terraform/environment/cloudwatch_alarms.tf b/terraform/environment/cloudwatch_alarms.tf index 9a9ceacc34..6924b38632 100644 --- a/terraform/environment/cloudwatch_alarms.tf +++ b/terraform/environment/cloudwatch_alarms.tf @@ -108,7 +108,6 @@ resource "aws_cloudwatch_metric_alarm" "viewer_ddos_attack_external" { } resource "aws_cloudwatch_metric_alarm" "admin_ddos_attack_external" { - count = local.environment.build_admin ? 1 : 0 alarm_name = "${local.environment_name}_AdminDDoSDetected" comparison_operator = "GreaterThanThreshold" evaluation_periods = "3" @@ -121,6 +120,11 @@ resource "aws_cloudwatch_metric_alarm" "admin_ddos_attack_external" { treat_missing_data = "notBreaching" alarm_actions = [aws_sns_topic.cloudwatch_to_pagerduty.arn] dimensions = { - ResourceArn = aws_lb.admin[0].arn + ResourceArn = aws_lb.admin.arn } } + +moved { + from = aws_cloudwatch_metric_alarm.admin_ddos_attack_external[0] + to = aws_cloudwatch_metric_alarm.admin_ddos_attack_external +} diff --git a/terraform/environment/cognito_client.tf b/terraform/environment/cognito_client.tf index 15e4d61634..092c5a5b78 100644 --- a/terraform/environment/cognito_client.tf +++ b/terraform/environment/cognito_client.tf @@ -14,7 +14,6 @@ locals { } resource "aws_cognito_user_pool_client" "use_a_lasting_power_of_attorney_admin" { - count = local.environment.build_admin ? 1 : 0 provider = aws.identity name = "${local.environment_name}-admin-auth" user_pool_id = local.admin_cognito_user_pool_id @@ -42,6 +41,11 @@ resource "aws_cognito_user_pool_client" "use_a_lasting_power_of_attorney_admin" read_attributes = [] write_attributes = [] - callback_urls = ["https://${aws_route53_record.admin_use_my_lpa[0].fqdn}/oauth2/idpresponse"] - logout_urls = ["https://${aws_route53_record.admin_use_my_lpa[0].fqdn}/"] + callback_urls = ["https://${aws_route53_record.admin_use_my_lpa.fqdn}/oauth2/idpresponse"] + logout_urls = ["https://${aws_route53_record.admin_use_my_lpa.fqdn}/"] +} + +moved { + from = aws_cognito_user_pool_client.use_a_lasting_power_of_attorney_admin[0] + to = aws_cognito_user_pool_client.use_a_lasting_power_of_attorney_admin } diff --git a/terraform/environment/config_file.tf b/terraform/environment/config_file.tf index 375ded0a4a..60dd440507 100644 --- a/terraform/environment/config_file.tf +++ b/terraform/environment/config_file.tf @@ -14,7 +14,7 @@ locals { stats_table = aws_dynamodb_table.stats_table.name actor_fqdn = aws_route53_record.actor-use-my-lpa.fqdn viewer_fqdn = aws_route53_record.viewer-use-my-lpa.fqdn - admin_fqdn = local.environment.build_admin ? aws_route53_record.admin_use_my_lpa[0].fqdn : "" + admin_fqdn = aws_route53_record.admin_use_my_lpa.fqdn public_facing_use_fqdn = aws_route53_record.public_facing_use_lasting_power_of_attorney.fqdn public_facing_view_fqdn = aws_route53_record.public_facing_view_lasting_power_of_attorney.fqdn viewer_load_balancer_security_group_name = aws_security_group.viewer_loadbalancer.name diff --git a/terraform/environment/dns.tf b/terraform/environment/dns.tf index a07e3b1362..1700e7c43d 100644 --- a/terraform/environment/dns.tf +++ b/terraform/environment/dns.tf @@ -107,7 +107,6 @@ resource "aws_route53_record" "actor-use-my-lpa" { resource "aws_route53_record" "admin_use_my_lpa" { # admin.lastingpowerofattorney.opg.service.justice.gov.uk - count = local.environment.build_admin ? 1 : 0 provider = aws.management zone_id = data.aws_route53_zone.opg_service_justice_gov_uk.zone_id name = "${local.dns_namespace_env}admin.lastingpowerofattorney" @@ -115,11 +114,16 @@ resource "aws_route53_record" "admin_use_my_lpa" { alias { evaluate_target_health = false - name = aws_lb.admin[0].dns_name - zone_id = aws_lb.admin[0].zone_id + name = aws_lb.admin.dns_name + zone_id = aws_lb.admin.zone_id } lifecycle { create_before_destroy = true } } + +moved { + from = aws_route53_record.admin_use_my_lpa[0] + to = aws_route53_record.admin_use_my_lpa +} diff --git a/terraform/environment/locals.tf b/terraform/environment/locals.tf index 2ddd805730..17ad3e0399 100644 --- a/terraform/environment/locals.tf +++ b/terraform/environment/locals.tf @@ -52,7 +52,6 @@ variable "environments" { maximum = number }) }) - build_admin = bool cookie_expires_use = number cookie_expires_view = number google_analytics_id_use = string diff --git a/terraform/environment/terraform.tf b/terraform/environment/terraform.tf index 9f93f9f122..bcf418ccf2 100644 --- a/terraform/environment/terraform.tf +++ b/terraform/environment/terraform.tf @@ -1,5 +1,5 @@ terraform { - required_version = "<= 1.5.6" + required_version = "<= 1.5.7" backend "s3" { bucket = "opg.terraform.state" diff --git a/terraform/environment/terraform.tfvars.json b/terraform/environment/terraform.tfvars.json index a3b3c68ac2..c27b26778e 100644 --- a/terraform/environment/terraform.tfvars.json +++ b/terraform/environment/terraform.tfvars.json @@ -21,7 +21,6 @@ "minimum": 2 } }, - "build_admin": true, "cookie_expires_use": 1440, "cookie_expires_view": 1440, "google_analytics_id_use": "G-JQHJE49CBB", @@ -97,7 +96,6 @@ "minimum": 2 } }, - "build_admin": true, "cookie_expires_use": 1440, "cookie_expires_view": 1440, "google_analytics_id_use": "G-JQHJE49CBB", @@ -173,7 +171,6 @@ "minimum": 2 } }, - "build_admin": true, "cookie_expires_use": 1440, "cookie_expires_view": 1440, "google_analytics_id_use": "", @@ -249,7 +246,6 @@ "minimum": 2 } }, - "build_admin": true, "cookie_expires_use": 1440, "cookie_expires_view": 1440, "google_analytics_id_use": "G-TX93T4G7SZ", From dbc86639f6281ba4b61356438c7d665cdcbbb73c Mon Sep 17 00:00:00 2001 From: Sam Ainsworth Date: Mon, 9 Oct 2023 15:19:58 +0100 Subject: [PATCH 2/3] UML-3112 Increase actor and api min containers --- terraform/environment/terraform.tfvars.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terraform/environment/terraform.tfvars.json b/terraform/environment/terraform.tfvars.json index c27b26778e..a4aab5d701 100644 --- a/terraform/environment/terraform.tfvars.json +++ b/terraform/environment/terraform.tfvars.json @@ -231,7 +231,7 @@ "autoscaling": { "api": { "maximum": 10, - "minimum": 2 + "minimum": 3 }, "pdf": { "maximum": 10, @@ -239,7 +239,7 @@ }, "use": { "maximum": 10, - "minimum": 2 + "minimum": 3 }, "view": { "maximum": 10, From 960e3a898f6a9cc896f8724c3a5ba309249425cb Mon Sep 17 00:00:00 2001 From: Nick Davis <62664046+nickdavis2001@users.noreply.github.com> Date: Mon, 9 Oct 2023 16:40:26 +0100 Subject: [PATCH 3/3] Uml 3082 mock onelogin is now moved to seperate repo (#2361) * use one-login mock from ecr * remove onelogin mock as that is now in a seperate repo * add ECR LOGIN --- Makefile | 1 + docker-compose.dependencies.yml | 5 +- mock-integrations/one-login/Dockerfile | 11 - mock-integrations/one-login/go.mod | 8 - mock-integrations/one-login/go.sum | 8 - mock-integrations/one-login/main.go | 266 ------------------------- 6 files changed, 3 insertions(+), 296 deletions(-) delete mode 100644 mock-integrations/one-login/Dockerfile delete mode 100644 mock-integrations/one-login/go.mod delete mode 100644 mock-integrations/one-login/go.sum delete mode 100644 mock-integrations/one-login/main.go diff --git a/Makefile b/Makefile index bd28f408ec..7c72bdf46a 100644 --- a/Makefile +++ b/Makefile @@ -80,6 +80,7 @@ logs: .PHONY: logs up_dependencies: $(SM_PATH)private_key.pem $(SM_PATH)public_key.pem + $(ECR_LOGIN) $(COMPOSE) up -d --remove-orphans dynamodb-local codes-gateway redis kms mock-one-login localstack .PHONY: up_dependencies diff --git a/docker-compose.dependencies.yml b/docker-compose.dependencies.yml index 82d75fdbae..50878a0234 100644 --- a/docker-compose.dependencies.yml +++ b/docker-compose.dependencies.yml @@ -102,9 +102,8 @@ services: mock-one-login: container_name: mock-one-login - image: mock-one-login - build: - context: mock-integrations/one-login + image: 311462405659.dkr.ecr.eu-west-1.amazonaws.com/use_an_lpa/mock_onelogin_app + platform: linux/amd64 ports: - "4013:8080" environment: diff --git a/mock-integrations/one-login/Dockerfile b/mock-integrations/one-login/Dockerfile deleted file mode 100644 index 2cd6dbd512..0000000000 --- a/mock-integrations/one-login/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM golang:1.20 as build-env - -WORKDIR /app - -COPY . . - -RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -o onelogin main.go - -CMD [ "./onelogin" ] - -EXPOSE 8080 diff --git a/mock-integrations/one-login/go.mod b/mock-integrations/one-login/go.mod deleted file mode 100644 index 6b6eea71ab..0000000000 --- a/mock-integrations/one-login/go.mod +++ /dev/null @@ -1,8 +0,0 @@ -module github.com/ministryofjustice/opg-use-an-lpa/mock-integrations/one-login - -go 1.20 - -require ( - github.com/golang-jwt/jwt/v4 v4.5.0 - github.com/ministryofjustice/opg-go-common v0.0.0-20220816144329-763497f29f90 -) diff --git a/mock-integrations/one-login/go.sum b/mock-integrations/one-login/go.sum deleted file mode 100644 index 41c7d22043..0000000000 --- a/mock-integrations/one-login/go.sum +++ /dev/null @@ -1,8 +0,0 @@ -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/ministryofjustice/opg-go-common v0.0.0-20220816144329-763497f29f90 h1:mxTHIeCYV7LDZPN7C44wwLlBTUsgQ0G8FQprsrsKXaA= -github.com/ministryofjustice/opg-go-common v0.0.0-20220816144329-763497f29f90/go.mod h1:1RmCNi6dkAv8umAgNHp8RkuBoSKLlxp1UtfsGYH7ufc= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= diff --git a/mock-integrations/one-login/main.go b/mock-integrations/one-login/main.go deleted file mode 100644 index ef79bea560..0000000000 --- a/mock-integrations/one-login/main.go +++ /dev/null @@ -1,266 +0,0 @@ -package main - -import ( - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "encoding/base64" - "encoding/json" - "flag" - "fmt" - "log" - "net/http" - "net/url" - "time" - - "github.com/golang-jwt/jwt/v4" - "github.com/ministryofjustice/opg-go-common/env" -) - -var ( - port = env.Get("PORT", "8080") - publicURL = env.Get("PUBLIC_URL", "http://localhost:8080") - internalURL = env.Get("INTERNAL_URL", "http://mock-onelogin:8080") - clientId = env.Get("CLIENT_ID", "theClientId") - serviceRedirectUrl = env.Get("REDIRECT_URL", "http://localhost:5050/auth/redirect") - - nonce string - returnIdentity = false - signingKid = "my-kid" - signingKey, _ = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - sub = "urn:fdc:mock-one-login:2023:T25lIExvZ2luICsgUEhQIG1ha2VzIGZvciBzYWQgdGltZQ==" -) - -type OpenIdConfig struct { - AuthorizationEndpoint string `json:"authorization_endpoint"` - Issuer string `json:"issuer"` - TokenEndpoint string `json:"token_endpoint"` - UserinfoEndpoint string `json:"userinfo_endpoint"` - JwksURI string `json:"jwks_uri"` -} - -type TokenResponse struct { - AccessToken string `json:"access_token"` - TokenType string `json:"token_type"` - IDToken string `json:"id_token"` -} - -type UserInfoResponse struct { - Sub string `json:"sub"` - Email string `json:"email"` - EmailVerified bool `json:"email_verified"` - Phone string `json:"phone"` - PhoneVerified bool `json:"phone_verified"` - UpdatedAt int `json:"updated_at"` - CoreIdentityJWT string `json:"https://vocab.account.gov.uk/v1/coreIdentityJWT,omitempty"` -} - -const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - -func stringWithCharset(length int, charset string) string { - bytes := make([]byte, length) - _, err := rand.Read(bytes) - if err != nil { - panic(err) - } - for i, b := range bytes { - bytes[i] = charset[b%byte(len(charset))] - } - return string(bytes) -} - -func randomString(length int) string { - return stringWithCharset(length, charset) -} - -func createSignedToken(clientId, issuer string) (string, error) { - t := jwt.New(jwt.SigningMethodES256) - - t.Header["kid"] = signingKid - - t.Claims = jwt.MapClaims{ - "sub": sub, - "iss": issuer, - "nonce": nonce, - "aud": clientId, - "exp": time.Now().Add(time.Minute * 5).Unix(), - "iat": time.Now().Unix(), - } - - return t.SignedString(signingKey) -} - -func openIDConfig(c OpenIdConfig) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(c) - } -} - -func jwks() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - publicKey := signingKey.PublicKey - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(map[string]interface{}{ - "keys": []map[string]interface{}{ - { - "kty": "EC", - "use": "sig", - "crv": "P-256", - "kid": signingKid, - "x": base64.URLEncoding.EncodeToString(publicKey.X.Bytes()), - "y": base64.URLEncoding.EncodeToString(publicKey.Y.Bytes()), - "alg": "ES256", - }, - }, - }) - } -} - -func token(clientId, issuer string) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - t, err := createSignedToken(clientId, issuer) - if err != nil { - log.Fatalf("Error creating JWT: %s", err) - } - - json.NewEncoder(w).Encode(TokenResponse{ - AccessToken: "access-token-value", - TokenType: "Bearer", - IDToken: t, - }) - } -} - -func authorize() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - log.Println("/authorize") - - nonce = r.FormValue("nonce") - - redirectUri := r.FormValue("redirect_uri") - if redirectUri == "" { - log.Fatal("Required query param 'redirect_uri' missing from request") - } - - if redirectUri != serviceRedirectUrl { - log.Fatalf("redirect_uri does not match pre-defined redirect URL (in RL this is set with GDS at a service level). Got %s, want %s", redirectUri, serviceRedirectUrl) - } - - u, parseErr := url.Parse(redirectUri) - if parseErr != nil { - log.Fatalf("Error parsing redirect_uri: %s", parseErr) - } - - q := u.Query() - - code := randomString(10) - q.Set("code", code) - q.Set("state", r.FormValue("state")) - - if r.FormValue("vtr") == "[Cl.Cm.P2]" && r.FormValue("claims") == `{"userinfo":{"https://vocab.account.gov.uk/v1/coreIdentityJWT": null}}` { - returnIdentity = true - } - - u.RawQuery = q.Encode() - - log.Printf("Redirecting to %s", u.String()) - - http.Redirect(w, r, u.String(), 302) - } -} - -func userInfo(privateKey *ecdsa.PrivateKey) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - userInfo := UserInfoResponse{ - Sub: sub, - Email: "opg-use-an-lpa+test-user@digital.justice.gov.uk", - EmailVerified: true, - Phone: "01406946277", - PhoneVerified: true, - UpdatedAt: 1311280970, - } - - if returnIdentity { - userInfo.CoreIdentityJWT, _ = jwt.NewWithClaims(jwt.SigningMethodES256, jwt.MapClaims{ - "iat": time.Now().Add(-time.Minute).Unix(), - "vc": map[string]any{ - "type": []string{}, - "credentialSubject": map[string]any{ - "name": []map[string]any{ - { - "validFrom": "2000-01-01", - "nameParts": []map[string]any{ - {"type": "GivenName", "value": "John"}, - {"type": "FamilyName", "value": "Doe"}, - }, - }, - }, - "birthDate": []map[string]any{ - { - "value": "1970-01-02", - }, - }, - }, - }, - }).SignedString(privateKey) - } - - json.NewEncoder(w).Encode(userInfo) - } -} - -func logout() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - log.Println("/logout was called") - postLogoutRedirectUri := r.FormValue("post_logout_redirect_uri") - - if postLogoutRedirectUri == "" { - log.Fatal("Required query param 'post_logout_redirect_uri' missing from request") - } - - u, parseErr := url.Parse(postLogoutRedirectUri) - if parseErr != nil { - log.Fatalf("Error parsing redirect_uri: %s", parseErr) - } - - log.Printf("Redirecting to %s", u.String()) - http.Redirect(w, r, u.String(), 302) - } -} - -func main() { - flag.Parse() - - c := OpenIdConfig{ - Issuer: publicURL, - AuthorizationEndpoint: publicURL + "/authorize", - TokenEndpoint: internalURL + "/token", - UserinfoEndpoint: internalURL + "/userinfo", - JwksURI: internalURL + "/.well-known/jwks", - } - - privateKeyBytes, _ := base64.StdEncoding.DecodeString("LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSVBheDJBYW92aXlQWDF3cndmS2FWckxEOHdQbkpJcUlicTMzZm8rWHdBZDdvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFSlEyVmtpZWtzNW9rSTIxY1Jma0FhOXVxN0t4TTZtMmpaWUJ4cHJsVVdCWkNFZnhxMjdwVQp0Qzd5aXplVlRiZUVqUnlJaStYalhPQjFBbDhPbHFtaXJnPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=") - privateKey, _ := jwt.ParseECPrivateKeyFromPEM(privateKeyBytes) - - http.HandleFunc("/.well-known/openid-configuration", openIDConfig(c)) - http.HandleFunc("/.well-known/jwks", jwks()) - http.HandleFunc("/authorize", authorize()) - http.HandleFunc("/token", token(clientId, c.Issuer)) - http.HandleFunc("/userinfo", userInfo(privateKey)) - http.HandleFunc("/logout", logout()) - - log.Println("GOV UK Sign in mock initialized") - - if err := http.ListenAndServe(fmt.Sprintf(":%s", port), logRoute(http.DefaultServeMux)); err != nil { - panic(err) - } -} - -func logRoute(h http.Handler) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - log.Println(r.Method, r.URL.Path) - h.ServeHTTP(w, r) - } -}