Skip to content

Commit b962c57

Browse files
authored
chore: Refactored secret manager and static data for credential rotator (#88)
1 parent f961f16 commit b962c57

File tree

12 files changed

+192
-51
lines changed

12 files changed

+192
-51
lines changed

src/release_artifacts_resources/ios/cdk/cdk/credential_rotation/lambda_functions/src/destination/circleci.py

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,30 +20,21 @@
2020
2. The Lambda function invokes the CircleCI V2 API endpoint to update
2121
environment variables, authenticating with the **CircleCI API token**
2222
"""
23-
import os
24-
25-
import boto3
2623
import requests
2724
from src.utils.retry import retry
25+
from src.utils.secrets_manager_helper import retrieve_secret
2826

2927
CIRCLECI_URL_TEMPLATE = "https://circleci.com/api/v2/project/gh/{project_path}/envvar"
30-
DEFAULT_REGION = "us-west-2"
31-
32-
# Provided by Lambda
33-
REGION = os.environ.get("AWS_REGION", DEFAULT_REGION)
3428

3529

36-
def update_environment_variables(variables: map, configuration: map, secretsmanager=None):
30+
def update_environment_variables(variables: map, configuration: map):
3731
"""Updates CircleCI environment variables
3832
3933
Args:
4034
variables:
4135
<list expected keys & values>
4236
configuration:
4337
<list expected keys & values>
44-
secretsmanager:
45-
(optional) reference to an AWS SecretsManager client.
46-
Defaults to the default client for the region
4738
4839
Raises
4940
KeyError: if `configuration` does not contain the expected keys
@@ -54,18 +45,10 @@ def update_environment_variables(variables: map, configuration: map, secretsmana
5445
raise RuntimeError("Configuration is required to update CircleCI environment variables")
5546

5647
github_path = configuration["github_path"]
57-
circleci_api_token_secret_arn_lambda_env_var_key = configuration[
58-
"circleci_api_token_secret_arn_lambda_env_var_key"
48+
circleci_api_token_secret_id_lambda_env_var_key = configuration[
49+
"circleci_api_token_secret_id_lambda_env_var_key"
5950
]
60-
secret_id = os.environ.get(circleci_api_token_secret_arn_lambda_env_var_key)
61-
62-
if secret_id is None:
63-
raise ValueError(
64-
f"Lambda env var {circleci_api_token_secret_arn_lambda_env_var_key} is not set"
65-
)
66-
67-
secretsmanager_client = secretsmanager or boto3.client("secretsmanager", region_name=REGION)
68-
circleci_api_token = get_secret_value(secret_id, secretsmanager=secretsmanager_client)
51+
circleci_api_token = retrieve_secret(circleci_api_token_secret_id_lambda_env_var_key)
6952

7053
for key, value in variables.items():
7154
update_env_vars(

src/release_artifacts_resources/ios/cdk/cdk/credential_rotation/lambda_functions/src/handler.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
from destination import circleci
44
from models.destination_type import DestinationType
55
from models.source_type import SourceType
6-
from source_data_generator import aws_session_credential_source
6+
from source_data_generator import (
7+
aws_session_credential_source,
8+
secrets_data_source,
9+
lambda_env_var_data_source,
10+
)
711

812

913
def handler(event, context, *, iam=None, sts=None, secretsmanager=None):
@@ -78,7 +82,7 @@ def handler(event, context, *, iam=None, sts=None, secretsmanager=None):
7882
"type": "cci_env_variable",
7983
"description": "Circle CI environment variable for AWS SDK iOS repo",
8084
"github_path": "aws-amplify/aws-sdk-ios",
81-
"circleci_api_token_secret_arn_lambda_env_var_key": "CIRCLE_CI_IOS_SDK_API_TOKEN"
85+
"circleci_api_token_secret_id_lambda_env_var_key": "CIRCLE_CI_IOS_SDK_API_TOKEN"
8286
}
8387
}
8488
}
@@ -96,19 +100,21 @@ def handler(event, context, *, iam=None, sts=None, secretsmanager=None):
96100
destination_mapping = source["destination"]["mapping_to_destination"]
97101
configuration = source["configuration"]
98102

103+
source_map = {}
99104
if source_type == SourceType.AWS_SESSION_CREDENTIALS:
100-
credentials = aws_session_credential_source.generate_session_credentials(configuration)
101-
mapped_result = {}
102-
for item in destination_mapping:
103-
destination_key_name = item["destination_key_name"]
104-
result_value_key = item["result_value_key"]
105-
mapped_result[destination_key_name] = credentials[result_value_key]
105+
source_map = aws_session_credential_source.generate_session_credentials(configuration)
106106

107107
elif source_type == SourceType.SECRETS_MANAGER:
108-
mapped_result = {}
108+
source_map = secrets_data_source.retrieve_secrets(configuration)
109109

110110
elif source_type == SourceType.LAMBDA_ENVIRONMENT_VARIABLE:
111-
mapped_result = {}
111+
source_map = lambda_env_var_data_source.retrieve_lambda_env_var_value(configuration)
112+
113+
mapped_result = {}
114+
for item in destination_mapping:
115+
destination_key_name = item["destination_key_name"]
116+
result_value_key = item["result_value_key"]
117+
mapped_result[destination_key_name] = source_map[result_value_key]
112118

113119
destination_values_map.setdefault(destination_specifier, []).append(mapped_result)
114120

src/release_artifacts_resources/ios/cdk/cdk/credential_rotation/lambda_functions/src/source_data_generator/aws_session_credential_source.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ def generate_session_credentials(configuration: map, iam=None, sts=None) -> Dict
4646
finally:
4747
if user_credentials:
4848
iam_client.delete_access_key(UserName=iam_username, AccessKeyId=user_credentials[0])
49-
5049
return session_credentials
5150

5251

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import os
2+
from typing import Dict
3+
4+
5+
def retrieve_lambda_env_var_value(configuration: map) -> Dict[str, str]:
6+
7+
if not configuration:
8+
raise RuntimeError("Configuration is required to retrieve static data")
9+
10+
lambda_env_variable = configuration["lambda_env_var_key"]
11+
static_data = os.environ.get(lambda_env_variable)
12+
return {"result": static_data}

src/release_artifacts_resources/ios/cdk/cdk/credential_rotation/lambda_functions/src/source_data_generator/secrets_data_propagator.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import json
2+
from typing import Dict
3+
4+
from src.utils.secrets_manager_helper import retrieve_secret
5+
6+
7+
def retrieve_secrets(configuration: map) -> Dict[str, str]:
8+
9+
if not configuration:
10+
raise RuntimeError("Configuration is required to retrieve secrets")
11+
12+
secret_key_env_variable = configuration["secret_key_env_variable"]
13+
secret_value = retrieve_secret(secret_key_env_variable)
14+
try:
15+
json_value = json.loads(secret_value)
16+
return json_value
17+
except (json.decoder.JSONDecodeError):
18+
return {"result": secret_value}

src/release_artifacts_resources/ios/cdk/cdk/credential_rotation/lambda_functions/src/source_data_generator/static_data_propagator.py

Whitespace-only changes.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import os
2+
3+
import boto3
4+
5+
DEFAULT_REGION = "us-west-2"
6+
7+
# Provided by Lambda
8+
REGION = os.environ.get("AWS_REGION", DEFAULT_REGION)
9+
10+
11+
def retrieve_secret(secret_id_lambda_env_var_key, secretsmanager=None):
12+
"""Retrieve an AWS SecretsManager whose Secret Id is contained in
13+
the specified Lambda Env Var.
14+
"""
15+
16+
secretsmanager_client = secretsmanager or boto3.client("secretsmanager", region_name=REGION)
17+
secret_id = os.environ.get(secret_id_lambda_env_var_key)
18+
19+
if secret_id is None:
20+
raise ValueError(f"Lambda env var {secret_id_lambda_env_var_key} is not set")
21+
22+
return get_secret_value(secret_id, secretsmanager=secretsmanager_client)
23+
24+
25+
def get_secret_value(secret_id: str, *, secretsmanager) -> str:
26+
response = secretsmanager.get_secret_value(SecretId=secret_id)
27+
secret_value = response["SecretString"]
28+
return secret_value

src/release_artifacts_resources/ios/cdk/cdk/credential_rotation/lambda_functions/test/test_circleci.py

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
1-
import os
21
import unittest
32
from unittest.mock import Mock, call, patch
43

5-
import botocore.session
6-
from botocore.stub import Stubber
74
from src.destination import circleci
85

9-
session = botocore.session.get_session()
10-
secretsmanager = session.create_client("secretsmanager", region_name=circleci.REGION)
11-
126
access_key_id = "AKIAIOSFODNN7EXAMPLE"
137
secret_access_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY"
148
session_token = (
@@ -33,24 +27,17 @@ def test_generate_credential_with_null_configuration(self):
3327
variables=self.mock_variables(), configuration=None
3428
)
3529

36-
@patch.dict(os.environ, {"CIRCLE_CI_IOS_SDK_API_TOKEN": "arn:::xxx"})
3730
@patch("src.destination.circleci.requests.post")
38-
def test_updates_variables(self, post):
39-
secretsmanager_stubber = Stubber(secretsmanager)
40-
request = {"SecretId": "arn:::xxx"}
41-
response = {"SecretString": "SEKRET!"}
42-
secretsmanager_stubber.add_response("get_secret_value", response, request)
31+
@patch("src.destination.circleci.retrieve_secret")
32+
def test_updates_variables(self, mock_retrieve_secret, post):
4333

4434
post.return_value = Mock()
4535
post.return_value.status_code = 200
4636

47-
secretsmanager_stubber.activate()
37+
mock_retrieve_secret.return_value = "SEKRET!"
4838
circleci.update_environment_variables(
49-
variables=self.mock_variables(),
50-
configuration=self.mock_configuration(),
51-
secretsmanager=secretsmanager,
39+
variables=self.mock_variables(), configuration=self.mock_configuration()
5240
)
53-
secretsmanager_stubber.assert_no_pending_responses()
5441

5542
url = "https://circleci.com/api/v2/project/gh/aws-amplify/aws-sdk-ios/envvar"
5643
header = {"Circle-Token": "SEKRET!"}
@@ -76,7 +63,7 @@ def mock_configuration(self):
7663
"type": "cci_env_variable",
7764
"description": "Circle CI environment variable for AWS SDK iOS repo",
7865
"github_path": "aws-amplify/aws-sdk-ios",
79-
"circleci_api_token_secret_arn_lambda_env_var_key": "CIRCLE_CI_IOS_SDK_API_TOKEN",
66+
"circleci_api_token_secret_id_lambda_env_var_key": "CIRCLE_CI_IOS_SDK_API_TOKEN",
8067
}
8168

