From 9984545d689138a84c42af16f2eaa729675882a1 Mon Sep 17 00:00:00 2001 From: Petros Kalos Date: Wed, 19 Jun 2024 15:43:52 +0300 Subject: [PATCH] split cognito urls setup and cognito user creation --- .../cognito_config/cognito_urls.py | 82 ++++++++++++++++ ...ognito_urls_config.py => cognito_users.py} | 56 ----------- deploy/custom_resources/utils.py | 23 +++++ deploy/stacks/cloudfront.py | 98 +++++++++++++++++++ deploy/stacks/cloudfront_stack.py | 2 + deploy/stacks/cloudfront_stage.py | 2 + deploy/stacks/cognito.py | 22 +---- deploy/stacks/pipeline.py | 1 + 8 files changed, 211 insertions(+), 75 deletions(-) create mode 100644 deploy/custom_resources/cognito_config/cognito_urls.py rename deploy/custom_resources/cognito_config/{cognito_urls_config.py => cognito_users.py} (63%) create mode 100644 deploy/custom_resources/utils.py diff --git a/deploy/custom_resources/cognito_config/cognito_urls.py b/deploy/custom_resources/cognito_config/cognito_urls.py new file mode 100644 index 000000000..43be35c1f --- /dev/null +++ b/deploy/custom_resources/cognito_config/cognito_urls.py @@ -0,0 +1,82 @@ +import logging +import os + +import boto3 + +logger = logging.getLogger() +logger.setLevel(os.environ.get('LOG_LEVEL', 'INFO')) +log = logging.getLogger(__name__) + + +def setup_cognito( + region, + envname, + custom_domain='False', +): + ssm = boto3.client('ssm', region_name=region) + user_pool_id = ssm.get_parameter(Name=f'/dataall/{envname}/cognito/userpool')['Parameter']['Value'] + log.info(f'Cognito Pool ID: {user_pool_id}') + app_client = ssm.get_parameter(Name=f'/dataall/{envname}/cognito/appclient')['Parameter']['Value'] + + if custom_domain == 'False': + log.info('Switching to us-east-1 region...') + ssm = boto3.client('ssm', region_name='us-east-1') + signin_singout_link = ssm.get_parameter(Name=f'/dataall/{envname}/CloudfrontDistributionDomainName')[ + 'Parameter' + ]['Value'] + user_guide_link = ssm.get_parameter( + Name=f'/dataall/{envname}/cloudfront/docs/user/CloudfrontDistributionDomainName' + )['Parameter']['Value'] + else: + signin_singout_link = ssm.get_parameter(Name=f'/dataall/{envname}/frontend/custom_domain_name')['Parameter'][ + 'Value' + ] + user_guide_link = ssm.get_parameter(Name=f'/dataall/{envname}/userguide/custom_domain_name')['Parameter'][ + 'Value' + ] + + log.info(f'UI: {signin_singout_link}') + log.info(f'USERGUIDE: {user_guide_link}') + + cognito = boto3.client('cognito-idp', region_name=region) + user_pool = cognito.describe_user_pool_client(UserPoolId=user_pool_id, ClientId=app_client) + + del user_pool['UserPoolClient']['CreationDate'] + del user_pool['UserPoolClient']['LastModifiedDate'] + + config_callbacks = [ + f'https://{signin_singout_link}', + f'https://{user_guide_link}/parseauth', + ] + existing_callbacks = user_pool['UserPoolClient'].get('CallbackURLs', []) + if 'https://example.com' in existing_callbacks: + existing_callbacks.remove('https://example.com') + updated_callbacks = existing_callbacks + list(set(config_callbacks) - set(existing_callbacks)) + log.info(f'Updated CallBackUrls: {updated_callbacks}') + + config_logout_urls = [f'https://{signin_singout_link}'] + existing_logout_urls = user_pool['UserPoolClient'].get('LogoutURLs', []) + updated_logout_urls = existing_logout_urls + list(set(config_logout_urls) - set(existing_logout_urls)) + log.info(f'Updated LogOutUrls: {updated_logout_urls}') + + user_pool['UserPoolClient']['CallbackURLs'] = updated_callbacks + user_pool['UserPoolClient']['LogoutURLs'] = updated_logout_urls + + response = cognito.update_user_pool_client( + **user_pool['UserPoolClient'], + ) + + log.info(f'CallbackUrls and LogOutUrls updated successfully: {response}') + + +def handler(event, context) -> None: + log.info('Starting Cognito Configuration...') + envname = os.environ.get('envname') + region = os.environ.get('deployment_region') + custom_domain = os.environ.get('custom_domain') + setup_cognito( + region, + envname, + custom_domain, + ) + log.info('Cognito Configuration Finished Successfully') diff --git a/deploy/custom_resources/cognito_config/cognito_urls_config.py b/deploy/custom_resources/cognito_config/cognito_users.py similarity index 63% rename from deploy/custom_resources/cognito_config/cognito_urls_config.py rename to deploy/custom_resources/cognito_config/cognito_users.py index 836616822..dc17c8517 100644 --- a/deploy/custom_resources/cognito_config/cognito_urls_config.py +++ b/deploy/custom_resources/cognito_config/cognito_users.py @@ -22,65 +22,13 @@ def setup_cognito( region, resource_prefix, envname, - internet_facing='True', - custom_domain='False', enable_cw_canaries='False', with_approval_tests='False', ): ssm = boto3.client('ssm', region_name=region) user_pool_id = ssm.get_parameter(Name=f'/dataall/{envname}/cognito/userpool')['Parameter']['Value'] log.info(f'Cognito Pool ID: {user_pool_id}') - app_client = ssm.get_parameter(Name=f'/dataall/{envname}/cognito/appclient')['Parameter']['Value'] - - if custom_domain == 'False' and internet_facing == 'True': - log.info('Switching to us-east-1 region...') - ssm = boto3.client('ssm', region_name='us-east-1') - signin_singout_link = ssm.get_parameter(Name=f'/dataall/{envname}/CloudfrontDistributionDomainName')[ - 'Parameter' - ]['Value'] - user_guide_link = ssm.get_parameter( - Name=f'/dataall/{envname}/cloudfront/docs/user/CloudfrontDistributionDomainName' - )['Parameter']['Value'] - else: - signin_singout_link = ssm.get_parameter(Name=f'/dataall/{envname}/frontend/custom_domain_name')['Parameter'][ - 'Value' - ] - user_guide_link = ssm.get_parameter(Name=f'/dataall/{envname}/userguide/custom_domain_name')['Parameter'][ - 'Value' - ] - - log.info(f'UI: {signin_singout_link}') - log.info(f'USERGUIDE: {user_guide_link}') - cognito = boto3.client('cognito-idp', region_name=region) - user_pool = cognito.describe_user_pool_client(UserPoolId=user_pool_id, ClientId=app_client) - - del user_pool['UserPoolClient']['CreationDate'] - del user_pool['UserPoolClient']['LastModifiedDate'] - - config_callbacks = [ - f'https://{signin_singout_link}', - f'https://{user_guide_link}/parseauth', - ] - existing_callbacks = user_pool['UserPoolClient'].get('CallbackURLs', []) - if 'https://example.com' in existing_callbacks: - existing_callbacks.remove('https://example.com') - updated_callbacks = existing_callbacks + list(set(config_callbacks) - set(existing_callbacks)) - log.info(f'Updated CallBackUrls: {updated_callbacks}') - - config_logout_urls = [f'https://{signin_singout_link}'] - existing_logout_urls = user_pool['UserPoolClient'].get('LogoutURLs', []) - updated_logout_urls = existing_logout_urls + list(set(config_logout_urls) - set(existing_logout_urls)) - log.info(f'Updated LogOutUrls: {updated_logout_urls}') - - user_pool['UserPoolClient']['CallbackURLs'] = updated_callbacks - user_pool['UserPoolClient']['LogoutURLs'] = updated_logout_urls - - response = cognito.update_user_pool_client( - **user_pool['UserPoolClient'], - ) - - log.info(f'CallbackUrls and LogOutUrls updated successfully: {response}') try: response = cognito.create_group( @@ -162,8 +110,6 @@ def handler(event, context) -> None: log.info('Starting Cognito Configuration...') envname = os.environ.get('envname') region = os.environ.get('deployment_region') - internet_facing = os.environ.get('internet_facing') - custom_domain = os.environ.get('custom_domain') enable_cw_canaries = os.environ.get('enable_cw_canaries') resource_prefix = os.environ.get('resource_prefix') with_approval_tests = os.environ.get('with_approval_tests') @@ -171,8 +117,6 @@ def handler(event, context) -> None: region, resource_prefix, envname, - internet_facing, - custom_domain, enable_cw_canaries, with_approval_tests, ) diff --git a/deploy/custom_resources/utils.py b/deploy/custom_resources/utils.py new file mode 100644 index 000000000..899b8a6cc --- /dev/null +++ b/deploy/custom_resources/utils.py @@ -0,0 +1,23 @@ +import os + +from aws_cdk import aws_lambda, BundlingOptions +from aws_cdk.aws_lambda import AssetCode + +from stacks.solution_bundling import SolutionBundling + + +def get_lambda_code(path, image=aws_lambda.Runtime.PYTHON_3_9.bundling_image) -> AssetCode: + assets_path = os.path.realpath( + os.path.join( + os.path.dirname(__file__), + path, + ) + ) + + return aws_lambda.Code.from_asset( + path=assets_path, + bundling=BundlingOptions( + image=image, + local=SolutionBundling(source_path=assets_path), + ), + ) diff --git a/deploy/stacks/cloudfront.py b/deploy/stacks/cloudfront.py index 20c004bb8..8a2d0ef15 100644 --- a/deploy/stacks/cloudfront.py +++ b/deploy/stacks/cloudfront.py @@ -15,8 +15,11 @@ RemovalPolicy, CfnOutput, BundlingOptions, + aws_kms, ) +from aws_cdk.triggers import TriggerFunction +from custom_resources.utils import get_lambda_code from .pyNestedStack import pyNestedClass from .solution_bundling import SolutionBundling from .waf_rules import get_waf_rules @@ -34,6 +37,7 @@ def __init__( custom_waf_rules=None, tooling_account_id=None, custom_auth=None, + backend_region=None, **kwargs, ): super().__init__(scope, id, **kwargs) @@ -269,6 +273,9 @@ def __init__( ) ) + if not custom_auth: + self.cognito_urls_config(resource_prefix, envname, backend_region, custom_domain, [cloudfront_distribution]) + CfnOutput( self, 'OutputCfnFrontDistribution', @@ -489,3 +496,94 @@ def error_responses(): response_page_path='/index.html', ), ] + + def cognito_urls_config(self, resource_prefix, envname, backend_region, custom_domain, execute_after): + lambda_env_key = aws_kms.Key( + self, + f'{resource_prefix}-cogn-lambda-env-var-key', + removal_policy=RemovalPolicy.DESTROY, + alias=f'{resource_prefix}-cogn-lambda-env-var-key', + enable_key_rotation=True, + policy=iam.PolicyDocument( + statements=[ + iam.PolicyStatement( + resources=['*'], + effect=iam.Effect.ALLOW, + principals=[ + iam.AccountPrincipal(account_id=self.account), + ], + actions=['kms:*'], + ), + iam.PolicyStatement( + resources=['*'], + effect=iam.Effect.ALLOW, + principals=[ + iam.ServicePrincipal(service='lambda.amazonaws.com'), + ], + actions=['kms:GenerateDataKey*', 'kms:Decrypt'], + ), + ], + ), + ) + + cognito_config_code = get_lambda_code('cognito_config') + + TriggerFunction( + self, + 'TriggerFunction-CognitoUrlsConfig', + function_name=f'{resource_prefix}-{envname}-cognito_urls_config', + description='dataall CognitoUrlsConfig trigger function', + initial_policy=[ + iam.PolicyStatement( + effect=iam.Effect.ALLOW, + actions=[ + 'secretsmanager:DescribeSecret', + 'secretsmanager:GetSecretValue', + 'ssm:GetParameterHistory', + 'ssm:GetParameters', + 'ssm:GetParameter', + 'ssm:GetParametersByPath', + 'kms:Decrypt', + 'kms:GenerateDataKey', + 'kms:DescribeKey', + 'rum:GetAppMonitor', + ], + resources=[ + f'arn:aws:kms:{self.region}:{self.account}:key/*', + f'arn:aws:ssm:*:{self.account}:parameter/*dataall*', + f'arn:aws:secretsmanager:{self.region}:{self.account}:secret:*dataall*', + f'arn:aws:rum:{self.region}:{self.account}:appmonitor/*dataall*', + ], + ), + iam.PolicyStatement( + effect=iam.Effect.ALLOW, + actions=[ + 'cognito-idp:AddCustomAttributes', + 'cognito-idp:UpdateUserPool', + 'cognito-idp:DescribeUserPoolClient', + 'cognito-idp:CreateGroup', + 'cognito-idp:UpdateUserPoolClient', + 'cognito-idp:AdminSetUserPassword', + 'cognito-idp:AdminCreateUser', + 'cognito-idp:DescribeUserPool', + 'cognito-idp:AdminAddUserToGroup', + ], + resources=[f'arn:aws:cognito-idp:{backend_region}:{self.account}:userpool/*'], + ), + ], + code=cognito_config_code, + memory_size=256, + timeout=Duration.minutes(15), + environment={ + 'envname': envname, + 'deployment_region': backend_region, + 'custom_domain': str(bool(custom_domain)), + }, + environment_encryption=lambda_env_key, + tracing=_lambda.Tracing.ACTIVE, + retry_attempts=0, + runtime=_lambda.Runtime.PYTHON_3_9, + handler='cognito_urls.handler', + execute_after=execute_after, + execute_on_handler_change=True, + ) diff --git a/deploy/stacks/cloudfront_stack.py b/deploy/stacks/cloudfront_stack.py index 412a64975..bd1bd4512 100644 --- a/deploy/stacks/cloudfront_stack.py +++ b/deploy/stacks/cloudfront_stack.py @@ -16,6 +16,7 @@ def __init__( custom_domain=None, custom_waf_rules=None, custom_auth=None, + backend_region=None, **kwargs, ): super().__init__(scope, id, **kwargs) @@ -42,5 +43,6 @@ def __init__( custom_domain=custom_domain, custom_waf_rules=custom_waf_rules, custom_auth=custom_auth, + backend_region=backend_region, **kwargs, ) diff --git a/deploy/stacks/cloudfront_stage.py b/deploy/stacks/cloudfront_stage.py index 19a79c2e6..329816834 100644 --- a/deploy/stacks/cloudfront_stage.py +++ b/deploy/stacks/cloudfront_stage.py @@ -16,6 +16,7 @@ def __init__( custom_domain=None, custom_waf_rules=None, custom_auth=None, + backend_region=None, **kwargs, ): super().__init__(scope, id, **kwargs) @@ -29,6 +30,7 @@ def __init__( custom_domain=custom_domain, custom_waf_rules=custom_waf_rules, custom_auth=custom_auth, + backend_region=backend_region, ) Tags.of(cloudfront_stack).add('Application', f'{resource_prefix}-{envname}') diff --git a/deploy/stacks/cognito.py b/deploy/stacks/cognito.py index a0c93d46a..75a218059 100644 --- a/deploy/stacks/cognito.py +++ b/deploy/stacks/cognito.py @@ -17,6 +17,7 @@ from aws_cdk.aws_cognito import AuthFlow from aws_cdk.triggers import TriggerFunction +from custom_resources.utils import get_lambda_code from .pyNestedStack import pyNestedClass from .solution_bundling import SolutionBundling from .waf_rules import get_waf_rules @@ -349,22 +350,7 @@ def __init__( sync_cr.node.add_dependency(domain_name) sync_cr.node.add_dependency(pool_arn) - cognito_config_assets = os.path.realpath( - os.path.join( - os.path.dirname(__file__), - '..', - 'custom_resources', - 'cognito_config', - ) - ) - - cognito_config_code = _lambda.Code.from_asset( - path=cognito_config_assets, - bundling=BundlingOptions( - image=_lambda.Runtime.PYTHON_3_9.bundling_image, - local=SolutionBundling(source_path=cognito_config_assets), - ), - ) + cognito_config_code = get_lambda_code('cognito_config') TriggerFunction( self, @@ -411,8 +397,6 @@ def __init__( environment={ 'envname': envname, 'deployment_region': self.region, - 'internet_facing': str(internet_facing), - 'custom_domain': str(not domain_name), 'enable_cw_canaries': str(enable_cw_rum), 'resource_prefix': resource_prefix, 'with_approval_tests': str(with_approval_tests), @@ -421,7 +405,7 @@ def __init__( tracing=_lambda.Tracing.ACTIVE, retry_attempts=0, runtime=_lambda.Runtime.PYTHON_3_9, - handler='cognito_urls_config.handler', + handler='cognito_users.handler', execute_after=[self.client], execute_on_handler_change=True, ) diff --git a/deploy/stacks/pipeline.py b/deploy/stacks/pipeline.py index 4553826e8..0df007dfc 100644 --- a/deploy/stacks/pipeline.py +++ b/deploy/stacks/pipeline.py @@ -755,6 +755,7 @@ def set_cloudfront_stage(self, target_env): custom_domain=target_env.get('custom_domain'), custom_auth=target_env.get('custom_auth', None), custom_waf_rules=target_env.get('custom_waf_rules', None), + backend_region=target_env.get('region', self.region), ) ) front_stage_actions = (