From bd105611e665ee325506e81d2490441cc09ae4ed Mon Sep 17 00:00:00 2001 From: Shweta <216860557+shweta-nhs@users.noreply.github.com> Date: Wed, 16 Jul 2025 13:44:39 +0100 Subject: [PATCH 1/3] ELI-331: Mask PII/GDPR info --- .../modules/api_gateway/cloudwatch.tf | 93 +++++++++++++++++++ .../services/eligibility_services.py | 5 + 2 files changed, 98 insertions(+) diff --git a/infrastructure/modules/api_gateway/cloudwatch.tf b/infrastructure/modules/api_gateway/cloudwatch.tf index 272a1c465..a124c8434 100644 --- a/infrastructure/modules/api_gateway/cloudwatch.tf +++ b/infrastructure/modules/api_gateway/cloudwatch.tf @@ -8,3 +8,96 @@ resource "aws_cloudwatch_log_group" "api_gateway" { prevent_destroy = false } } + +resource "aws_cloudwatch_log_data_protection_policy" "api_gateway_data_protection" { + log_group_name = aws_cloudwatch_log_group.api_gateway.name + policy_document = jsonencode({ + Name = "data-protection-policy" + Version = "2021-06-01" + Statement = [ + { + Sid = "MaskSensitiveData" + Effect = "Deny" + Principal = { "AWS" : "*" } + Action = "cloudwatch:PutLogEvents" + Resource = "*" + DataIdentifier = [ + "arn:aws:dataprotection::aws:data-identifier/DateOfBirth", + "arn:aws:dataprotection::aws:data-identifier/UkPostcode", + "arn:aws:dataprotection::aws:data-identifier/Custom:UkPostcodeSector", + "arn:aws:dataprotection::aws:data-identifier/Custom:GpPracticeCode", + "arn:aws:dataprotection::aws:data-identifier/Custom:13QFlag", + "arn:aws:dataprotection::aws:data-identifier/Custom:CareHomeFlag", + "arn:aws:dataprotection::aws:data-identifier/Custom:DEFlag", + "arn:aws:dataprotection::aws:data-identifier/Custom:RemovalReasonCode", + "arn:aws:dataprotection::aws:data-identifier/Custom:ValidDosesCount", + "arn:aws:dataprotection::aws:data-identifier/Custom:InvalidDosesCount", + "arn:aws:dataprotection::aws:data-identifier/Custom:LastSuccessfulDate", + "arn:aws:dataprotection::aws:data-identifier/Custom:LastValidDoseDate", + "arn:aws:dataprotection::aws:data-identifier/Custom:CohortLabel" + + ] + Operation = { + "cloudwatch:Mask" = {} + } + }, + ] + CustomDataIdentifier = [ + { + Name = "UkPostcodeSector" + Regex = "[A-Z]{1,2}[0-9R-9][0A-Z]? ?[0-9]" + Severity = "High" + }, + { + Name = "GpPracticeCode" + Regex = "GP_PRACTICE[\\s\\\"':=]*([A-Z][0-9]{5})" + Severity = "High" + }, + { + Name = "13QFlag" + Regex = "13Q_FLAG[\\s\\\"':=]*[YN]" + Severity = "High" + }, + { + Name = "CareHomeFlag" + Regex = "CARE_HOME_FLAG[\\s\\\"':=]*[YN]" + Severity = "High" + }, + { + Name = "DEFlag" + Regex = "DE_FLAG[\\s\\\"':=]*[YN]" + Severity = "High" + }, + { + Name = "RemovalReasonCode" + Regex = "REMOVAL_REASON_CODE[\\s\\\"':=]*([A-Z]{3})" + Severity = "High" + }, + { + Name = "ValidDosesCount" + Regex = "VALID_DOSES_COUNT[\\s\\\"':=]*([0-9]{1,2}|100)" + Severity = "High" + }, + { + Name = "InvalidDosesCount" + Regex = "INVALID_DOSES_COUNT[\\s\\\"':=]*([0-9]{1,2}|100)" + Severity = "High" + }, + { + Name = "LastSuccessfulDate" + Regex = "LAST_SUCCESSFUL_DATE[\\s\\\"':=]*([0-9]{8})" + Severity = "High" + }, + { + Name = "LastValidDoseDate" + Regex = "LAST_VALID_DOSE_DATE[\\s\\\"':=]*([0-9]{8})" + Severity = "High" + }, + { + Name = "CohortLabel" + Regex = "COHORT_LABEL[\\s\\\"':=]*([A-Za-z0-9_ -]{1,100})" + Severity = "High" + } + ] + }) +} diff --git a/src/eligibility_signposting_api/services/eligibility_services.py b/src/eligibility_signposting_api/services/eligibility_services.py index 48586290b..478156ef0 100644 --- a/src/eligibility_signposting_api/services/eligibility_services.py +++ b/src/eligibility_signposting_api/services/eligibility_services.py @@ -51,6 +51,11 @@ def get_eligibility_status( "nhs_number": nhs_number, }, ) + + logger.info("Test data masking person data: %r", person_data[0]) + logger.info("Test data masking campaign config data: %r", + campaign_configs[0].model_dump(by_alias=True)) + except NotFoundError as e: raise UnknownPersonError from e else: From f86605b6d45f8076e13567d4471f979aeac3f799 Mon Sep 17 00:00:00 2001 From: Shweta <216860557+shweta-nhs@users.noreply.github.com> Date: Wed, 16 Jul 2025 13:58:11 +0100 Subject: [PATCH 2/3] ELI-331: Fix formatting --- .../services/eligibility_services.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/eligibility_signposting_api/services/eligibility_services.py b/src/eligibility_signposting_api/services/eligibility_services.py index 478156ef0..762d991f7 100644 --- a/src/eligibility_signposting_api/services/eligibility_services.py +++ b/src/eligibility_signposting_api/services/eligibility_services.py @@ -53,8 +53,7 @@ def get_eligibility_status( ) logger.info("Test data masking person data: %r", person_data[0]) - logger.info("Test data masking campaign config data: %r", - campaign_configs[0].model_dump(by_alias=True)) + logger.info("Test data masking campaign config data: %r", campaign_configs[0].model_dump(by_alias=True)) except NotFoundError as e: raise UnknownPersonError from e From fe9705fcd54d45142514bc89d5ce83f8255698a0 Mon Sep 17 00:00:00 2001 From: Shweta <216860557+shweta-nhs@users.noreply.github.com> Date: Wed, 16 Jul 2025 14:11:16 +0100 Subject: [PATCH 3/3] ELI-331: Fix format and lint --- .../services/eligibility_services.py | 7 +++++-- tests/integration/lambda/test_app_running_as_lambda.py | 4 ---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/eligibility_signposting_api/services/eligibility_services.py b/src/eligibility_signposting_api/services/eligibility_services.py index 762d991f7..e8db56d69 100644 --- a/src/eligibility_signposting_api/services/eligibility_services.py +++ b/src/eligibility_signposting_api/services/eligibility_services.py @@ -52,8 +52,11 @@ def get_eligibility_status( }, ) - logger.info("Test data masking person data: %r", person_data[0]) - logger.info("Test data masking campaign config data: %r", campaign_configs[0].model_dump(by_alias=True)) + if person_data and person_data[0] and campaign_configs and campaign_configs[0]: + logger.info("Test data masking person data: %r", person_data[0]) + logger.info( + "Test data masking campaign config data: %r", campaign_configs[0].model_dump(by_alias=True) + ) except NotFoundError as e: raise UnknownPersonError from e diff --git a/tests/integration/lambda/test_app_running_as_lambda.py b/tests/integration/lambda/test_app_running_as_lambda.py index b157fe44f..3328a8939 100644 --- a/tests/integration/lambda/test_app_running_as_lambda.py +++ b/tests/integration/lambda/test_app_running_as_lambda.py @@ -1,4 +1,3 @@ -import base64 import json import logging from http import HTTPStatus @@ -69,7 +68,6 @@ def test_install_and_call_lambda_flask( Payload=json.dumps(request_payload), LogType="Tail", ) - log_output = base64.b64decode(response["LogResult"]).decode("utf-8") # Then assert_that(response, has_entries(StatusCode=HTTPStatus.OK)) @@ -80,8 +78,6 @@ def test_install_and_call_lambda_flask( has_entries(statusCode=HTTPStatus.OK, body=is_json_that(has_key("processedSuggestions"))), ) - assert_that(log_output, contains_string("person_data")) - def test_install_and_call_flask_lambda_over_http( persisted_person: NHSNumber,