8269

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import unittest
2+
from unittest.mock import patch
3+
4+
from src.source_data_generator import secrets_data_source
5+
6+
7+
class TestSecretsDataSource(unittest.TestCase):
8+
def test_null_environment_value(self):
9+
with self.assertRaises(RuntimeError):
10+
secrets_data_source.retrieve_secrets(configuration=None)
11+
12+
@patch("src.source_data_generator.secrets_data_source.retrieve_secret")
13+
def test_valid_result(self, mock_retrieve_secret):
14+
mock_retrieve_secret.return_value = "SEKRET!"
15+
configuration = {"secret_key_env_variable": "secret_key"}
16+
result = secrets_data_source.retrieve_secrets(configuration)
17+
self.assertIsNotNone(result)
18+
19+
@patch("src.source_data_generator.secrets_data_source.retrieve_secret")
20+
def test_valid_result_string(self, mock_retrieve_secret):
21+
mock_retrieve_secret.return_value = "SEKRET!"
22+
configuration = {"secret_key_env_variable": "secret_key"}
23+
result = secrets_data_source.retrieve_secrets(configuration)
24+
self.assertTrue(isinstance(result, dict))
25+
secret_value = result["result"]
26+
self.assertEqual(secret_value, "SEKRET!")
27+
28+
@patch("src.source_data_generator.secrets_data_source.retrieve_secret")
29+
def test_valid_result_json(self, mock_retrieve_secret):
30+
mock_retrieve_secret.return_value = """{"GITHUB_SPM_RELEASE_USER": "user",
31+
"GITHUB_SPM_RELEASE_TOKEN": "token"}
32+
"""
33+
configuration = {"secret_key_env_variable": "secret_key"}
34+
result = secrets_data_source.retrieve_secrets(configuration)
35+
self.assertTrue(isinstance(result, dict))
36+
37+
secret_value = result["GITHUB_SPM_RELEASE_USER"]
38+
secret_token = result["GITHUB_SPM_RELEASE_TOKEN"]
39+
self.assertEqual(secret_value, "user")
40+
self.assertEqual(secret_token, "token")
41+
42+
43+
if __name__ == "__main__":
44+
unittest.main()

0 commit comments

Comments
 (0)