Skip to content

Commit

Permalink
remove all the cross-account stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
tmclaugh committed Oct 27, 2024
1 parent 8cbc08c commit fb6b661
Show file tree
Hide file tree
Showing 4 changed files with 15 additions and 160 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ jobs:
- name: Validate StackSets SAM template (CFN CR)
run: sam validate --lint -t stacksets/cfn-cr/stackset.yaml

- name: Unit tests
id: unit-tests
shell: bash
run: pipenv run pytest

# Stacksets can't use CFN transforms so we need to handle the SAM transform ourselves.
- name: SAM build (CFN CR)
id: sam-build-cfn-cr
Expand Down
72 changes: 7 additions & 65 deletions src/handlers/RegisterDnsZone/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,78 +3,30 @@
import json

from crhelper import CfnResource
from dataclasses import dataclass
from mypy_boto3_route53 import Route53Client
from mypy_boto3_route53.type_defs import ChangeBatchTypeDef, ChangeResourceRecordSetsRequestRequestTypeDef
from mypy_boto3_sts.type_defs import CredentialsTypeDef
from mypy_boto3_route53.type_defs import (
ChangeBatchTypeDef,
ChangeResourceRecordSetsRequestRequestTypeDef
)
from typing import Dict

from aws_lambda_powertools.logging import Logger
from aws_lambda_powertools.utilities.typing import LambdaContext
from aws_lambda_powertools.utilities.data_classes import (
CloudFormationCustomResourceEvent,
SNSEvent,
event_source
)
helper = CfnResource(json_logging=True)

try:
# FIXME: We should be able to set the service name from the environment.
LOGGER = Logger(utc=True, service="RegisterDnsZone")
STS_CLIENT = boto3.client('sts')
CROSS_ACCOUNT_IAM_ROLE_NAME = os.environ.get('CROSS_ACCOUNT_IAM_ROLE_NAME', '')
RT53_CLIENT: Route53Client = boto3.client('route53')
DNS_ROOT_ZONE_ID = os.environ.get('DNS_ROOT_ZONE_ID', '')
DNS_ROOT_ZONE_ACCOUNT_ID = os.environ.get('DNS_ROOT_ZONE_ACCOUNT_ID', '')

if CROSS_ACCOUNT_IAM_ROLE_NAME == '':
raise ValueError("CROSS_ACCOUNT_IAM_ROLE_NAME must be provided")
if DNS_ROOT_ZONE_ID == '':
raise ValueError("DNS_ROOT_ZONE_ID must be provided")
if DNS_ROOT_ZONE_ACCOUNT_ID == '':
raise ValueError("DNS_ROOT_ZONE_ACCOUNT_ID must be provided")

except Exception as e:
LOGGER.exception(e)
helper.init_failure(e)


def _get_cross_account_credentials(
account_id: str,
role_name: str
) -> CredentialsTypeDef:
'''Return the IAM role for cross-account access'''
role_arn = 'arn:aws:iam::{}:role/{}'.format(account_id, role_name)
try:
response = STS_CLIENT.assume_role(
RoleArn=role_arn,
RoleSessionName='RegisterDnsZoneCrossAccount'
)
except Exception as e:
LOGGER.exception(e)
raise e

return response['Credentials']


def _get_cross_account_route53_client(
account_id: str,
role_name: str
) -> Route53Client:
'''Return a Route 53 client for cross-account access'''
credentials = _get_cross_account_credentials(
account_id,
role_name
)
client = boto3.client(
'route53',
aws_access_key_id=credentials['AccessKeyId'],
aws_secret_access_key=credentials['SecretAccessKey'],
aws_session_token=credentials['SessionToken']
)

return client


@helper.create
@helper.update
def create_or_update(event, _: LambdaContext):
Expand Down Expand Up @@ -120,13 +72,8 @@ def create_or_update(event, _: LambdaContext):
'ChangeBatch': change_batch
}


route53_client = _get_cross_account_route53_client(
DNS_ROOT_ZONE_ACCOUNT_ID,
CROSS_ACCOUNT_IAM_ROLE_NAME
)
# Update the Route 53 hosted zone with the new NS record
response = route53_client.change_resource_record_sets(**change_args)
response = RT53_CLIENT.change_resource_record_sets(**change_args)

LOGGER.info("Change Info: {}".format(response['ChangeInfo']))

Expand Down Expand Up @@ -169,13 +116,8 @@ def delete(event, _: LambdaContext):
]
}

route53_client = _get_cross_account_route53_client(
DNS_ROOT_ZONE_ACCOUNT_ID,
CROSS_ACCOUNT_IAM_ROLE_NAME
)

# Delete the NS record from the Route 53 hosted zone
response = route53_client.change_resource_record_sets(
response = RT53_CLIENT.change_resource_record_sets(
HostedZoneId=DNS_ROOT_ZONE_ID,
ChangeBatch=change_batch
)
Expand Down
2 changes: 1 addition & 1 deletion src/handlers/RegisterDnsZone/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
aws_lambda_powertools
boto3-stubs[route53,sts]
boto3-stubs[route53]
crhelper
96 changes: 2 additions & 94 deletions tests/unit/src/handlers/RegisterDnsZone/test_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@
SNSEvent
)
from collections import namedtuple
from datetime import datetime, timedelta
from moto import mock_aws
from mypy_boto3_route53 import Route53Client
from mypy_boto3_sts import STSClient
from mypy_boto3_sts.type_defs import CredentialsTypeDef
from pytest_mock import MockerFixture
from types import ModuleType
from typing import cast, Generator, Tuple
Expand Down Expand Up @@ -60,33 +58,9 @@ def mocked_aws(aws_credentials):
with mock_aws():
yield

