diff --git a/backend/local.graphql.server.py b/backend/local.graphql.server.py index 47f4e7112..39435cb82 100644 --- a/backend/local.graphql.server.py +++ b/backend/local.graphql.server.py @@ -55,7 +55,7 @@ def request_context(headers, mock=False): if not headers.get('Authorization'): raise Exception('Missing Authorization header') try: - decoded = jwt.decode(headers.get('Authorization'), verify=False) + decoded = jwt.decode(headers.get('Authorization'), options={"verify_signature": False}) username = decoded.get('email', 'anonymous') groups = [] saml_groups = decoded.get('custom:saml.groups', []) diff --git a/cdk.json b/cdk.json index 892c04e15..b185b217f 100644 --- a/cdk.json +++ b/cdk.json @@ -24,6 +24,10 @@ "hosted_zone_name": "string_ROUTE_53_EXISTING_DOMAIN_NAME|DEFAULT=None, REQUIRED if internet_facing=false", "hosted_zone_id": "string_ROUTE_53_EXISTING_HOSTED_ZONE_ID|DEFAULT=None, REQUIRED if internet_facing=false" }, + "custom_waf_rules": { + "allowed_geo_list": "list_of_strings_COUNTRIES_CODE_ALLOWED_THROUGH_WAF_RULE", + "allowed_ip_list": "list_of_strings_IP_ADDRESSES_ALLOWED_THROUGH_WAF_RULE" + }, "ip_ranges": "list_of_strings_IP_RANGES_TO_ALLOW_IF_NOT_INTERNET_FACING|DEFAULT=None", "apig_vpce": "string_USE_AN_EXISTING_VPCE_FOR_APIG_IF_NOT_INTERNET_FACING|DEFAULT=None", "prod_sizing": "boolean_SET_INFRA_SIZING_TO_PROD_VALUES_IF_TRUE|DEFAULT=true", diff --git a/deploy/stacks/backend_stack.py b/deploy/stacks/backend_stack.py index 108034dc7..3c1db5ce5 100644 --- a/deploy/stacks/backend_stack.py +++ b/deploy/stacks/backend_stack.py @@ -34,6 +34,7 @@ def __init__( vpc_endpoints_sg=None, internet_facing=True, custom_domain=None, + custom_waf_rules=None, ip_ranges=None, apig_vpce=None, prod_sizing=False, @@ -116,6 +117,7 @@ def __init__( image_tag=image_tag, ecr_repository=repo, internet_facing=internet_facing, + custom_waf_rules=custom_waf_rules, ip_ranges=ip_ranges, apig_vpce=apig_vpce, prod_sizing=prod_sizing, diff --git a/deploy/stacks/backend_stage.py b/deploy/stacks/backend_stage.py index 9371357db..e4088709e 100644 --- a/deploy/stacks/backend_stage.py +++ b/deploy/stacks/backend_stage.py @@ -20,6 +20,7 @@ def __init__( vpc_endpoints_sg=None, internet_facing=True, custom_domain=None, + custom_waf_rules=None, ip_ranges=None, apig_vpce=None, prod_sizing=False, @@ -42,6 +43,7 @@ def __init__( vpc_endpoints_sg=vpc_endpoints_sg, internet_facing=internet_facing, custom_domain=custom_domain, + custom_waf_rules=custom_waf_rules, ip_ranges=ip_ranges, apig_vpce=apig_vpce, prod_sizing=prod_sizing, diff --git a/deploy/stacks/cloudfront.py b/deploy/stacks/cloudfront.py index d732b6e54..b44487585 100644 --- a/deploy/stacks/cloudfront.py +++ b/deploy/stacks/cloudfront.py @@ -20,7 +20,6 @@ from .pyNestedStack import pyNestedClass from .solution_bundling import SolutionBundling - class CloudfrontDistro(pyNestedClass): def __init__( self, @@ -30,11 +29,74 @@ def __init__( resource_prefix='dataall', auth_at_edge=None, custom_domain=None, + custom_waf_rules=None, tooling_account_id=None, **kwargs, ): super().__init__(scope, id, **kwargs) + + # Create IP set if IP filtering enabled + ip_set_cloudfront=None + if custom_waf_rules and custom_waf_rules.get("allowed_ip_list"): + ip_set_cloudfront = wafv2.CfnIPSet( + self, + "DataallCloudfrontIPSet", + name=f"{resource_prefix}-{envname}-ipset-cloudfront", + description=f"IP addresses to allow for Dataall {envname}", + addresses=custom_waf_rules.get("allowed_ip_list"), + ip_address_version="IPV4", + scope="CLOUDFRONT" + ) + waf_rules = [] + priority = 0 + if custom_waf_rules: + if custom_waf_rules.get("allowed_geo_list"): + waf_rules.append( + wafv2.CfnWebACL.RuleProperty( + name='GeoMatch', + statement=wafv2.CfnWebACL.StatementProperty( + not_statement=wafv2.CfnWebACL.NotStatementProperty( + statement=wafv2.CfnWebACL.StatementProperty( + geo_match_statement=wafv2.CfnWebACL.GeoMatchStatementProperty( + country_codes=custom_waf_rules.get("allowed_geo_list") + ) + ) + ) + ), + action=wafv2.CfnWebACL.RuleActionProperty(block={}), + visibility_config=wafv2.CfnWebACL.VisibilityConfigProperty( + sampled_requests_enabled=True, + cloud_watch_metrics_enabled=True, + metric_name='GeoMatch', + ), + priority=priority, + ) + ) + priority += 1 + if custom_waf_rules.get("allowed_ip_list"): + waf_rules.append( + wafv2.CfnWebACL.RuleProperty( + name='IPMatch', + statement=wafv2.CfnWebACL.StatementProperty( + not_statement=wafv2.CfnWebACL.NotStatementProperty( + statement=wafv2.CfnWebACL.StatementProperty( + ip_set_reference_statement={ + "arn" : ip_set_cloudfront.attr_arn + } + ) + ) + ), + action=wafv2.CfnWebACL.RuleActionProperty(block={}), + visibility_config=wafv2.CfnWebACL.VisibilityConfigProperty( + sampled_requests_enabled=True, + cloud_watch_metrics_enabled=True, + metric_name='IPMatch', + ), + priority=priority, + ) + ) + priority += 1 waf_rules.append( wafv2.CfnWebACL.RuleProperty( name='AWS-AWSManagedRulesAdminProtectionRuleSet', @@ -48,10 +110,11 @@ def __init__( cloud_watch_metrics_enabled=True, metric_name='AWS-AWSManagedRulesAdminProtectionRuleSet', ), - priority=0, + priority=priority, override_action=wafv2.CfnWebACL.OverrideActionProperty(none={}), ) ) + priority += 1 waf_rules.append( wafv2.CfnWebACL.RuleProperty( name='AWS-AWSManagedRulesAmazonIpReputationList', @@ -65,10 +128,11 @@ def __init__( cloud_watch_metrics_enabled=True, metric_name='AWS-AWSManagedRulesAmazonIpReputationList', ), - priority=1, + priority=priority, override_action=wafv2.CfnWebACL.OverrideActionProperty(none={}), ) ) + priority += 1 waf_rules.append( wafv2.CfnWebACL.RuleProperty( name='AWS-AWSManagedRulesCommonRuleSet', @@ -82,10 +146,11 @@ def __init__( cloud_watch_metrics_enabled=True, metric_name='AWS-AWSManagedRulesCommonRuleSet', ), - priority=2, + priority=priority, override_action=wafv2.CfnWebACL.OverrideActionProperty(none={}), ) ) + priority += 1 waf_rules.append( wafv2.CfnWebACL.RuleProperty( name='AWS-AWSManagedRulesKnownBadInputsRuleSet', @@ -99,10 +164,11 @@ def __init__( cloud_watch_metrics_enabled=True, metric_name='AWS-AWSManagedRulesKnownBadInputsRuleSet', ), - priority=3, + priority=priority, override_action=wafv2.CfnWebACL.OverrideActionProperty(none={}), ) ) + priority += 1 waf_rules.append( wafv2.CfnWebACL.RuleProperty( name='AWS-AWSManagedRulesLinuxRuleSet', @@ -116,10 +182,11 @@ def __init__( cloud_watch_metrics_enabled=True, metric_name='AWS-AWSManagedRulesLinuxRuleSet', ), - priority=4, + priority=priority, override_action=wafv2.CfnWebACL.OverrideActionProperty(none={}), ) ) + priority += 1 waf_rules.append( wafv2.CfnWebACL.RuleProperty( name='AWS-AWSManagedRulesSQLiRuleSet', @@ -133,10 +200,11 @@ def __init__( cloud_watch_metrics_enabled=True, metric_name='AWS-AWSManagedRulesSQLiRuleSet', ), - priority=5, + priority=priority, override_action=wafv2.CfnWebACL.OverrideActionProperty(none={}), ) ) + acl = wafv2.CfnWebACL( self, 'ACL-Cloudfront', diff --git a/deploy/stacks/cloudfront_stack.py b/deploy/stacks/cloudfront_stack.py index 8acde5acf..eb9454ce9 100644 --- a/deploy/stacks/cloudfront_stack.py +++ b/deploy/stacks/cloudfront_stack.py @@ -14,6 +14,7 @@ def __init__( resource_prefix='dataall', tooling_account_id=None, custom_domain=None, + custom_waf_rules=None, **kwargs, ): super().__init__(scope, id, **kwargs) @@ -34,5 +35,6 @@ def __init__( auth_at_edge=auth_at_edge, tooling_account_id=tooling_account_id, custom_domain=custom_domain, + custom_waf_rules=custom_waf_rules, **kwargs, ) diff --git a/deploy/stacks/cloudfront_stage.py b/deploy/stacks/cloudfront_stage.py index 753aa011d..0a808a690 100644 --- a/deploy/stacks/cloudfront_stage.py +++ b/deploy/stacks/cloudfront_stage.py @@ -14,6 +14,7 @@ def __init__( resource_prefix='dataall', tooling_account_id=None, custom_domain=None, + custom_waf_rules=None, **kwargs, ): super().__init__(scope, id, **kwargs) @@ -25,6 +26,7 @@ def __init__( resource_prefix=resource_prefix, tooling_account_id=tooling_account_id, custom_domain=custom_domain, + custom_waf_rules=custom_waf_rules, ) Tags.of(cloudfront_stack).add('Application', f'{resource_prefix}-{envname}') diff --git a/deploy/stacks/lambda_api.py b/deploy/stacks/lambda_api.py index 967acdbaa..e562ee376 100644 --- a/deploy/stacks/lambda_api.py +++ b/deploy/stacks/lambda_api.py @@ -27,7 +27,6 @@ from .pyNestedStack import pyNestedClass - class LambdaApiStack(pyNestedClass): def __init__( self, @@ -40,6 +39,7 @@ def __init__( ecr_repository=None, image_tag=None, internet_facing=True, + custom_waf_rules=None, ip_ranges=None, apig_vpce=None, prod_sizing=False, @@ -127,6 +127,7 @@ def __init__( apig_vpce, envname, internet_facing, + custom_waf_rules, ip_ranges, resource_prefix, vpc, @@ -260,6 +261,7 @@ def create_api_gateway( apig_vpce, envname, internet_facing, + custom_waf_rules, ip_ranges, resource_prefix, vpc, @@ -289,6 +291,19 @@ def create_api_gateway( user_pool, ) + # Create IP set if IP filtering enabled in CDK.json + ip_set_regional=None + if custom_waf_rules and custom_waf_rules.get("allowed_ip_list"): + ip_set_regional = wafv2.CfnIPSet( + self, + "DataallRegionalIPSet", + name=f"{resource_prefix}-{envname}-ipset-regional", + description=f"IP addresses allowed for Dataall {envname}", + addresses=custom_waf_rules.get("allowed_ip_list"), + ip_address_version="IPV4", + scope="REGIONAL" + ) + acl = wafv2.CfnWebACL( self, 'ACL-ApiGW', @@ -299,7 +314,7 @@ def create_api_gateway( metric_name='waf-apigw', sampled_requests_enabled=True, ), - rules=self.get_waf_rules(envname), + rules=self.get_waf_rules(envname,custom_waf_rules,ip_set_regional), ) wafv2.CfnWebACLAssociation( @@ -580,8 +595,56 @@ def get_api_resource_policy(vpc, ip_ranges): return api_policy @staticmethod - def get_waf_rules(envname): + def get_waf_rules(envname,custom_waf_rules=None,ip_set_regional=None): waf_rules = [] + priority = 0 + if custom_waf_rules: + if custom_waf_rules.get("allowed_geo_list"): + waf_rules.append( + wafv2.CfnWebACL.RuleProperty( + name='GeoMatch', + statement=wafv2.CfnWebACL.StatementProperty( + not_statement=wafv2.CfnWebACL.NotStatementProperty( + statement=wafv2.CfnWebACL.StatementProperty( + geo_match_statement=wafv2.CfnWebACL.GeoMatchStatementProperty( + country_codes=custom_waf_rules.get("allowed_geo_list") + ) + ) + ) + ), + action=wafv2.CfnWebACL.RuleActionProperty(block={}), + visibility_config=wafv2.CfnWebACL.VisibilityConfigProperty( + sampled_requests_enabled=True, + cloud_watch_metrics_enabled=True, + metric_name='GeoMatch', + ), + priority=priority, + ) + ) + priority += 1 + if custom_waf_rules.get("allowed_ip_list"): + waf_rules.append( + wafv2.CfnWebACL.RuleProperty( + name='IPMatch', + statement=wafv2.CfnWebACL.StatementProperty( + not_statement=wafv2.CfnWebACL.NotStatementProperty( + statement=wafv2.CfnWebACL.StatementProperty( + ip_set_reference_statement={ + "arn" : ip_set_regional.attr_arn + } + ) + ) + ), + action=wafv2.CfnWebACL.RuleActionProperty(block={}), + visibility_config=wafv2.CfnWebACL.VisibilityConfigProperty( + sampled_requests_enabled=True, + cloud_watch_metrics_enabled=True, + metric_name='IPMatch', + ), + priority=priority, + ) + ) + priority += 1 waf_rules.append( wafv2.CfnWebACL.RuleProperty( name='AWS-AWSManagedRulesAdminProtectionRuleSet', @@ -595,10 +658,11 @@ def get_waf_rules(envname): cloud_watch_metrics_enabled=True, metric_name='AWS-AWSManagedRulesAdminProtectionRuleSet', ), - priority=0, + priority=priority, override_action=wafv2.CfnWebACL.OverrideActionProperty(none={}), ) ) + priority += 1 waf_rules.append( wafv2.CfnWebACL.RuleProperty( name='AWS-AWSManagedRulesAmazonIpReputationList', @@ -612,10 +676,11 @@ def get_waf_rules(envname): cloud_watch_metrics_enabled=True, metric_name='AWS-AWSManagedRulesAmazonIpReputationList', ), - priority=1, + priority=priority, override_action=wafv2.CfnWebACL.OverrideActionProperty(none={}), ) ) + priority += 1 waf_rules.append( wafv2.CfnWebACL.RuleProperty( name='AWS-AWSManagedRulesCommonRuleSet', @@ -629,10 +694,11 @@ def get_waf_rules(envname): cloud_watch_metrics_enabled=True, metric_name='AWS-AWSManagedRulesCommonRuleSet', ), - priority=2, + priority=priority, override_action=wafv2.CfnWebACL.OverrideActionProperty(none={}), ) ) + priority += 1 waf_rules.append( wafv2.CfnWebACL.RuleProperty( name='AWS-AWSManagedRulesKnownBadInputsRuleSet', @@ -646,10 +712,11 @@ def get_waf_rules(envname): cloud_watch_metrics_enabled=True, metric_name='AWS-AWSManagedRulesKnownBadInputsRuleSet', ), - priority=3, + priority=priority, override_action=wafv2.CfnWebACL.OverrideActionProperty(none={}), ) ) + priority += 1 waf_rules.append( wafv2.CfnWebACL.RuleProperty( name='AWS-AWSManagedRulesLinuxRuleSet', @@ -663,10 +730,11 @@ def get_waf_rules(envname): cloud_watch_metrics_enabled=True, metric_name='AWS-AWSManagedRulesLinuxRuleSet', ), - priority=4, + priority=priority, override_action=wafv2.CfnWebACL.OverrideActionProperty(none={}), ) ) + priority += 1 waf_rules.append( wafv2.CfnWebACL.RuleProperty( name='AWS-AWSManagedRulesSQLiRuleSet', @@ -680,10 +748,11 @@ def get_waf_rules(envname): cloud_watch_metrics_enabled=True, metric_name='AWS-AWSManagedRulesSQLiRuleSet', ), - priority=5, + priority=priority, override_action=wafv2.CfnWebACL.OverrideActionProperty(none={}), ) ) + priority += 1 waf_rules.append( wafv2.CfnWebACL.RuleProperty( name='APIGatewayRateLimit', @@ -698,9 +767,10 @@ def get_waf_rules(envname): cloud_watch_metrics_enabled=True, metric_name=f'WAFAPIGatewayRateLimit{envname}', ), - priority=6, + priority=priority, ) ) + priority += 1 return waf_rules def create_sns_topic( diff --git a/deploy/stacks/pipeline.py b/deploy/stacks/pipeline.py index c14a22b91..b44fdcd0e 100644 --- a/deploy/stacks/pipeline.py +++ b/deploy/stacks/pipeline.py @@ -527,6 +527,7 @@ def set_backend_stage(self, target_env, repository_name): vpc_endpoints_sg=target_env.get('vpc_endpoints_sg'), internet_facing=target_env.get('internet_facing', True), custom_domain=target_env.get('custom_domain'), + custom_waf_rules=target_env.get('custom_waf_rules'), ip_ranges=target_env.get('ip_ranges'), apig_vpce=target_env.get('apig_vpce'), prod_sizing=target_env.get('prod_sizing', True), @@ -579,6 +580,7 @@ def set_cloudfront_stage(self, target_env): resource_prefix=self.resource_prefix, tooling_account_id=self.account, custom_domain=target_env.get('custom_domain'), + custom_waf_rules=target_env.get('custom_waf_rules'), ) ) front_stage_actions = (