From 6f3aee3c96b0c999ff41b5eb70ea7154470cdb78 Mon Sep 17 00:00:00 2001 From: Tejas Rajopadhye Date: Fri, 8 Dec 2023 12:33:45 -0600 Subject: [PATCH] Frontend Global Imports, Backend Validation for custom auth params and alb frontend changes as per custom auth --- deploy/stacks/albfront_stack.py | 116 +++++++++--------- deploy/stacks/albfront_stage.py | 2 + deploy/stacks/lambda_api.py | 10 +- deploy/stacks/pipeline.py | 109 +++++++++++----- .../authentication/components/GuestGuard.js | 2 +- frontend/src/authentication/hooks/useToken.js | 2 +- .../components/popovers/AccountPopover.js | 2 +- .../contexts/RequestContext.js | 2 +- .../graphql/Groups/getGroupsForUser.js} | 0 frontend/src/services/graphql/Groups/index.js | 1 + .../graphql/Groups/listCognitoGroups.js | 14 --- frontend/src/services/hooks/useGroups.js | 6 +- 12 files changed, 154 insertions(+), 112 deletions(-) rename frontend/src/{authentication/services/getServiceProviderInfo.js => services/graphql/Groups/getGroupsForUser.js} (100%) delete mode 100644 frontend/src/services/graphql/Groups/listCognitoGroups.js diff --git a/deploy/stacks/albfront_stack.py b/deploy/stacks/albfront_stack.py index 52e2b28c9..cebd7f46d 100644 --- a/deploy/stacks/albfront_stack.py +++ b/deploy/stacks/albfront_stack.py @@ -28,6 +28,7 @@ def __init__( image_tag=None, custom_domain=None, ip_ranges=None, + custom_auth=None, **kwargs, ): super().__init__(scope, id, **kwargs) @@ -220,65 +221,66 @@ def __init__( ) self.allow_alb_access(frontend_alb, ip_ranges, vpc) - userguide_sg = ec2.SecurityGroup( - self, - 'FargateTaskUserGuideSG', - security_group_name=f'{resource_prefix}-{envname}-userguide-service-sg', - vpc=vpc, - allow_all_outbound=True, - ) - userguide_alb = ecs_patterns.ApplicationLoadBalancedFargateService( - self, - f'UserGuideService{envname}', - cluster=cluster, - cpu=1024, - memory_limit_mib=2048, - service_name=f'userguide-{envname}', - desired_count=1, - certificate=certificate if (custom_domain and custom_domain.get('certificate_arn')) else None, - domain_name=userguide_alternate_domain, - domain_zone=hosted_zone, - task_image_options=ecs_patterns.ApplicationLoadBalancedTaskImageOptions( - container_port=80, - environment={ - 'AWS_REGION': self.region, - 'envname': envname, - 'LOGLEVEL': 'DEBUG', - }, - task_role=task_role, - image=ecs.ContainerImage.from_ecr_repository( - repository=ecr_repository, tag=userguide_image_tag - ), - enable_logging=True, - log_driver=ecs.LogDriver.aws_logs( - stream_prefix='service', - log_group=self.create_log_group( - envname, resource_prefix, log_group_name='userguide' + if custom_auth is None: + userguide_sg = ec2.SecurityGroup( + self, + 'FargateTaskUserGuideSG', + security_group_name=f'{resource_prefix}-{envname}-userguide-service-sg', + vpc=vpc, + allow_all_outbound=True, + ) + userguide_alb = ecs_patterns.ApplicationLoadBalancedFargateService( + self, + f'UserGuideService{envname}', + cluster=cluster, + cpu=1024, + memory_limit_mib=2048, + service_name=f'userguide-{envname}', + desired_count=1, + certificate=certificate if (custom_domain and custom_domain.get('certificate_arn')) else None, + domain_name=userguide_alternate_domain, + domain_zone=hosted_zone, + task_image_options=ecs_patterns.ApplicationLoadBalancedTaskImageOptions( + container_port=80, + environment={ + 'AWS_REGION': self.region, + 'envname': envname, + 'LOGLEVEL': 'DEBUG', + }, + task_role=task_role, + image=ecs.ContainerImage.from_ecr_repository( + repository=ecr_repository, tag=userguide_image_tag + ), + enable_logging=True, + log_driver=ecs.LogDriver.aws_logs( + stream_prefix='service', + log_group=self.create_log_group( + envname, resource_prefix, log_group_name='userguide' + ), ), ), - ), - public_load_balancer=False, - assign_public_ip=False, - open_listener=False, - max_healthy_percent=100, - min_healthy_percent=0, - security_groups=[userguide_sg], - ) - ulb: elb.CfnLoadBalancer = userguide_alb.load_balancer.node.default_child - ulb.access_logging_policy = elb.CfnLoadBalancer.AccessLoggingPolicyProperty( - enabled=True, - s3_bucket_name=logs_bucket.bucket_name, - s3_bucket_prefix='userguide', - ) - userguide_alb.target_group.configure_health_check( - port='80', - path='/', - timeout=Duration.seconds(10), - healthy_threshold_count=2, - unhealthy_threshold_count=2, - interval=Duration.seconds(15), - ) - self.allow_alb_access(userguide_alb, ip_ranges, vpc) + public_load_balancer=False, + assign_public_ip=False, + open_listener=False, + max_healthy_percent=100, + min_healthy_percent=0, + security_groups=[userguide_sg], + ) + ulb: elb.CfnLoadBalancer = userguide_alb.load_balancer.node.default_child + ulb.access_logging_policy = elb.CfnLoadBalancer.AccessLoggingPolicyProperty( + enabled=True, + s3_bucket_name=logs_bucket.bucket_name, + s3_bucket_prefix='userguide', + ) + userguide_alb.target_group.configure_health_check( + port='80', + path='/', + timeout=Duration.seconds(10), + healthy_threshold_count=2, + unhealthy_threshold_count=2, + interval=Duration.seconds(15), + ) + self.allow_alb_access(userguide_alb, ip_ranges, vpc) CfnOutput( self, diff --git a/deploy/stacks/albfront_stage.py b/deploy/stacks/albfront_stage.py index 3b641101e..6c84f4a21 100644 --- a/deploy/stacks/albfront_stage.py +++ b/deploy/stacks/albfront_stage.py @@ -16,6 +16,7 @@ def __init__( image_tag=None, custom_domain=None, ip_ranges=None, + custom_auth=None, **kwargs, ): super().__init__(scope, id, **kwargs) @@ -29,6 +30,7 @@ def __init__( image_tag=image_tag, custom_domain=custom_domain, ip_ranges=ip_ranges, + custom_auth=custom_auth ) Tags.of(albfront_stack).add('Application', f'{resource_prefix}-{envname}') diff --git a/deploy/stacks/lambda_api.py b/deploy/stacks/lambda_api.py index 4ec0cdb64..137bf2b79 100644 --- a/deploy/stacks/lambda_api.py +++ b/deploy/stacks/lambda_api.py @@ -183,7 +183,7 @@ def __init__( for claims_map in custom_auth.get('claims_mapping', {}): custom_lambda_env[claims_map] = custom_auth.get('claims_mapping', '').get(claims_map, '') - authorizerfn_sg = self.create_lambda_sgs(envname, "customauthorizer", resource_prefix, vpc) + authorizer_fn_sg = self.create_lambda_sgs(envname, "customauthorizer", resource_prefix, vpc) self.authorizer_fn = _lambda.Function( self, f'CustomAuthorizerFunction-{envname}', @@ -200,9 +200,9 @@ def __init__( description='dataall Custom authorizer replacing cognito authorizer', timeout=Duration.seconds(20), environment=custom_lambda_env, - security_groups=[authorizerfn_sg], vpc=vpc, - runtime=_lambda.Runtime.PYTHON_3_9, + security_groups=[authorizer_fn_sg], + runtime=_lambda.Runtime.PYTHON_3_9 ) # Add NAT Connectivity For Custom Authorizer Lambda @@ -513,6 +513,7 @@ def set_up_graphql_api_gateway( #Create a custom Authorizer custom_authorizer_role = iam.Role(self, f'{resource_prefix}-{envname}-custom-authorizer-role', + role_name=f'{resource_prefix}-{envname}-custom-authorizer-role', assumed_by=iam.ServicePrincipal("apigateway.amazonaws.com"), description="Allow Custom Authorizer to call custom auth lambda" ) @@ -528,7 +529,8 @@ def set_up_graphql_api_gateway( handler=self.authorizer_fn, identity_sources=[apigw.IdentitySource.header('Authorization')], authorizer_name=f'{resource_prefix}-{envname}-custom-authorizer', - assume_role=custom_authorizer_role + assume_role=custom_authorizer_role, + results_cache_ttl=Duration.minutes(60), ) if not internet_facing: if apig_vpce: diff --git a/deploy/stacks/pipeline.py b/deploy/stacks/pipeline.py index c14d7e4fc..5a8564437 100644 --- a/deploy/stacks/pipeline.py +++ b/deploy/stacks/pipeline.py @@ -422,6 +422,39 @@ def validate_deployment_params(self, source, repo_connection_arn, git_branch, re f'must be less than 50 characters to avoid AWS resources naming limits' ) + # Validate if all configs are present when deploying custom_auth + for env in target_envs: + if 'custom_auth' in env: + custom_auth_configs = env.get('custom_auth') + if ('url' not in custom_auth_configs or + 'provider' not in custom_auth_configs or + 'redirect_url' not in custom_auth_configs or + 'client_id' not in custom_auth_configs or + 'response_types' not in custom_auth_configs or + 'scopes' not in custom_auth_configs or + 'jwks_url' not in custom_auth_configs or + 'claims_mapping' not in custom_auth_configs or + 'user_id' not in custom_auth_configs['claims_mapping'] or + 'email' not in custom_auth_configs['claims_mapping'] + ): + raise ValueError( + 'Custom Auth Configuration Error : Missing some configurations in custom_auth section in Deployments. Please take a look at template_cdk.json for reference or visit the data.all webpage and checkout the Deploy to AWS section' + ) + + if (not isinstance(custom_auth_configs['url'], str) or + not isinstance(custom_auth_configs['provider'], str) or + not isinstance(custom_auth_configs['redirect_url'], str) or + not isinstance(custom_auth_configs['client_id'], str) or + not isinstance(custom_auth_configs['response_types'], str) or + not isinstance(custom_auth_configs['scopes'], str) or + not isinstance(custom_auth_configs['jwks_url'], str) or + not isinstance(custom_auth_configs['claims_mapping']['user_id'], str) or + not isinstance(custom_auth_configs['claims_mapping']['email'], str) + ): + raise TypeError( + 'Custom Auth Configuration Error : Type error: Configs type is not as required. Please take a look at template_cdk.json for reference or visit the data.all webpage and checkout the Deploy to AWS section' + ) + def set_quality_gate_stage(self): quality_gate_param = self.node.try_get_context('quality_gate') if quality_gate_param is not False: @@ -636,7 +669,7 @@ def set_backend_stage(self, target_env, repository_name): apig_vpce=target_env.get('apig_vpce'), prod_sizing=target_env.get('prod_sizing', True), quicksight_enabled=target_env.get('enable_quicksight_monitoring', False), - enable_cw_rum=target_env.get('enable_cw_rum', False), + enable_cw_rum=target_env.get('enable_cw_rum', False) and target_env.get("custom_auth", None) == None, enable_cw_canaries=target_env.get('enable_cw_canaries', False) and target_env.get("custom_auth", None) == None, shared_dashboard_sessions=target_env.get('shared_dashboard_sessions', 'anonymous'), enable_opensearch_serverless=target_env.get('enable_opensearch_serverless', False), @@ -741,7 +774,7 @@ def set_cloudfront_stage(self, target_env): f'export internet_facing={target_env.get("internet_facing", True)}', f'export custom_domain={str(True) if target_env.get("custom_domain") else str(False)}', f'export deployment_region={target_env.get("region", self.region)}', - f'export enable_cw_rum={target_env.get("enable_cw_rum", False)}', + f'export enable_cw_rum={target_env.get("enable_cw_rum", False) and target_env.get("custom_auth", None) == None }', f'export resource_prefix={self.resource_prefix}', f'export reauth_ttl={str(target_env.get("reauth_config", {}).get("ttl", 5))}', f'export custom_auth_provider={str(target_env.get("custom_auth", {}).get("provider", "None"))}', @@ -781,7 +814,7 @@ def set_cloudfront_stage(self, target_env): *front_stage_actions, self.cognito_config_action(target_env), ) - if target_env.get('enable_cw_rum', False): + if target_env.get('enable_cw_rum', False) and target_env.get("custom_auth", None) == None: front_stage_actions = ( *front_stage_actions, self.cw_rum_config_action(target_env), @@ -885,6 +918,7 @@ def set_albfront_stage(self, target_env, repository_name): custom_domain=target_env['custom_domain'], ip_ranges=target_env.get('ip_ranges'), resource_prefix=self.resource_prefix, + custom_auth=target_env.get('custom_auth', None) ), pre=[ pipelines.CodeBuildStep( @@ -907,8 +941,18 @@ def set_albfront_stage(self, target_env, repository_name): f'export internet_facing={target_env.get("internet_facing", False)}', f'export custom_domain=True', f'export deployment_region={target_env.get("region", self.region)}', - f'export enable_cw_rum={target_env.get("enable_cw_rum", False)}', + f'export enable_cw_rum={target_env.get("enable_cw_rum", False) and target_env.get("custom_auth", None) == None}', f'export resource_prefix={self.resource_prefix}', + f'export reauth_ttl={str(target_env.get("reauth_config", {}).get("ttl", 5))}', + f'export custom_auth_provider={str(target_env.get("custom_auth", {}).get("provider", "None"))}', + f'export custom_auth_url={str(target_env.get("custom_auth", {}).get("url", "None"))}', + f'export custom_auth_redirect_url={str(target_env.get("custom_auth", {}).get("redirect_url", "None"))}', + f'export custom_auth_client_id={str(target_env.get("custom_auth", {}).get("client_id", "None"))}', + f'export custom_auth_response_types={str(target_env.get("custom_auth", {}).get("response_types", "None"))}', + f'export custom_auth_scopes={str(target_env.get("custom_auth", {}).get("scopes", "None"))}', + f'export custom_auth_claims_mapping_email={str(target_env.get("custom_auth", {}).get("claims_mapping", {}).get("email", "None"))}', + f'export custom_auth_claims_mapping_user_id={str(target_env.get("custom_auth", {}).get("claims_mapping", {}).get("user_id", "None"))}', + ## Ask can we make the .aws file with a permission of 400 'mkdir ~/.aws/ && touch ~/.aws/config', 'echo "[profile buildprofile]" > ~/.aws/config', f'echo "role_arn = arn:aws:iam::{target_env["account"]}:role/{self.resource_prefix}-{target_env["envname"]}-cognito-config-role" >> ~/.aws/config', @@ -926,39 +970,44 @@ def set_albfront_stage(self, target_env, repository_name): ], role=self.expanded_codebuild_role.without_policy_updates(), vpc=self.vpc, - ), - pipelines.CodeBuildStep( - id='UserGuideImage', - build_environment=codebuild.BuildEnvironment( - build_image=codebuild.LinuxBuildImage.AMAZON_LINUX_2_4, - compute_type=codebuild.ComputeType.LARGE, - privileged=True, - environment_variables={ - 'REPOSITORY_URI': codebuild.BuildEnvironmentVariable( - value=f'{self.account}.dkr.ecr.{self.region}.amazonaws.com/{repository_name}' - ), - 'IMAGE_TAG': codebuild.BuildEnvironmentVariable(value=f'userguide-{self.image_tag}'), - }, - ), - commands=[ - f'aws codeartifact login --tool pip --repository {self.codeartifact.codeartifact_pip_repo_name} --domain {self.codeartifact.codeartifact_domain_name} --domain-owner {self.codeartifact.domain.attr_owner}', - 'cd documentation/userguide', - 'docker build -f docker/prod/Dockerfile -t $IMAGE_TAG:$IMAGE_TAG .', - f'aws ecr get-login-password --region {self.region} | docker login --username AWS --password-stdin {self.account}.dkr.ecr.{self.region}.amazonaws.com', - 'docker tag $IMAGE_TAG:$IMAGE_TAG $REPOSITORY_URI:$IMAGE_TAG', - 'docker push $REPOSITORY_URI:$IMAGE_TAG', - ], - role=self.expanded_codebuild_role.without_policy_updates(), - vpc=self.vpc, - ), + ) ] ) + if target_env.get('custom_auth') is None: + albfront_stage.add_pre(self.user_guide_pre_build_alb(repository_name)) + if target_env.get('custom_auth') is None: albfront_stage.add_post(self.cognito_config_action(target_env)) - if target_env.get('enable_cw_rum', False): + if target_env.get('enable_cw_rum', False) and target_env.get("custom_auth", None) == None: albfront_stage.add_post(self.cw_rum_config_action(target_env)) + def user_guide_pre_build_alb(self, repository_name): + return pipelines.CodeBuildStep( + id='UserGuideImage', + build_environment=codebuild.BuildEnvironment( + build_image=codebuild.LinuxBuildImage.AMAZON_LINUX_2_4, + compute_type=codebuild.ComputeType.LARGE, + privileged=True, + environment_variables={ + 'REPOSITORY_URI': codebuild.BuildEnvironmentVariable( + value=f'{self.account}.dkr.ecr.{self.region}.amazonaws.com/{repository_name}' + ), + 'IMAGE_TAG': codebuild.BuildEnvironmentVariable(value=f'userguide-{self.image_tag}'), + }, + ), + commands=[ + f'aws codeartifact login --tool pip --repository {self.codeartifact.codeartifact_pip_repo_name} --domain {self.codeartifact.codeartifact_domain_name} --domain-owner {self.codeartifact.domain.attr_owner}', + 'cd documentation/userguide', + 'docker build -f docker/prod/Dockerfile -t $IMAGE_TAG:$IMAGE_TAG .', + f'aws ecr get-login-password --region {self.region} | docker login --username AWS --password-stdin {self.account}.dkr.ecr.{self.region}.amazonaws.com', + 'docker tag $IMAGE_TAG:$IMAGE_TAG $REPOSITORY_URI:$IMAGE_TAG', + 'docker push $REPOSITORY_URI:$IMAGE_TAG', + ], + role=self.expanded_codebuild_role.without_policy_updates(), + vpc=self.vpc, + ) + def set_release_stage( self, ): diff --git a/frontend/src/authentication/components/GuestGuard.js b/frontend/src/authentication/components/GuestGuard.js index b61ce8501..27cfa0229 100644 --- a/frontend/src/authentication/components/GuestGuard.js +++ b/frontend/src/authentication/components/GuestGuard.js @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; import { Navigate } from 'react-router-dom'; -import { useAuth } from '../hooks/useAuth'; +import { useAuth } from 'authentication'; export const GuestGuard = ({ children }) => { const { isAuthenticated } = useAuth(); diff --git a/frontend/src/authentication/hooks/useToken.js b/frontend/src/authentication/hooks/useToken.js index 22c51dcb5..08cfd5668 100644 --- a/frontend/src/authentication/hooks/useToken.js +++ b/frontend/src/authentication/hooks/useToken.js @@ -1,7 +1,7 @@ import { Auth } from 'aws-amplify'; import { useEffect, useState } from 'react'; import { SET_ERROR, useDispatch } from 'globalErrors'; -import { useAuth } from './useAuth'; +import { useAuth } from 'authentication'; export const useToken = () => { const dispatch = useDispatch(); diff --git a/frontend/src/design/components/popovers/AccountPopover.js b/frontend/src/design/components/popovers/AccountPopover.js index 58c5e61b3..eefacaa72 100644 --- a/frontend/src/design/components/popovers/AccountPopover.js +++ b/frontend/src/design/components/popovers/AccountPopover.js @@ -15,7 +15,7 @@ import { Link as RouterLink, useNavigate } from 'react-router-dom'; import { useGroups } from 'services'; import { CogIcon } from '../../icons'; import { TextAvatar } from '../TextAvatar'; -import { useAuth } from '../../../authentication'; +import { useAuth } from 'authentication'; export const AccountPopover = () => { const anchorRef = useRef(null); diff --git a/frontend/src/reauthentication/contexts/RequestContext.js b/frontend/src/reauthentication/contexts/RequestContext.js index fce9f44a4..6986d5db3 100644 --- a/frontend/src/reauthentication/contexts/RequestContext.js +++ b/frontend/src/reauthentication/contexts/RequestContext.js @@ -6,7 +6,7 @@ import { print } from 'graphql/language'; import { useNavigate } from 'react-router'; import { useSnackbar } from 'notistack'; import { Auth } from 'aws-amplify'; -import { useAuth, useToken } from '../../authentication'; +import { useAuth, useToken } from 'authentication'; // Create a context for API request headers const RequestContext = createContext(); diff --git a/frontend/src/authentication/services/getServiceProviderInfo.js b/frontend/src/services/graphql/Groups/getGroupsForUser.js similarity index 100% rename from frontend/src/authentication/services/getServiceProviderInfo.js rename to frontend/src/services/graphql/Groups/getGroupsForUser.js diff --git a/frontend/src/services/graphql/Groups/index.js b/frontend/src/services/graphql/Groups/index.js index 07e00aa43..f1bce30a4 100644 --- a/frontend/src/services/graphql/Groups/index.js +++ b/frontend/src/services/graphql/Groups/index.js @@ -1 +1,2 @@ export * from './listGroups'; +export * from './getGroupsForUser'; diff --git a/frontend/src/services/graphql/Groups/listCognitoGroups.js b/frontend/src/services/graphql/Groups/listCognitoGroups.js deleted file mode 100644 index a634106e4..000000000 --- a/frontend/src/services/graphql/Groups/listCognitoGroups.js +++ /dev/null @@ -1,14 +0,0 @@ -import { gql } from 'apollo-boost'; - -export const listCognitoGroups = ({ filter }) => ({ - variables: { - filter - }, - query: gql` - query listCognitoGroups($filter: CognitoGroupFilter) { - listCognitoGroups(filter: $filter) { - groupName - } - } - ` -}); diff --git a/frontend/src/services/hooks/useGroups.js b/frontend/src/services/hooks/useGroups.js index dba21b7ec..f175baa54 100644 --- a/frontend/src/services/hooks/useGroups.js +++ b/frontend/src/services/hooks/useGroups.js @@ -1,9 +1,9 @@ import { Auth } from 'aws-amplify'; import { useEffect, useState } from 'react'; import { SET_ERROR, useDispatch } from 'globalErrors'; -import { useClient } from './useClient'; -import { useAuth } from '../../authentication'; -import { getGroupsForUser } from '../../authentication/services/getServiceProviderInfo'; +import { useClient } from 'services'; +import { useAuth } from 'authentication'; +import { getGroupsForUser } from 'services'; export const useGroups = () => { const dispatch = useDispatch();