@pytest.fixture()
def mock_sts_client(mocked_aws) -> Generator[STSClient, None, None]:
with mock_aws():
yield boto3.client('sts')

@pytest.fixture
def mock_cross_account_iam_credentials(mock_sts_client) -> CredentialsTypeDef:
'''Mocked cross-account IAM credentials'''

credentials: CredentialsTypeDef = {
'AccessKeyId': 'cross_account_testing',
'SecretAccessKey': 'cross_account_testing',
'SessionToken': 'cross_account_testing',
'Expiration': datetime.now() + timedelta(hours=1)
}

return credentials


@pytest.fixture
def mock_route53_client(mocked_aws, mock_cross_account_iam_credentials) -> Generator[Route53Client, None, None]:
yield boto3.client(
'route53',
aws_access_key_id=mock_cross_account_iam_credentials['AccessKeyId'],
aws_secret_access_key=mock_cross_account_iam_credentials['SecretAccessKey'],
aws_session_token=mock_cross_account_iam_credentials['SessionToken']
)
def mock_route53_client(mocked_aws) -> Generator[Route53Client, None, None]:
yield boto3.client('route53')

@pytest.fixture()
def mock_hosted_zone_id(mock_route53_client: Route53Client) -> str:
Expand Down Expand Up @@ -159,7 +133,6 @@ def mock_data(data: str = DATA) -> EventResourceProperties:
def mock_fn(
mocked_aws,
mock_hosted_zone_id: str,
mock_cross_account_id: str,
mocker: MockerFixture,
) -> Generator[ModuleType, None, None]:
'''Patch the environment variables for the function'''
Expand All @@ -169,60 +142,10 @@ def mock_fn(
'src.handlers.RegisterDnsZone.function.DNS_ROOT_ZONE_ID',
mock_hosted_zone_id
)
mocker.patch(
'src.handlers.RegisterDnsZone.function.DNS_ROOT_ZONE_ACCOUNT_ID',
mock_cross_account_id
)

yield fn


# Tests
def test__get_cross_account_credentials(
mock_fn: ModuleType,
mock_cross_account_id: str,
) -> None:
role_name = 'MockRegisterDnsZoneCrossAccountRole'

# Call the create_or_update function
credentials = mock_fn._get_cross_account_credentials(mock_cross_account_id, role_name)

# Verify credentials exist and that we got new ones
assert credentials is not None
assert isinstance(credentials, dict)
assert isinstance(credentials['AccessKeyId'], str)
assert isinstance(credentials['SecretAccessKey'], str)
assert isinstance(credentials['SessionToken'], str)


def test__get_cross_account_route53_client(
mock_fn: ModuleType,
mock_cross_account_iam_credentials: CredentialsTypeDef,
mock_hosted_zone_id: str,
mocker: MockerFixture,
) -> None:

mocker.patch(
'src.handlers.RegisterDnsZone.function._get_cross_account_credentials',
return_value=mock_cross_account_iam_credentials
)

cross_account_id = mock_fn.DNS_ROOT_ZONE_ACCOUNT_ID
role_name = mock_fn.CROSS_ACCOUNT_IAM_ROLE_NAME

# Call the create_or_update function
client = mock_fn._get_cross_account_route53_client(cross_account_id, role_name)

# Verify the NS record was created
assert client is not None
assert client.meta.endpoint_url == 'https://route53.amazonaws.com'
assert client.meta.region_name == 'aws-global'
assert client.meta.config.user_agent.startswith('Boto3/')

zones = client.list_hosted_zones()
assert zones is not None
# Ensure we have a route53 client with credentials for the cross-account
assert zones['HostedZones'][0]['Id'] == mock_hosted_zone_id


def test_create_or_update_as_create(
Expand All @@ -238,11 +161,6 @@ def test_create_or_update_as_create(
event['ResourceProperties']['ZoneName'] = mock_data.ZoneName
event['ResourceProperties']['NameServers'] = mock_data.NameServers

mocker.patch(
'src.handlers.RegisterDnsZone.function._get_cross_account_route53_client',
return_value=mock_route53_client
)

# Call the create_or_update function
mock_fn.create_or_update(event, mock_context)

Expand Down Expand Up @@ -281,11 +199,6 @@ def test_create_or_update_as_update(
event['ResourceProperties']['ZoneName'] = mock_data.ZoneName
event['ResourceProperties']['NameServers'] = mock_data.NameServers

mocker.patch(
'src.handlers.RegisterDnsZone.function._get_cross_account_route53_client',
return_value=mock_route53_client
)

# Call the create_or_update function
mock_fn.create_or_update(event, mock_context)

Expand Down Expand Up @@ -322,11 +235,6 @@ def test_delete(
mocker: MockerFixture,
) -> None:

mocker.patch(
'src.handlers.RegisterDnsZone.function._get_cross_account_route53_client',
return_value=mock_route53_client
)

# Create record to be deleted
create_event = mock_message_create._data
create_event['ResourceProperties']['ZoneName'] = mock_data.ZoneName
Expand Down

0 comments on commit fb6b661

Please sign in to comment.