From a41cbb3958d8526a10f4c4b9991eaf9dcca4a378 Mon Sep 17 00:00:00 2001 From: Nicholas Hulston Date: Tue, 11 Mar 2025 16:17:19 -0400 Subject: [PATCH 01/11] use fips endpoints in govcloud regions --- datadog_lambda/api.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/datadog_lambda/api.py b/datadog_lambda/api.py index a114fe8f..4a4408a3 100644 --- a/datadog_lambda/api.py +++ b/datadog_lambda/api.py @@ -64,16 +64,36 @@ def get_api_key() -> str: DD_KMS_API_KEY = os.environ.get("DD_KMS_API_KEY", "") DD_API_KEY = os.environ.get("DD_API_KEY", os.environ.get("DATADOG_API_KEY", "")) + REGION = os.environ.get("AWS_REGION", "") + is_gov_region = REGION.startswith("us-gov-") + if DD_API_KEY_SECRET_ARN: - api_key = boto3.client("secretsmanager").get_secret_value( + # Secrets manager endpoints: https://docs.aws.amazon.com/general/latest/gr/asm.html + fips_endpoint = f"https://secretsmanager-fips.{REGION}.amazonaws.com" if is_gov_region else None + secrets_manager_client = boto3.client( + "secretsmanager", + endpoint_url=fips_endpoint + ) + api_key = secrets_manager_client.get_secret_value( SecretId=DD_API_KEY_SECRET_ARN )["SecretString"] elif DD_API_KEY_SSM_NAME: - api_key = boto3.client("ssm").get_parameter( + # SSM endpoints: https://docs.aws.amazon.com/general/latest/gr/ssm.html + fips_endpoint = f"https://ssm-fips.{REGION}.amazonaws.com" if is_gov_region else None + ssm_client = boto3.client( + "ssm", + endpoint_url=fips_endpoint + ) + api_key = ssm_client.get_parameter( Name=DD_API_KEY_SSM_NAME, WithDecryption=True )["Parameter"]["Value"] elif DD_KMS_API_KEY: - kms_client = boto3.client("kms") + # KMS endpoints: https://docs.aws.amazon.com/general/latest/gr/kms.html + fips_endpoint = f"https://kms-fips.{REGION}.amazonaws.com" if is_gov_region else None + kms_client = boto3.client( + "kms", + endpoint_url=fips_endpoint + ) api_key = decrypt_kms_api_key(kms_client, DD_KMS_API_KEY) else: api_key = DD_API_KEY From ca7e5c51fcbccd6b7a39d24388e0c7bb31832ccf Mon Sep 17 00:00:00 2001 From: Nicholas Hulston Date: Tue, 11 Mar 2025 16:51:31 -0400 Subject: [PATCH 02/11] lint --- datadog_lambda/api.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/datadog_lambda/api.py b/datadog_lambda/api.py index 4a4408a3..6acedae3 100644 --- a/datadog_lambda/api.py +++ b/datadog_lambda/api.py @@ -71,8 +71,7 @@ def get_api_key() -> str: # Secrets manager endpoints: https://docs.aws.amazon.com/general/latest/gr/asm.html fips_endpoint = f"https://secretsmanager-fips.{REGION}.amazonaws.com" if is_gov_region else None secrets_manager_client = boto3.client( - "secretsmanager", - endpoint_url=fips_endpoint + "secretsmanager", endpoint_url=fips_endpoint ) api_key = secrets_manager_client.get_secret_value( SecretId=DD_API_KEY_SECRET_ARN @@ -80,20 +79,14 @@ def get_api_key() -> str: elif DD_API_KEY_SSM_NAME: # SSM endpoints: https://docs.aws.amazon.com/general/latest/gr/ssm.html fips_endpoint = f"https://ssm-fips.{REGION}.amazonaws.com" if is_gov_region else None - ssm_client = boto3.client( - "ssm", - endpoint_url=fips_endpoint - ) + ssm_client = boto3.client("ssm", endpoint_url=fips_endpoint) api_key = ssm_client.get_parameter( Name=DD_API_KEY_SSM_NAME, WithDecryption=True )["Parameter"]["Value"] elif DD_KMS_API_KEY: # KMS endpoints: https://docs.aws.amazon.com/general/latest/gr/kms.html fips_endpoint = f"https://kms-fips.{REGION}.amazonaws.com" if is_gov_region else None - kms_client = boto3.client( - "kms", - endpoint_url=fips_endpoint - ) + kms_client = boto3.client("kms",endpoint_url=fips_endpoint) api_key = decrypt_kms_api_key(kms_client, DD_KMS_API_KEY) else: api_key = DD_API_KEY From bbf80426f2b31b3f20b78a7ff5aaffda826bfaa6 Mon Sep 17 00:00:00 2001 From: Nicholas Hulston Date: Tue, 11 Mar 2025 16:52:37 -0400 Subject: [PATCH 03/11] log when using fips endpoints --- datadog_lambda/api.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/datadog_lambda/api.py b/datadog_lambda/api.py index 6acedae3..d8ae1234 100644 --- a/datadog_lambda/api.py +++ b/datadog_lambda/api.py @@ -66,6 +66,8 @@ def get_api_key() -> str: REGION = os.environ.get("AWS_REGION", "") is_gov_region = REGION.startswith("us-gov-") + if is_gov_region: + logger.info("Govcloud region detected. Using FIPs endpoints for secrets management.") if DD_API_KEY_SECRET_ARN: # Secrets manager endpoints: https://docs.aws.amazon.com/general/latest/gr/asm.html From 0c4fdc45f700e7132df5523b8aea6aa1c7c0a17e Mon Sep 17 00:00:00 2001 From: Nicholas Hulston Date: Tue, 11 Mar 2025 17:03:50 -0400 Subject: [PATCH 04/11] lint --- datadog_lambda/api.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/datadog_lambda/api.py b/datadog_lambda/api.py index d8ae1234..e959ad1f 100644 --- a/datadog_lambda/api.py +++ b/datadog_lambda/api.py @@ -71,7 +71,11 @@ def get_api_key() -> str: if DD_API_KEY_SECRET_ARN: # Secrets manager endpoints: https://docs.aws.amazon.com/general/latest/gr/asm.html - fips_endpoint = f"https://secretsmanager-fips.{REGION}.amazonaws.com" if is_gov_region else None + fips_endpoint = ( + f"https://secretsmanager-fips.{REGION}.amazonaws.com" + if is_gov_region + else None + ) secrets_manager_client = boto3.client( "secretsmanager", endpoint_url=fips_endpoint ) @@ -80,14 +84,22 @@ def get_api_key() -> str: )["SecretString"] elif DD_API_KEY_SSM_NAME: # SSM endpoints: https://docs.aws.amazon.com/general/latest/gr/ssm.html - fips_endpoint = f"https://ssm-fips.{REGION}.amazonaws.com" if is_gov_region else None + fips_endpoint = ( + f"https://ssm-fips.{REGION}.amazonaws.com" + if is_gov_region + else None + ) ssm_client = boto3.client("ssm", endpoint_url=fips_endpoint) api_key = ssm_client.get_parameter( Name=DD_API_KEY_SSM_NAME, WithDecryption=True )["Parameter"]["Value"] elif DD_KMS_API_KEY: # KMS endpoints: https://docs.aws.amazon.com/general/latest/gr/kms.html - fips_endpoint = f"https://kms-fips.{REGION}.amazonaws.com" if is_gov_region else None + fips_endpoint = ( + f"https://kms-fips.{REGION}.amazonaws.com" + if is_gov_region + else None + ) kms_client = boto3.client("kms",endpoint_url=fips_endpoint) api_key = decrypt_kms_api_key(kms_client, DD_KMS_API_KEY) else: From 6d5ff78c67771af59f11b05646e7790fb07c3dab Mon Sep 17 00:00:00 2001 From: Nicholas Hulston Date: Tue, 11 Mar 2025 17:10:40 -0400 Subject: [PATCH 05/11] lint --- datadog_lambda/api.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/datadog_lambda/api.py b/datadog_lambda/api.py index e959ad1f..ce979e30 100644 --- a/datadog_lambda/api.py +++ b/datadog_lambda/api.py @@ -67,7 +67,9 @@ def get_api_key() -> str: REGION = os.environ.get("AWS_REGION", "") is_gov_region = REGION.startswith("us-gov-") if is_gov_region: - logger.info("Govcloud region detected. Using FIPs endpoints for secrets management.") + logger.info( + "Govcloud region detected. Using FIPs endpoints for secrets management." + ) if DD_API_KEY_SECRET_ARN: # Secrets manager endpoints: https://docs.aws.amazon.com/general/latest/gr/asm.html @@ -85,9 +87,7 @@ def get_api_key() -> str: elif DD_API_KEY_SSM_NAME: # SSM endpoints: https://docs.aws.amazon.com/general/latest/gr/ssm.html fips_endpoint = ( - f"https://ssm-fips.{REGION}.amazonaws.com" - if is_gov_region - else None + f"https://ssm-fips.{REGION}.amazonaws.com" if is_gov_region else None ) ssm_client = boto3.client("ssm", endpoint_url=fips_endpoint) api_key = ssm_client.get_parameter( @@ -96,9 +96,7 @@ def get_api_key() -> str: elif DD_KMS_API_KEY: # KMS endpoints: https://docs.aws.amazon.com/general/latest/gr/kms.html fips_endpoint = ( - f"https://kms-fips.{REGION}.amazonaws.com" - if is_gov_region - else None + f"https://kms-fips.{REGION}.amazonaws.com" if is_gov_region else None ) kms_client = boto3.client("kms",endpoint_url=fips_endpoint) api_key = decrypt_kms_api_key(kms_client, DD_KMS_API_KEY) From f054e2239df27525f1dce50c8c0d9ef94dcf1ae6 Mon Sep 17 00:00:00 2001 From: Nicholas Hulston Date: Tue, 11 Mar 2025 17:11:50 -0400 Subject: [PATCH 06/11] lint again --- datadog_lambda/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datadog_lambda/api.py b/datadog_lambda/api.py index ce979e30..7e4fc6cb 100644 --- a/datadog_lambda/api.py +++ b/datadog_lambda/api.py @@ -98,7 +98,7 @@ def get_api_key() -> str: fips_endpoint = ( f"https://kms-fips.{REGION}.amazonaws.com" if is_gov_region else None ) - kms_client = boto3.client("kms",endpoint_url=fips_endpoint) + kms_client = boto3.client("kms", endpoint_url=fips_endpoint) api_key = decrypt_kms_api_key(kms_client, DD_KMS_API_KEY) else: api_key = DD_API_KEY From af87910e0feda6cb62052022f7135fea09a27624 Mon Sep 17 00:00:00 2001 From: Nicholas Hulston Date: Wed, 12 Mar 2025 10:26:48 -0400 Subject: [PATCH 07/11] change to debug --- datadog_lambda/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datadog_lambda/api.py b/datadog_lambda/api.py index 7e4fc6cb..03135912 100644 --- a/datadog_lambda/api.py +++ b/datadog_lambda/api.py @@ -67,7 +67,7 @@ def get_api_key() -> str: REGION = os.environ.get("AWS_REGION", "") is_gov_region = REGION.startswith("us-gov-") if is_gov_region: - logger.info( + logger.debug( "Govcloud region detected. Using FIPs endpoints for secrets management." ) From 7fdd7e84de15d9a37a488e23462acc3e95cc29ab Mon Sep 17 00:00:00 2001 From: Nicholas Hulston Date: Wed, 12 Mar 2025 10:37:07 -0400 Subject: [PATCH 08/11] add unit test --- tests/test_api.py | 84 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 tests/test_api.py diff --git a/tests/test_api.py b/tests/test_api.py new file mode 100644 index 00000000..795c6d56 --- /dev/null +++ b/tests/test_api.py @@ -0,0 +1,84 @@ +import os +import unittest +from unittest.mock import patch, MagicMock + +import datadog_lambda.api as api + +class TestDatadogLambdaAPI(unittest.TestCase): + def setUp(self): + api.api_key = None + self.env_patcher = patch.dict(os.environ, { + "DD_API_KEY_SECRET_ARN": "", + "DD_API_KEY_SSM_NAME": "", + "DD_KMS_API_KEY": "", + "DD_API_KEY": "", + "DATADOG_API_KEY": "", + "AWS_REGION": "", + }, clear=True) + self.env_patcher.start() + + @patch('boto3.client') + def test_secrets_manager_fips_endpoint(self, mock_boto3_client): + mock_client = MagicMock() + mock_client.get_secret_value.return_value = {"SecretString": "test-api-key"} + mock_boto3_client.return_value = mock_client + + os.environ["AWS_REGION"] = "us-gov-east-1" + os.environ["DD_API_KEY_SECRET_ARN"] = "test-secrets-arn" + + api_key = api.get_api_key() + + mock_boto3_client.assert_called_with( + "secretsmanager", + endpoint_url="https://secretsmanager-fips.us-gov-east-1.amazonaws.com" + ) + self.assertEqual(api_key, "test-api-key") + + @patch('boto3.client') + def test_ssm_fips_endpoint(self, mock_boto3_client): + mock_client = MagicMock() + mock_client.get_parameter.return_value = {"Parameter": {"Value": "test-api-key"}} + mock_boto3_client.return_value = mock_client + + os.environ["AWS_REGION"] = "us-gov-west-1" + os.environ["DD_API_KEY_SSM_NAME"] = "test-ssm-param" + + api_key = api.get_api_key() + + mock_boto3_client.assert_called_with( + "ssm", + endpoint_url="https://ssm-fips.us-gov-west-1.amazonaws.com" + ) + self.assertEqual(api_key, "test-api-key") + + @patch('boto3.client') + @patch('datadog_lambda.api.decrypt_kms_api_key') + def test_kms_fips_endpoint(self, mock_decrypt_kms, mock_boto3_client): + mock_client = MagicMock() + mock_boto3_client.return_value = mock_client + mock_decrypt_kms.return_value = "test-api-key" + + os.environ["AWS_REGION"] = "us-gov-west-1" + os.environ["DD_KMS_API_KEY"] = "encrypted-api-key" + + api_key = api.get_api_key() + + mock_boto3_client.assert_called_with( + "kms", + endpoint_url="https://kms-fips.us-gov-west-1.amazonaws.com" + ) + self.assertEqual(api_key, "test-api-key") + + @patch('boto3.client') + def test_no_fips_for_standard_regions(self, mock_boto3_client): + mock_client = MagicMock() + mock_client.get_secret_value.return_value = {"SecretString": "test-api-key"} + mock_boto3_client.return_value = mock_client + + os.environ.clear() + os.environ["AWS_REGION"] = "us-west-2" + os.environ["DD_API_KEY_SECRET_ARN"] = "test-arn" + + api.get_api_key() + + mock_boto3_client.assert_called_with("secretsmanager", endpoint_url=None) From 6396cefc4122e9e4a569769186ff1a4a4572de8e Mon Sep 17 00:00:00 2001 From: Nicholas Hulston Date: Wed, 12 Mar 2025 10:40:42 -0400 Subject: [PATCH 09/11] lint --- tests/test_api.py | 50 ++++++++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index 795c6d56..d210c668 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -7,17 +7,21 @@ class TestDatadogLambdaAPI(unittest.TestCase): def setUp(self): api.api_key = None - self.env_patcher = patch.dict(os.environ, { - "DD_API_KEY_SECRET_ARN": "", - "DD_API_KEY_SSM_NAME": "", - "DD_KMS_API_KEY": "", - "DD_API_KEY": "", - "DATADOG_API_KEY": "", - "AWS_REGION": "", - }, clear=True) + self.env_patcher = patch.dict( + os.environ, + { + "DD_API_KEY_SECRET_ARN": "", + "DD_API_KEY_SSM_NAME": "", + "DD_KMS_API_KEY": "", + "DD_API_KEY": "", + "DATADOG_API_KEY": "", + "AWS_REGION": "", + }, + clear=True, + ) self.env_patcher.start() - @patch('boto3.client') + @patch("boto3.client") def test_secrets_manager_fips_endpoint(self, mock_boto3_client): mock_client = MagicMock() mock_client.get_secret_value.return_value = {"SecretString": "test-api-key"} @@ -30,14 +34,16 @@ def test_secrets_manager_fips_endpoint(self, mock_boto3_client): mock_boto3_client.assert_called_with( "secretsmanager", - endpoint_url="https://secretsmanager-fips.us-gov-east-1.amazonaws.com" + endpoint_url="https://secretsmanager-fips.us-gov-east-1.amazonaws.com", ) self.assertEqual(api_key, "test-api-key") - @patch('boto3.client') + @patch("boto3.client") def test_ssm_fips_endpoint(self, mock_boto3_client): mock_client = MagicMock() - mock_client.get_parameter.return_value = {"Parameter": {"Value": "test-api-key"}} + mock_client.get_parameter.return_value = { + "Parameter": {"Value": "test-api-key"} + } mock_boto3_client.return_value = mock_client os.environ["AWS_REGION"] = "us-gov-west-1" @@ -46,13 +52,12 @@ def test_ssm_fips_endpoint(self, mock_boto3_client): api_key = api.get_api_key() mock_boto3_client.assert_called_with( - "ssm", - endpoint_url="https://ssm-fips.us-gov-west-1.amazonaws.com" + "ssm", endpoint_url="https://ssm-fips.us-gov-west-1.amazonaws.com" ) self.assertEqual(api_key, "test-api-key") - @patch('boto3.client') - @patch('datadog_lambda.api.decrypt_kms_api_key') + @patch("boto3.client") + @patch("datadog_lambda.api.decrypt_kms_api_key") def test_kms_fips_endpoint(self, mock_decrypt_kms, mock_boto3_client): mock_client = MagicMock() mock_boto3_client.return_value = mock_client @@ -64,21 +69,12 @@ def test_kms_fips_endpoint(self, mock_decrypt_kms, mock_boto3_client): api_key = api.get_api_key() mock_boto3_client.assert_called_with( - "kms", - endpoint_url="https://kms-fips.us-gov-west-1.amazonaws.com" + "kms", endpoint_url="https://kms-fips.us-gov-west-1.amazonaws.com" ) self.assertEqual(api_key, "test-api-key") - @patch('boto3.client') + @patch("boto3.client") def test_no_fips_for_standard_regions(self, mock_boto3_client): mock_client = MagicMock() mock_client.get_secret_value.return_value = {"SecretString": "test-api-key"} mock_boto3_client.return_value = mock_client - - os.environ.clear() - os.environ["AWS_REGION"] = "us-west-2" - os.environ["DD_API_KEY_SECRET_ARN"] = "test-arn" - - api.get_api_key() - - mock_boto3_client.assert_called_with("secretsmanager", endpoint_url=None) From 7411cf2871ed8a6814affddf5dc53db9983237f7 Mon Sep 17 00:00:00 2001 From: Nicholas Hulston Date: Wed, 12 Mar 2025 10:41:25 -0400 Subject: [PATCH 10/11] fix `test_no_fips_for_standard_regions` --- tests/test_api.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_api.py b/tests/test_api.py index d210c668..c97f09d2 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -78,3 +78,11 @@ def test_no_fips_for_standard_regions(self, mock_boto3_client): mock_client = MagicMock() mock_client.get_secret_value.return_value = {"SecretString": "test-api-key"} mock_boto3_client.return_value = mock_client + + os.environ.clear() + os.environ["AWS_REGION"] = "us-west-2" + os.environ["DD_API_KEY_SECRET_ARN"] = "test-arn" + + api.get_api_key() + + mock_boto3_client.assert_called_with("secretsmanager", endpoint_url=None) From 2b02f029c63cd52419c12e9a3f25874cf8afaafe Mon Sep 17 00:00:00 2001 From: Nicholas Hulston Date: Wed, 12 Mar 2025 10:42:20 -0400 Subject: [PATCH 11/11] lint --- tests/test_api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_api.py b/tests/test_api.py index c97f09d2..a69f4382 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -4,6 +4,7 @@ import datadog_lambda.api as api + class TestDatadogLambdaAPI(unittest.TestCase): def setUp(self): api.api_key = None