diff --git a/.taskcat.yml b/.taskcat.yml index b747509..60d6d7d 100644 --- a/.taskcat.yml +++ b/.taskcat.yml @@ -18,6 +18,19 @@ tests: regions: - us-east-1 template: templates/crowdstrike_init_stack.yaml + cw-dspm-test: + parameters: + FalconClientID: $[taskcat_ssm_/crowdstrike/falcon_client_id] + FalconSecret: $[taskcat_ssm_/crowdstrike/falcon_secret] + SourceS3BucketName: $[taskcat_autobucket] + S3BucketRegion: $[taskcat_current_region] + ProvisionOU: $[taskcat_ssm_/crowdstrike/provision-ou] + ExcludeRegions: $[taskcat_ssm_/crowdstrike/exclude_regions] + EnableDSPM: "true" + DSPMRegions: $[taskcat_current_region] + regions: + - us-east-1 + template: templates/crowdstrike_init_stack.yaml cw-eks-test: parameters: FalconClientID: $[taskcat_ssm_/crowdstrike/falcon_client_id] diff --git a/VERSION b/VERSION index 56130fb..79127d8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v1.1.1 +v1.2.0 diff --git a/guide/content/architecture.md b/guide/content/architecture.md index bf08fd7..c479ec6 100644 --- a/guide/content/architecture.md +++ b/guide/content/architecture.md @@ -64,4 +64,21 @@ Deploying this ABI solution with default parameters builds the following archite * IAM Role for CodeBuild Execution * EventBridge Rule to send cluster events to centralized EventBus +### DSPM +* If you enable DSPM: + * In the primary region of all child accounts: + * IAM Role for CloudFormation execution + * IAM Role for integration with DSPM service + * IAM Role for Ec2 Instance operations (read-only) + * IAM Role for Lambda execution + * Secret to store Falcon API key + * Lambda Function to deploy CloudFormation across regions + * Instance Profile + * In all active regions of all child accounts: + * VPC with all required subnets, route tables, policies and + * KMS Key + * Redshift Subnet Group + * IAM Instance Profile for data scanner EC2 instances + * EC2 Instance (ephemeral) created at scan-time to scan S3 Buckets + **Next:** Choose [Deployment options](/deployment-options/index.html). \ No newline at end of file diff --git a/guide/content/costandlicenses.md b/guide/content/costandlicenses.md index 160a3aa..787d52b 100644 --- a/guide/content/costandlicenses.md +++ b/guide/content/costandlicenses.md @@ -11,7 +11,7 @@ description: Cost of the solution and licenses required. ### AWS service cost -In addition to the CrowdStrike Falcon cost, consider costs associated with the AWS services you choose and the scale of your operations. AWS services such as CloudTrail, Lambda, Amazon S3, and AWS Key Management Service (AWS KMS) may have associated costs. +In addition to the CrowdStrike Falcon cost, consider costs associated with the AWS services you choose and the scale of your operations. AWS services such as EventBridge, CloudTrail, Lambda, Amazon S3, and AWS Key Management Service (AWS KMS) may have associated costs. ### ABI cost and licenses diff --git a/guide/content/deployment-steps.md b/guide/content/deployment-steps.md index a2637a8..c91786a 100644 --- a/guide/content/deployment-steps.md +++ b/guide/content/deployment-steps.md @@ -70,6 +70,9 @@ description: Deployment steps. * **Registry**: Source Falcon Image from CrowdStrike or mirror to ECR. Allowed values are `crowdstrike` or `ecr`. Default is `crowdstrike` * **Backend**: kernel or bpf for Daemonset Sensor. Allowed Values are `kernel` or `bpf`. Default is `kernel` * **EnableKAC**: Deploy Kubernetes Admission Controller (KAC). For more info see https://falcon.crowdstrike.com/documentation/page/aa4fccee/container-security#s41cbec3 + * DSPM + * **EnableDSPM**: Whether to deploy DSPM + * **DSPMRegions**: Which regions to enable for DSPM 3. Select both of the following capabilities and choose **Submit** to launch the stack. diff --git a/guide/content/how-it-works.md b/guide/content/how-it-works.md index 3173a32..24cb1f9 100644 --- a/guide/content/how-it-works.md +++ b/guide/content/how-it-works.md @@ -61,4 +61,13 @@ This is accoomplished by 5. Lambda function to be triggered by CreateCluster and invoke codebuild against new clusters. 6. CodeBuild project to update access entries, pull CrowdStrike images and deploy Falcon Operator/Sensor. +### DSPM +Data security posture management (DSPM) identifies which of your Amazon S3 buckets contain sensitive data, such as personal information and credit card info. DSPM scans a sample of the data in all S3 buckets in your registered AWS environments every 3 months to discover and classify the data in those S3 buckets, making it easier to prioritize your security efforts. + +When DSPM performs a scan, it creates a data scanner using a c6a.2xlarge Amazon EC2 instance in the Amazon Virtual Private Cloud (VPC) that was created in your AWS account when you enabled DSPM. The data scanner discovers and classifies a sample of the data in the S3 buckets in your account and sends only classification labels and tags to CrowdStrike. Your data never leaves your environment. + +1. VPC in each region to run Data Scanner instance +2. IAM Role with trust to DSPM service to create Ec2 Instance at scan time +3. Leat privilege permissions applied to Ec2 to allow for S3 Scanning + **Next:** Choose [Architecture](/architecture/index.html). \ No newline at end of file diff --git a/guide/content/terminologies.md b/guide/content/terminologies.md index 1f4510e..9fced77 100644 --- a/guide/content/terminologies.md +++ b/guide/content/terminologies.md @@ -14,6 +14,7 @@ description: Terminolgies used in this guide. * **CrowdStrike API client:** CrowdStrike Falcon API client authentication credentials for interaction with CrowdStike APIs via OAuth 2.0 token. Includes an API client ID and API client secret. * **CrowdStrike event bus:** The AWS event bus in CrowdStrike's environment for receiving events and providing the data to CrowdStrike Cloud Security service. * **CSPM policies:** A set of rules defined to detect misconfigurations of the cloud resources (IOMs) or to detect suspicious behavior patterns (IOAs). +* **DSPM:** Data security posture management identifies which of your Amazon S3 buckets contain sensitive data * **Indicator of attack (IOA):** A pattern of suspicious behavior that suggests an attack might be underway. In CrowdStrike Cloud Security, IOAs are labeled as findings. * **Indicator of misconfiguration (IOM):** A configuration setting that doesn’t follow recommended security guidelines and might become a security vulnerability in a cloud environment. In CrowdStrike Cloud Security, IOMs are labeled as findings. * **Registration:** Enroll your AWS account ID with the CrowdStrike Cloud Security service. diff --git a/guide/content/troubleshooting.md b/guide/content/troubleshooting.md index a4e2dc7..65b6d6d 100644 --- a/guide/content/troubleshooting.md +++ b/guide/content/troubleshooting.md @@ -29,4 +29,7 @@ For troubleshooting common ABI issues, refer to the [ABI Reference Guide](https: 2. Check the CodeBuild Execution logs for ```crowdstrike-eks-codebuild```. 3. Check the Falcon Operator logs on the cluster. See [Operator Troubleshooting](https://github.com/CrowdStrike/falcon-operator/blob/main/docs/install_guide.md). +#### DSPM +See falcon documentation for detailed troubleshooting information [here](https://falcon.crowdstrike.com/documentation/page/efd9a6d9/troubleshooting-and-maintenance-for-aws-accounts#j47ce12b). + **Next:** Choose [Feedback](/feedback/index.html). \ No newline at end of file diff --git a/lambda_functions/source/register-organization/lambda.py b/lambda_functions/source/register-organization/lambda.py index 72f447c..b75a465 100644 --- a/lambda_functions/source/register-organization/lambda.py +++ b/lambda_functions/source/register-organization/lambda.py @@ -22,7 +22,7 @@ SUCCESS = "SUCCESS" FAILED = "FAILED" -VERSION = "1.1.1" +VERSION = "1.2.0" NAME = "crowdstrike-cloud-abi" USERAGENT = ("%s/%s" % (NAME, VERSION)) @@ -30,6 +30,7 @@ SECRET_STORE_REGION = os.environ['secret_region'] EXCLUDE_REGIONS = os.environ['exclude_regions'] EXISTING_CLOUDTRAIL = eval(os.environ['existing_cloudtrail']) +ENABLE_DSPM = eval(os.environ['enable_dspm']) AWS_REGION = os.environ['AWS_REGION'] CS_CLOUD = os.environ['cs_cloud'] AWS_ACCOUNT_TYPE = os.environ['aws_account_type'] @@ -163,26 +164,41 @@ def lambda_handler(event, context): if event['RequestType'] in ['Create']: logger.info('Event = %s' % event) if EXISTING_CLOUDTRAIL: - response = falcon.create_aws_account(account_id=aws_account_id, - organization_id=org_id, - behavior_assessment_enabled=True, - sensor_management_enabled=True, - use_existing_cloudtrail=EXISTING_CLOUDTRAIL, - user_agent=USERAGENT, - is_master=True, - account_type=AWS_ACCOUNT_TYPE - ) + payload = { + "resources": [ + { + "account_id": aws_account_id, + "account_type": AWS_ACCOUNT_TYPE, + "behavior_assessment_enabled": True, + "dspm_enabled": ENABLE_DSPM, + "dspm_role": 'CrowdStrikeDSPMIntegrationRole', + "is_master": True, + 'organization_id': org_id, + "sensor_management_enabled": True, + "use_existing_cloudtrail": EXISTING_CLOUDTRAIL, + "user_agent": USERAGENT + } + ] + } else: - response = falcon.create_aws_account(account_id=aws_account_id, - organization_id=org_id, - behavior_assessment_enabled=True, - sensor_management_enabled=True, - use_existing_cloudtrail=EXISTING_CLOUDTRAIL, - cloudtrail_region=AWS_REGION, - user_agent=USERAGENT, - is_master=True, - account_type=AWS_ACCOUNT_TYPE - ) + payload = { + "resources": [ + { + "account_id": aws_account_id, + "account_type": AWS_ACCOUNT_TYPE, + "behavior_assessment_enabled": True, + "cloudtrail_region": AWS_REGION, + "dspm_enabled": ENABLE_DSPM, + "dspm_role": 'CrowdStrikeDSPMIntegrationRole', + "is_master": True, + 'organization_id': org_id, + "sensor_management_enabled": True, + "use_existing_cloudtrail": EXISTING_CLOUDTRAIL, + "user_agent": USERAGENT + } + ] + } + response = falcon.create_aws_account(body=payload) logger.info('Response: %s' % response) if response['status_code'] == 201: cs_account = response['body']['resources'][0]['intermediate_role_arn'].rsplit('::')[1] @@ -191,7 +207,8 @@ def lambda_handler(event, context): "iam_role_name": response['body']['resources'][0]['iam_role_arn'].rsplit('/')[1], "intermediate_role_arn": response['body']['resources'][0]['intermediate_role_arn'], "cs_role_name": response['body']['resources'][0]['intermediate_role_arn'].rsplit('/')[1], - "external_id": response['body']['resources'][0]['external_id'] + "external_id": response['body']['resources'][0]['external_id'], + "dspm_role_arn": response['body']['resources'][0]['dspm_role_arn'] } if not EXISTING_CLOUDTRAIL: response_d['cs_bucket_name'] = response['body']['resources'][0]['aws_cloudtrail_bucket_name'] @@ -221,7 +238,8 @@ def lambda_handler(event, context): "iam_role_name": response['body']['resources'][0]['iam_role_arn'].rsplit('/')[1], "intermediate_role_arn": response['body']['resources'][0]['intermediate_role_arn'], "cs_role_name": response['body']['resources'][0]['intermediate_role_arn'].rsplit('/')[1], - "external_id": response['body']['resources'][0]['external_id'] + "external_id": response['body']['resources'][0]['external_id'], + "dspm_role_arn": response['body']['resources'][0]['dspm_role_arn'] } if not EXISTING_CLOUDTRAIL: response_d['cs_bucket_name'] = response['body']['resources'][0]['aws_cloudtrail_bucket_name'] diff --git a/templates/aws_cspm_cloudformation_dspm_env.yml b/templates/aws_cspm_cloudformation_dspm_env.yml new file mode 100644 index 0000000..badae5b --- /dev/null +++ b/templates/aws_cspm_cloudformation_dspm_env.yml @@ -0,0 +1,498 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: Create environment for CrowdStrike Data Analysis +Mappings: + TagMap: + CrowdStrikeTag: + Name: ProvisionedBy + Value: CrowdStrike + LogicalTag: + Name: CrowdStrikeLogicalId +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: Deployment Configuration + Parameters: + - CreateVPC + - CreateNatGateway + - Label: + default: VPC Configuration (relevant only if using an existing VPC) + Parameters: + - VPC + - DBSubnetA + - DBSubnetB + - Label: + default: Role Names + Parameters: + - CrowdStrikeIntegrationRoleName + - CrowdStrikeScannerRoleName + ParameterLabels: + CreateVPC: + default: Create a new VPC for CrowdStrike environment + CreateNatGateway: + default: Create nat gateway (relevant only if creating a new VPC) + VPC: + default: The VPC ID to use + DBSubnetA: + default: The first subnet id to use (must be in the chosen VPC) + DBSubnetB: + default: The second subnet id to use (must be in the chosen VPC) + CrowdStrikeIntegrationRoleName: + default: CrowdStrike integration role name + CrowdStrikeScannerRoleName: + default: CrowdStrike scanner role name +Parameters: + CreateNatGateway: + Type: String + Description: Create nat gateway + Default: "yes" + AllowedValues: [ "yes", "no" ] + CreateVPC: + Type: String + Default: "yes" + AllowedValues: [ "yes", "no" ] + VPC: + Type: String + Default: "" + DBSubnetA: + Type: String + Default: "" + DBSubnetB: + Type: String + Default: "" + CrowdStrikeIntegrationRoleName: + Type: String + Description: The ARN of the role name you gave in the original deployment + Default: CrowdStrikeDSPMRole + CrowdStrikeScannerRoleName: + Type: String + Description: The ARN of the scanner role name you gave in the original deployment + Default: CrowdStrikeDSPMScannerRole +Conditions: + CreateNatGateway: + !Equals [!Ref CreateNatGateway, "yes"] + NotCreateNatGateway: + !Equals [ !Ref CreateNatGateway, "no" ] + CreateVPC: + !Equals ["yes", !Ref CreateVPC] + # NotCreateVPC: + # !Equals ["no", !Ref CreateVPC] + CreateVPCAndCreateNatGateway: + !And [!Condition CreateVPC, !Condition CreateNatGateway] + CreateVPCAndNotCreateNatGateway: + !And [!Condition CreateVPC, !Condition NotCreateNatGateway] + +Resources: + CreatedVPC: + Type: AWS::EC2::VPC + Metadata: + cfn_nag: + rules_to_suppress: + - id: W60 + reason: Leaving FlowLog disabled to remain at parity with product templates + Condition: CreateVPC + Properties: + CidrBlock: 10.0.0.0/16 + EnableDnsSupport: true + EnableDnsHostnames: true + Tags: + - Key: Name + Value: !Join [ '', [ !Ref "AWS::StackName", "-VPC" ] ] + - Key: !FindInMap [TagMap, CrowdStrikeTag, Name] + Value: !FindInMap [TagMap, CrowdStrikeTag, Value] + CreatedInternetGateway: + Type: AWS::EC2::InternetGateway + Condition: CreateVPC + DependsOn: CreatedVPC + AttachGateway: + Type: AWS::EC2::VPCGatewayAttachment + Condition: CreateVPC + Properties: + VpcId: !Ref CreatedVPC + InternetGatewayId: !Ref CreatedInternetGateway + CreatedDBSubnetA: + Type: AWS::EC2::Subnet + Condition: CreateVPC + Properties: + VpcId: !Ref CreatedVPC + CidrBlock: 10.0.0.0/24 + AvailabilityZone: + Fn::Select: + - 0 + - Fn::GetAZs: "" + Tags: + - Key: Name + Value: !Sub ${AWS::StackName}-DB-A + - Key: !FindInMap [ TagMap, CrowdStrikeTag, Name ] + Value: !FindInMap [ TagMap, CrowdStrikeTag, Value ] + CreatedDBSubnetB: + Type: AWS::EC2::Subnet + Condition: CreateVPC + Properties: + VpcId: !Ref CreatedVPC + CidrBlock: 10.0.1.0/24 + AvailabilityZone: + Fn::Select: + - 1 + - Fn::GetAZs: "" + Tags: + - Key: Name + Value: !Sub ${AWS::StackName}-DB-B + - Key: !FindInMap [ TagMap, CrowdStrikeTag, Name ] + Value: !FindInMap [ TagMap, CrowdStrikeTag, Value ] + DBSubnetGroup: + Type: AWS::RDS::DBSubnetGroup + Properties: + DBSubnetGroupName: !Sub ${AWS::StackName}-DBSubnetGroup + DBSubnetGroupDescription: "CrowdStrike DB subnet group" + SubnetIds: + - !If [ CreateVPC, !Ref CreatedDBSubnetA, !Ref DBSubnetA ] + - !If [ CreateVPC, !Ref CreatedDBSubnetB, !Ref DBSubnetB ] + Tags: + - Key: Name + Value: !Sub ${AWS::StackName}-DBSubnetGroup + - Key: !FindInMap [ TagMap, CrowdStrikeTag, Name ] + Value: !FindInMap [ TagMap, CrowdStrikeTag, Value ] + - Key: !FindInMap [ TagMap, LogicalTag, Name ] + Value: DBSubnetGroup + RedshiftSubnetGroup: + Type: AWS::Redshift::ClusterSubnetGroup + Properties: + Description: "CrowdStrike Redshift subnet group" + SubnetIds: + - !If [ CreateVPC, !Ref CreatedDBSubnetA, !Ref DBSubnetA ] + - !If [ CreateVPC, !Ref CreatedDBSubnetB, !Ref DBSubnetB ] + Tags: + - Key: Name + Value: !Sub ${AWS::StackName}-RedshiftSubnetGroup + - Key: !FindInMap [ TagMap, CrowdStrikeTag, Name ] + Value: !FindInMap [ TagMap, CrowdStrikeTag, Value ] + - Key: !FindInMap [ TagMap, LogicalTag, Name ] + Value: RedshiftSubnetGroup + PublicSubnet: + Metadata: + cfn-lint: + config: + ignore_checks: + - W1028 # CreateVPCAndCreateNatGateway cannot be true while CreateVPC is false + - W1030 # Ref will resolve to VPCID per Parameter + Condition: CreateVPCAndCreateNatGateway + Type: AWS::EC2::Subnet + Properties: + VpcId: !If [ CreateVPC, !Ref CreatedVPC, !Ref VPC ] + CidrBlock: 10.0.2.0/24 + AvailabilityZone: + Fn::Select: + - 0 + - Fn::GetAZs: "" + Tags: + - Key: Name + Value: !Sub ${AWS::StackName}-Public + - Key: !FindInMap [ TagMap, CrowdStrikeTag, Name ] + Value: !FindInMap [ TagMap, CrowdStrikeTag, Value ] + PrivateSubnet: + Metadata: + cfn-lint: + config: + ignore_checks: + - W1028 # CreateVPCAndCreateNatGateway cannot be true while CreateVPC is false + - W1030 # Ref will resolve to VPCID per Parameter + Type: AWS::EC2::Subnet + Condition: CreateVPC + Properties: + VpcId: !If [ CreateVPC, !Ref CreatedVPC, !Ref VPC ] + CidrBlock: 10.0.3.0/24 + AvailabilityZone: + Fn::Select: + - 0 + - Fn::GetAZs: "" + Tags: + - Key: Name + Value: !Sub ${AWS::StackName}-Private + - Key: !FindInMap [ TagMap, CrowdStrikeTag, Name ] + Value: !FindInMap [ TagMap, CrowdStrikeTag, Value ] + - Key: !FindInMap [ TagMap, LogicalTag, Name ] + Value: PrivateSubnet + PublicRouteTable: + Metadata: + cfn-lint: + config: + ignore_checks: + - W1028 # CreateVPCAndCreateNatGateway cannot be true while CreateVPC is false + - W1030 # Ref will resolve to VPCID per Parameter + Condition: CreateVPCAndCreateNatGateway + Type: AWS::EC2::RouteTable + Properties: + VpcId: !If [ CreateVPC, !Ref CreatedVPC, !Ref VPC] + Tags: + - Key: Name + Value: !Sub ${AWS::StackName}-Public + - Key: !FindInMap [ TagMap, CrowdStrikeTag, Name ] + Value: !FindInMap [ TagMap, CrowdStrikeTag, Value ] + PublicRoute1: + Metadata: + cfn-lint: + config: + ignore_checks: + - W1028 # CreateVPCAndCreateNatGateway cannot be true while CreateVPC is false + - W1030 # Ref will resolve to VPCID per Parameter + Condition: CreateVPCAndCreateNatGateway + Type: AWS::EC2::Route + DependsOn: AttachGateway + Properties: + RouteTableId: !Ref PublicRouteTable + DestinationCidrBlock: 0.0.0.0/0 + GatewayId: !Ref CreatedInternetGateway + PrivateRouteTable: + Metadata: + cfn-lint: + config: + ignore_checks: + - W1028 # CreateVPCAndCreateNatGateway cannot be true while CreateVPC is false + - W1030 # Ref will resolve to VPCID per Parameter + Type: AWS::EC2::RouteTable + Condition: CreateVPC + Properties: + VpcId: !If [ CreateVPC, !Ref CreatedVPC, !Ref VPC] + Tags: + - Key: Name + Value: !Sub ${AWS::StackName}-Private + - Key: !FindInMap [ TagMap, CrowdStrikeTag, Name ] + Value: !FindInMap [ TagMap, CrowdStrikeTag, Value ] + PrivateRoute1: + Condition: CreateVPCAndCreateNatGateway + Type: AWS::EC2::Route + Properties: + RouteTableId: !Ref PrivateRouteTable + DestinationCidrBlock: 0.0.0.0/0 + NatGatewayId: !Ref NATGateway + PrivateRoute2: + Condition: CreateVPCAndNotCreateNatGateway + Type: AWS::EC2::Route + DependsOn: AttachGateway + Properties: + RouteTableId: !Ref PrivateRouteTable + DestinationCidrBlock: 0.0.0.0/0 + GatewayId: !Ref CreatedInternetGateway + NATGateway: + Condition: CreateVPCAndCreateNatGateway + Type: AWS::EC2::NatGateway + Properties: + AllocationId: !GetAtt ElasticIPAddress.AllocationId + SubnetId: !Ref PublicSubnet + Tags: + - Key: Name + Value: !Sub NAT-${AWS::StackName} + - Key: !FindInMap [ TagMap, CrowdStrikeTag, Name ] + Value: !FindInMap [ TagMap, CrowdStrikeTag, Value ] + ElasticIPAddress: + Condition: CreateVPCAndCreateNatGateway + Type: AWS::EC2::EIP + Properties: + Domain: vpc + Tags: + - Key: Name + Value: !Sub EIP-${AWS::StackName} + - Key: !FindInMap [ TagMap, CrowdStrikeTag, Name ] + Value: !FindInMap [ TagMap, CrowdStrikeTag, Value ] + - Key: !FindInMap [ TagMap, LogicalTag, Name ] + Value: ElasticIP + PublicSubnetRouteTableAssociation: + Condition: CreateVPCAndCreateNatGateway + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref PublicSubnet + RouteTableId: !Ref PublicRouteTable + PrivateSubnetRouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Condition: CreateVPC + Properties: + SubnetId: !Ref PrivateSubnet + RouteTableId: !Ref PrivateRouteTable + DBSubnetARouteTableAssociation: + Condition: CreateVPCAndNotCreateNatGateway + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref CreatedDBSubnetA + RouteTableId: !Ref PrivateRouteTable + DBSubnetBRouteTableAssociation: + Condition: CreateVPCAndNotCreateNatGateway + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref CreatedDBSubnetB + RouteTableId: !Ref PrivateRouteTable + VPCPolicy: + Type: AWS::IAM::Policy + Properties: + PolicyName: !If [ CreateVPC, !Sub "RunDataScanner-${AWS::Region}-${CreatedVPC}", !Sub "RunDataScanner-${AWS::Region}-${VPC}"] + Roles: + - !Ref CrowdStrikeIntegrationRoleName + PolicyDocument: + Statement: + # Limits permission to launch EC2 to CrowdStrike vpc's subnet + # The condition key ec2:Vpc is applicable to below resources + - Sid: AllowRunInstances + Action: + - ec2:RunInstances + Effect: Allow + Resource: + - !Sub "arn:aws:ec2:*:${AWS::AccountId}:security-group/*" + - !Sub "arn:aws:ec2:*:${AWS::AccountId}:subnet/*" + Condition: + StringEquals: + "ec2:Vpc": !If [ CreateVPC, !Sub "arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:vpc/${CreatedVPC}", !Sub "arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:vpc/${VPC}" ] + + EC2SecurityGroup: + Metadata: + cfn-lint: + config: + ignore_checks: + - W1030 # Ref will resolve to VPCID per Parameter + cfn_nag: + rules_to_suppress: + - id: F1000 + reason: Leaving egress implicit to remain at parity with product templates + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: CrowdStrike + VpcId: !If [ CreateVPC, !Ref CreatedVPC, !Ref VPC ] + Tags: + - Key: Name + Value: !Sub ${AWS::StackName}-EC2 + - Key: !FindInMap [ TagMap, CrowdStrikeTag, Name ] + Value: !FindInMap [ TagMap, CrowdStrikeTag, Value ] + - Key: !FindInMap [ TagMap, LogicalTag, Name ] + Value: EC2SecurityGroup + DBSecurityGroup: + Metadata: + cfn-lint: + config: + ignore_checks: + - W1030 # Ref will resolve to VPCID per Parameter + cfn_nag: + rules_to_suppress: + - id: W27 + reason: Range necessary for functionality + - id: F1000 + reason: Leaving egress implicit to remain at parity with product templates + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Security group attached to RDS instance to allow EC2 instances with specific security groups attached to connect to the database + VpcId: !If [ CreateVPC, !Ref CreatedVPC, !Ref VPC ] + Tags: + - Key: Name + Value: !Sub ${AWS::StackName}-DB + - Key: !FindInMap [ TagMap, CrowdStrikeTag, Name ] + Value: !FindInMap [ TagMap, CrowdStrikeTag, Value ] + - Key: !FindInMap [ TagMap, LogicalTag, Name ] + Value: DBSecurityGroup + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: '5432' + ToPort: '5432' + SourceSecurityGroupId: !Ref EC2SecurityGroup + Description: 'DB Security Group Ingress' + - IpProtocol: tcp + FromPort: '3306' + ToPort: '3306' + SourceSecurityGroupId: !Ref EC2SecurityGroup + Description: 'DB Security Group Ingress' + - IpProtocol: tcp + FromPort: '1521' + ToPort: '1523' + SourceSecurityGroupId: !Ref EC2SecurityGroup + Description: 'DB Security Group Ingress' + - IpProtocol: tcp + FromPort: '1433' + ToPort: '1433' + SourceSecurityGroupId: !Ref EC2SecurityGroup + Description: 'DB Security Group Ingress' + - IpProtocol: tcp + FromPort: '27017' + ToPort: '27017' + SourceSecurityGroupId: !Ref EC2SecurityGroup + Description: 'DB Security Group Ingress' + - IpProtocol: tcp + FromPort: '6379' + ToPort: '6379' + SourceSecurityGroupId: !Ref EC2SecurityGroup + Description: 'DB Security Group Ingress' + - IpProtocol: tcp + FromPort: '9042' + ToPort: '9042' + SourceSecurityGroupId: !Ref EC2SecurityGroup + Description: 'DB Security Group Ingress' + - IpProtocol: tcp + FromPort: '9200' + ToPort: '9200' + SourceSecurityGroupId: !Ref EC2SecurityGroup + Description: 'DB Security Group Ingress' + - IpProtocol: tcp + FromPort: '5439' + ToPort: '5439' + SourceSecurityGroupId: !Ref EC2SecurityGroup + Description: 'DB Security Group Ingress' + CrowdStrikeKMSKey: + Type: AWS::KMS::Key + Properties: + Description: CrowdStrike DSPM KMS Key + EnableKeyRotation: true + PendingWindowInDays: 20 + KeyPolicy: + Version: 2012-10-17 + Id: CrowdStrike-key-policy + Statement: + - Sid: Enable IAM User Permissions + Effect: Allow + Principal: + AWS: !Join [ "", [ "arn:aws:iam::", !Ref "AWS::AccountId", ":root" ] ] + Action: + - kms:* + Resource: "*" + - Sid: Allow administration of the key + Effect: Allow + Principal: + AWS: !Join [ "", [ "arn:aws:iam::", !Ref "AWS::AccountId", ":role/", !Ref CrowdStrikeIntegrationRoleName ] ] + Action: + - kms:Create* + - kms:Describe* + - kms:Enable* + - kms:List* + - kms:Put* + - kms:Update* + - kms:Revoke* + - kms:Disable* + - kms:Get* + - kms:Delete* + - kms:ScheduleKeyDeletion + - kms:CancelKeyDeletion + Resource: "*" + - Sid: Allow use of the key + Effect: Allow + Principal: + AWS: + - !Join [ "", [ "arn:aws:iam::", !Ref "AWS::AccountId", ":role/", !Ref CrowdStrikeIntegrationRoleName ] ] + - !Join [ "", [ "arn:aws:iam::", !Ref "AWS::AccountId", ":role/", !Ref CrowdStrikeScannerRoleName ] ] + Action: + - kms:DescribeKey + - kms:Encrypt + - kms:Decrypt + - kms:ReEncrypt* + - kms:GenerateDataKey + - kms:GenerateDataKeyWithoutPlaintext + Resource: "*" + Tags: + - Key: !FindInMap [ TagMap, CrowdStrikeTag, Name ] + Value: !FindInMap [ TagMap, CrowdStrikeTag, Value ] + - Key: !FindInMap [ TagMap, LogicalTag, Name ] + Value: KMSKey + CrowdStrikeKeyAlias: + Type: 'AWS::KMS::Alias' + Properties: + AliasName: alias/CrowdStrikeDSPMKey + TargetKeyId: !Ref CrowdStrikeKMSKey + +Outputs: + CrowdStrikeKMSKey: + Description: The arn of the KMS key that CrowdStrike will use + Value: !GetAtt CrowdStrikeKMSKey.Arn \ No newline at end of file diff --git a/templates/aws_cspm_cloudformation_ioa_comm_gov.json b/templates/aws_cspm_cloudformation_ioa_comm_gov.json index a1d1ffd..2f4178a 100644 --- a/templates/aws_cspm_cloudformation_ioa_comm_gov.json +++ b/templates/aws_cspm_cloudformation_ioa_comm_gov.json @@ -294,6 +294,14 @@ "id": "CKV_AWS_173", "comment": "This is supported by APICredentialsStorageMode parameter" }, + { + "id": "CKV_AWS_117", + "comment": "We do not require customer to create and manage additional VPC" + }, + { + "id": "CKV_AWS_115", + "comment": "Lambda does not need reserved concurrent executions." + }, { "id": "W89", "comment": "We do not require customer to create and manage additional VPC" @@ -400,6 +408,14 @@ "id": "CKV_AWS_173", "comment": "Env variables are not sensitive" }, + { + "id": "CKV_AWS_117", + "comment": "We do not require customer to create and manage additional VPC" + }, + { + "id": "CKV_AWS_115", + "comment": "Lambda does not need reserved concurrent executions." + }, { "id": "W89", "comment": "We do not require customer to create and manage additional VPC" @@ -786,6 +802,14 @@ "id": "CKV_AWS_173", "comment": "Env variables are not sensitive" }, + { + "id": "CKV_AWS_117", + "comment": "We do not require customer to create and manage additional VPC" + }, + { + "id": "CKV_AWS_115", + "comment": "Lambda does not need reserved concurrent executions." + }, { "id": "W89", "comment": "We do not require customer to create and manage additional VPC" diff --git a/templates/aws_cspm_cloudformation_v2.json b/templates/aws_cspm_cloudformation_v2.json index 1c5aa61..50456d6 100644 --- a/templates/aws_cspm_cloudformation_v2.json +++ b/templates/aws_cspm_cloudformation_v2.json @@ -563,12 +563,20 @@ "comment": "This is supported by APICredentialsStorageMode parameter" }, { - "id": "W89", + "id": "CKV_AWS_117", "comment": "We do not require customer to create and manage additional VPC" }, + { + "id": "CKV_AWS_115", + "comment": "Lambda does not need reserved concurrent executions." + }, { "id": "W58", "comment": "Log permissions defined by CrowdStrikeSensorManagementOrchestratorRole" + }, + { + "id": "W89", + "comment": "We do not require customer to create and manage additional VPC" } ] } diff --git a/templates/crowdstrike_init_stack.yaml b/templates/crowdstrike_init_stack.yaml index 8b68cfd..1f8e1bf 100644 --- a/templates/crowdstrike_init_stack.yaml +++ b/templates/crowdstrike_init_stack.yaml @@ -71,6 +71,8 @@ Metadata: - pGovernedRegions - pSecurityAccountId - pLogArchiveAccountId + - pRepoURL + - pRepoBranch - Label: default: EKS Protection Parameters: @@ -86,6 +88,11 @@ Metadata: - Registry - Backend - EnableKAC + - Label: + default: DSPM + Parameters: + - EnableDSPM + - DSPMRegions ParameterLabels: # Account Type @@ -175,6 +182,10 @@ Metadata: default: Security Account Id pLogArchiveAccountId: default: LogArchive Account Id + pRepoURL: + default: SRA Repo URL + pRepoBranch: + default: SRA Repo Branch # EKS Protection EKSProtection: @@ -202,6 +213,12 @@ Metadata: EnableKAC: default: Enable Kubernetes Admission Controller + # DSPM + EnableDSPM: + default: Enable DSPM + DSPMRegions: + default: DSPM Regions + Parameters: # Account Type FalconAccountType: @@ -470,6 +487,21 @@ Parameters: - false Default: true + # DSPM + EnableDSPM: + Type: String + Description: Deploy DSPM. For more info see ... + AllowedValues: + - true + - false + Default: false + DSPMRegions: + Type: String + Description: comma separated list of regions to enable, e.g. us-east-1,us-west-2 + AllowedPattern: "^((\\s*)((?:us|eu|ap|sa|ca|af|me|il)-(?:north|south|east|west|central|northeast|southeast|southwest|northwest)-[1-4])(\\s*)(?:,|$))+$" + Default: us-east-1 + MinLength: "1" + Mappings: CloudMap: us1: @@ -500,6 +532,7 @@ Conditions: NotDelegatedAdminGovIOA: !And [ !Equals [ !Ref 'DelegatedAdmin', false ], !Equals [ !Ref 'AWSAccountType', govcloud ], !Equals [ !Ref 'FalconAccountType', govcloud ], !Equals [!Ref 'EnableIOA', true ]] NotDelegatedAdminCommGovIOA: !And [!Equals [ !Ref 'DelegatedAdmin', false ], !Equals [ !Ref 'AWSAccountType', commercial ], !Equals [ !Ref 'FalconAccountType', govcloud ], !Equals [!Ref 'EnableIOA', true ]] EnableEKSProtection: !Equals [ !Ref 'EKSProtection', true ] + CreateDSPMResources: !Equals [ !Ref 'EnableDSPM', true ] Resources: # Optional Default Organization CloudTrail @@ -1107,6 +1140,7 @@ Resources: aws_account_type: !Ref AWSAccountType falcon_account_type: !Ref FalconAccountType existing_cloudtrail: !If [ CreateIOATrail, 'False', 'True' ] + enable_dspm: !If [ CreateDSPMResources, 'True', 'True' ] Handler: lambda.lambda_handler MemorySize: 128 Role: !GetAtt "LambdaCrowdStrikeRegistrationRole.Arn" @@ -1558,6 +1592,58 @@ Resources: Regions: !GetAtt TriggerRegisterAccountLambda.my_regions TemplateURL: !Sub https://${SourceS3BucketName}.s3.${S3BucketRegion}.amazonaws.com/${SourceS3BucketNamePrefix}/templates/eks-eventbridge-stackset.yml + # Optional DSPM + DSPMStack: + Type: 'AWS::CloudFormation::Stack' + Condition: CreateDSPMResources + Properties: + TemplateURL: !Sub https://${SourceS3BucketName}.s3.${S3BucketRegion}.amazonaws.com/${SourceS3BucketNamePrefix}/templates/dspm-stack.yml + Parameters: + ClientID: !Ref FalconClientID + ClientSecret: !Ref FalconSecret + CSRoleName: "CrowdStrikeCSPMConnector" + CSAccountNumber: !GetAtt TriggerRegisterAccountLambda.cs_account_id + ExternalID: !GetAtt TriggerRegisterAccountLambda.external_id + DSPMRegions: !Ref DSPMRegions + + DSPMStackSet: + Type: 'AWS::CloudFormation::StackSet' + Condition: CreateDSPMResources + Properties: + StackSetName: crowdstrike-dspm-stackset + Capabilities: + - CAPABILITY_NAMED_IAM + Parameters: + - ParameterKey: ClientID + ParameterValue: !Ref FalconClientID + - ParameterKey: ClientSecret + ParameterValue: !Ref FalconSecret + - ParameterKey: CSRoleName + ParameterValue: "CrowdStrikeCSPMConnector" + - ParameterKey: CSAccountNumber + ParameterValue: !GetAtt TriggerRegisterAccountLambda.cs_account_id + - ParameterKey: ExternalID + ParameterValue: !GetAtt TriggerRegisterAccountLambda.external_id + - ParameterKey: DSPMRegions + ParameterValue: !Ref DSPMRegions + PermissionModel: SERVICE_MANAGED + CallAs: !If [ IsDelegatedAdmin, 'DELEGATED_ADMIN', 'SELF' ] + AutoDeployment: + Enabled: true + RetainStacksOnAccountRemoval: true + OperationPreferences: + MaxConcurrentPercentage: 100 + FailureTolerancePercentage: 50 + RegionConcurrencyType: PARALLEL + StackInstancesGroup: + - DeploymentTargets: + AccountFilterType: NONE + OrganizationalUnitIds: !Ref ProvisionOU + Regions: + - !Ref AWS::Region + TemplateURL: !Sub https://${SourceS3BucketName}.s3.${S3BucketRegion}.amazonaws.com/${SourceS3BucketNamePrefix}/templates/dspm-stack.yml + + Outputs: CSBucket: Condition: CreateIOATrail diff --git a/templates/dspm-stack.yml b/templates/dspm-stack.yml new file mode 100644 index 0000000..10cf89f --- /dev/null +++ b/templates/dspm-stack.yml @@ -0,0 +1,1101 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: Create assumable role for CrowdStrike Data Analysis +Parameters: + ClientID: + Type: String + NoEcho: true + Description: CrowdStrike client ID + MinLength: "1" + ClientSecret: + Type: String + NoEcho: true + Description: CrowdStrike client secret + MinLength: "1" + CSRoleName: + Description: Name of CrowdStrike assuming role + Type: String + MinLength: "10" + CSAccountNumber: + Description: CrowdStrike account number + Type: String + MinLength: "12" + MaxLength: "12" + ExternalID: + Type: String + Description: Unique ID for tenant + MinLength: "32" + ScannerRoleName: + Type: String + Description: The unique name of the IAM role that CrowdStrike Scanner will be assuming + Default: CrowdStrikeDSPMScannerRole + DSPMRoleName: + Type: String + Description: The unique name of the IAM role that CrowdStrike will be assuming + Default: CrowdStrikeDSPMIntegrationRole + DSPMRegions: + Type: String + Description: comma separated list of regions to enable, e.g. us-east-1,us-west-2 + AllowedPattern: "^((\\s*)((?:us|eu|ap|sa|ca|af|me|il)-(?:north|south|east|west|central|northeast|southeast|southwest|northwest)-[1-4])(\\s*)(?:,|$))+$" + Default: us-east-1 + MinLength: "1" +Mappings: + TagMap: + CrowdStrikeTag: + Value: CrowdStrike + DeploymentRegions: + Key: CrowdStrikeDeploymentRegions + LogicalTag: + Name: CrowdStrikeLogicalId + # Lambda: + # CloudformationTemplateURL: + # Value: https://cs-prod-cloudconnect-templates.s3.amazonaws.com/aws_cspm_cloudformation_dspm_env.yaml + +Resources: + # Creates instance profile. Attached as IAM role to EC2 instance, used for data scan + ClientSecrets: + Type: AWS::SecretsManager::Secret + Metadata: + checkov: + skip: + - id: CKV_AWS_149 + comment: The default key aws/secretsmanager is sufficient to secure this resource + cfn_nag: + rules_to_suppress: + - id: W77 + reason: The default key aws/secretsmanager is sufficient to secure this resource + Properties: + Name: "CrowdStrikeDSPMClientSecret" + SecretString: !Sub | + { + "ClientId": "${ClientID}", + "ClientSecret": "${ClientSecret}" + } + Tags: + - Key: ProvisionedBy + Value: !FindInMap [ TagMap, CrowdStrikeTag, Value ] + - Key: !FindInMap [ TagMap, LogicalTag, Name ] + Value: ClientSecrets + # Creates instance profile. Attached as IAM role to EC2 instance, used for data scan + InstanceProfile: + Type: AWS::IAM::InstanceProfile + DependsOn: + - CrowdStrikeAWSScannerRole + Properties: + InstanceProfileName: CrowdStrikeDSPMScannerRoleProfile + Path: / + Roles: + - !Ref ScannerRoleName + CrowdStrikeAWSIntegrationRole: + Type: AWS::IAM::Role + Metadata: + cfn-lint: + config: + ignore_checks: + - EIAMPolicyWildcardResource # Role has * to allow for future service monitoring without stack updates + - EIAMPolicyActionWildcard # Role has * to allow for future service monitoring without stack updates + checkov: + skip: + - id: CKV_AWS_109 + comment: IAM PassRole action is constrained by resource ARN. + - id: CKV_AWS_111 + comment: IAM PassRole action is constrained by resource ARN. + - id: CKV_AWS_108 + comment: Read only permissions required for integration to function. + cfn_nag: + rules_to_suppress: + - id: W11 + reason: Wildcard necessary for undefined ARNs + - id: F3 + reason: Wildcard necessary for undefined ARNs + - id: W28 + reason: Leaving explicit RoleName to remain at parity with product templates + - id: W76 + reason: false positive + Properties: + RoleName: !Ref DSPMRoleName + Path: / + AssumeRolePolicyDocument: + Statement: + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: !Sub "arn:aws:iam::${CSAccountNumber}:role/${CSRoleName}" + Condition: + StringEquals: + sts:ExternalId: !Ref ExternalID + ManagedPolicyArns: + - "arn:aws:iam::aws:policy/SecurityAudit" + Tags: + - Key: ProvisionedBy + Value: !FindInMap [ TagMap, CrowdStrikeTag, Value ] + Policies: + - PolicyName: CrowdStrikeCloudScanSupplemental + PolicyDocument: + Statement: + - Action: + - ses:DescribeActiveReceiptRuleSet + - athena:GetWorkGroup + - logs:DescribeLogGroups + - elastictranscoder:ListPipelines + - elasticfilesystem:DescribeFileSystems + - redshift:List* + - redshift:Describe* + - redshift:View* + - redshift-serverless:List* + - ec2:GetConsoleOutput + - sts:DecodeAuthorizationMessage + - elb:DescribeLoadBalancers + - cloudwatch:GetMetricData + - cloudwatch:GetMetricStatistics + - cloudwatch:ListMetrics + Effect: Allow + Resource: '*' + + - PolicyName: CrowdStrikeRDSClone + PolicyDocument: + Statement: + # Grants permission to add only requested tag mentioned in condition to RDS instance and snapshot + - Sid: RDSPermissionForTagging + Action: + - rds:AddTagsToResource + Effect: Allow + Resource: + - !Sub "arn:aws:rds:*:${AWS::AccountId}:db:crowdstrike-*" + - !Sub "arn:aws:rds:*:${AWS::AccountId}:snapshot:crowdstrike-*" + - !Sub "arn:aws:rds:*:${AWS::AccountId}:cluster:crowdstrike-*" + - !Sub "arn:aws:rds:*:${AWS::AccountId}:cluster-snapshot:crowdstrike-*" + Condition: + StringEquals: + "aws:RequestTag/ProvisionedBy": !FindInMap [ TagMap, CrowdStrikeTag, Value ] + + # Grants permission to restore CMK encrypted instances + - Sid: KMSPermissionsForRDSRestore + Action: + - kms:Encrypt + - kms:Decrypt + - kms:ReEncrypt* + - kms:GenerateDataKey* + - kms:CreateGrant + - kms:ListGrants + - kms:DescribeKey + Effect: Allow + Resource: '*' + Condition: + StringLike: + "kms:ViaService": "rds.*.amazonaws.com" + + # Grants permission to restore an instance/cluster from any snapshot + - Sid: RDSPermissionForInstanceRestore + Action: + - rds:RestoreDBInstanceFromDBSnapshot + - rds:RestoreDBClusterFromSnapshot + - kms:Decrypt # Require to restore encrypted db snapshot using KMS keys + Effect: Allow + Resource: + - !Sub "arn:aws:rds:*:${AWS::AccountId}:snapshot:*" + - !Sub "arn:aws:rds:*:${AWS::AccountId}:cluster-snapshot:*" + - !Sub "arn:aws:kms:*:${AWS::AccountId}:key/*" + + # Restricts permission to restore an instance/cluster to CrowdStrike VPC + - Sid: RDSPermissionForInstanceRestoreCrowdStrikeVPC + Action: + - rds:RestoreDBInstanceFromDBSnapshot + - rds:RestoreDBClusterFromSnapshot + Effect: Allow + Resource: + - !Sub "arn:aws:rds:*:${AWS::AccountId}:subgrp:*" + Condition: + StringEquals: + "aws:ResourceTag/ProvisionedBy": !FindInMap [ TagMap, CrowdStrikeTag, Value ] + + # Grants permission to restore instance/cluster with a tag mentioned in the condition + - Sid: RDSLimitedPermissionForInstanceRestore + Action: + - rds:RestoreDBInstanceFromDBSnapshot + - rds:RestoreDBClusterFromSnapshot + Effect: Allow + Resource: + - !Sub "arn:aws:rds:*:${AWS::AccountId}:db:*" + - !Sub "arn:aws:rds:*:${AWS::AccountId}:cluster:*" + - !Sub "arn:aws:ec2:*:${AWS::AccountId}:security-group/*" + - !Sub "arn:aws:rds:*:${AWS::AccountId}:og:*" + - !Sub "arn:aws:rds:*:${AWS::AccountId}:pg:*" + - !Sub "arn:aws:rds:*:${AWS::AccountId}:cluster-og:*" + - !Sub "arn:aws:rds:*:${AWS::AccountId}:cluster-pg:*" + Condition: + StringEquals: + "aws:RequestTag/ProvisionedBy": !FindInMap [ TagMap, CrowdStrikeTag, Value ] + + # Grants permission to create snapshot with a tag mentioned in the condition. + - Sid: RDSLimitedPermissionForSnapshotCreate + Action: + - rds:CreateDBSnapshot + - rds:CreateDBClusterSnapshot + Effect: Allow + Resource: + - !Sub "arn:aws:rds:*:${AWS::AccountId}:db:*" + - !Sub "arn:aws:rds:*:${AWS::AccountId}:snapshot:crowdstrike-*" + - !Sub "arn:aws:rds:*:${AWS::AccountId}:cluster:*" + - !Sub "arn:aws:rds:*:${AWS::AccountId}:cluster-snapshot:crowdstrike-*" + Condition: + StringEquals: + "aws:RequestTag/ProvisionedBy": !FindInMap [ TagMap, CrowdStrikeTag, Value ] + + # Grants permission to copy snapshot with a tag mentioned in the condition. + - Sid: RDSLimitedPermissionForCopySnapshot + Action: + - rds:CopyDBSnapshot + - rds:CopyDBClusterSnapshot + Effect: Allow + Resource: + - !Sub "arn:aws:rds:*:${AWS::AccountId}:snapshot:*" + - !Sub "arn:aws:rds:*:${AWS::AccountId}:cluster-snapshot:*" + Condition: + StringEquals: + "aws:RequestTag/ProvisionedBy": !FindInMap [ TagMap, CrowdStrikeTag, Value ] + + # Grants permission to create db instance with a tag mentioned in the condition inside db cluster + - Sid: RDSPermissionDBClusterCreateInstance + Action: + - rds:CreateDBInstance + Effect: Allow + Resource: + - !Sub "arn:aws:rds:*:${AWS::AccountId}:db:*" + - !Sub "arn:aws:rds:*:${AWS::AccountId}:cluster:*" + - !Sub "arn:aws:rds:*:${AWS::AccountId}:cluster-snapshot:*" + Condition: + StringEquals: + "aws:RequestTag/ProvisionedBy": !FindInMap [ TagMap, CrowdStrikeTag, Value ] + + # Restricts create db instance permission to CrowdStrike VPC + - Sid: RDSPermissionDBClusterCreateInstanceCrowdStrikeVPC + Action: + - rds:CreateDBInstance + Effect: Allow + Resource: + - !Sub "arn:aws:rds:*:${AWS::AccountId}:subgrp:*" + Condition: + StringEquals: + "aws:ResourceTag/ProvisionedBy": !FindInMap [ TagMap, CrowdStrikeTag, Value ] + + # Grants permission to delete or modify RDS DB/cluster, which are tagged as mentioned in the condition + - Sid: RDSPermissionDeleteRestorePermissions + Action: + - rds:DeleteDBInstance + - rds:DeleteDBSnapshot + - rds:ModifyDBInstance + - rds:DeleteDBCluster + - rds:DeleteDBClusterSnapshot + - rds:ModifyDBCluster + - rds:RebootDBInstance + Effect: Allow + Resource: + - !Sub "arn:aws:rds:*:${AWS::AccountId}:db:*" + - !Sub "arn:aws:rds:*:${AWS::AccountId}:snapshot:*" + - !Sub "arn:aws:rds:*:${AWS::AccountId}:cluster:*" + - !Sub "arn:aws:rds:*:${AWS::AccountId}:cluster-snapshot:*" + - !Sub "arn:aws:rds:*:${AWS::AccountId}:og:*" + - !Sub "arn:aws:rds:*:${AWS::AccountId}:pg:*" + - !Sub "arn:aws:rds:*:${AWS::AccountId}:cluster-og:*" + - !Sub "arn:aws:rds:*:${AWS::AccountId}:cluster-pg:*" + Condition: + StringEquals: + "aws:ResourceTag/ProvisionedBy": !FindInMap [ TagMap, CrowdStrikeTag, Value ] + + - PolicyName: CrowdStrikeRedshiftClone + PolicyDocument: + Statement: + # Grants permission to create a cluster snapshot and restore cluster from snapshot + - Sid: RedshiftPermissionsForRestoring + Action: + - redshift:RestoreFromClusterSnapshot + - redshift:CreateClusterSnapshot + Effect: Allow + Resource: + - !Sub "arn:aws:redshift:*:${AWS::AccountId}:cluster:*" + - !Sub "arn:aws:redshift:*:${AWS::AccountId}:snapshot:*" + + # Grants permission to create tags, modify and delete CrowdStrike's clusters and snapshots + - Sid: RedshiftPermissionsForControllingClones + Action: + - redshift:CreateTags + - redshift:ModifyCluster* + - redshift:DeleteCluster + - redshift:DeleteClusterSnapshot + Effect: Allow + Resource: + - !Sub "arn:aws:redshift:*:${AWS::AccountId}:cluster:crowdstrike-*" + - !Sub "arn:aws:redshift:*:${AWS::AccountId}:snapshot:*/crowdstrike-snapshot-*" + + # Grants permission to secret manager to restore redshift's password managed by secret manager + - Sid: RedshiftPermissionsForSecretsManager + Action: + - secretsmanager:CreateSecret + - secretsmanager:TagResource + - secretsmanager:DescribeSecret + Effect: Allow + Resource: + - !Sub "arn:aws:secretsmanager:*:${AWS::AccountId}:secret:*" + + - PolicyName: RunDataScannerRestricted + PolicyDocument: + Statement: + # Grants permission to start, terminate EC2 and attach, detach, delete volume + # on CrowdStrike EC2 instance + - Sid: AllowInstanceOperationsWithRestrictions + Action: + - ec2:StartInstances + - ec2:TerminateInstances + - ec2:RebootInstances + Effect: Allow + Resource: + - !Sub "arn:aws:ec2:*:${AWS::AccountId}:instance/*" + - !Sub "arn:aws:ec2:*:${AWS::AccountId}:volume/*" + Condition: + StringEquals: + "ec2:ResourceTag/ProvisionedBy": !FindInMap [ TagMap, CrowdStrikeTag, Value ] + + # Grants permission to launch EC2 from public image + # Below resources are generic as they are not known during launch + - Sid: AllowRunDistrosInstances + Action: + - ec2:RunInstances + Effect: Allow + Resource: + - "arn:aws:ec2:*::image/*" + - "arn:aws:ec2:*::snapshot/*" + - !Sub "arn:aws:ec2:*:${AWS::AccountId}:volume/*" + - !Sub "arn:aws:ec2:*:${AWS::AccountId}:network-interface/*" + - !Sub "arn:aws:ec2:*:${AWS::AccountId}:security-group/*" + + # Grants permission to Launch EC2 and create volume for CrowdStrike EC2 instance + # The condition key aws:RequestTag is applicable to below resources + - Sid: AllowRunInstancesWithRestrictions + Action: + - ec2:RunInstances + Effect: Allow + Resource: + - !Sub "arn:aws:ec2:*:${AWS::AccountId}:instance/*" + - !Sub "arn:aws:ec2:*:${AWS::AccountId}:volume/*" + Condition: + StringEquals: + "aws:RequestTag/ProvisionedBy": !FindInMap [ TagMap, CrowdStrikeTag, Value ] + + # Grants permission to create below resources with only CrowdStrike tag + # on CrowdStrike EC2 instance + - Sid: AllowCreateTagsOnlyLaunching + Action: + - ec2:CreateTags + Effect: Allow + Resource: + - !Sub "arn:aws:ec2:*:${AWS::AccountId}:instance/*" + - !Sub "arn:aws:ec2:*:${AWS::AccountId}:volume/*" + Condition: + StringEquals: + "aws:RequestTag/ProvisionedBy": !FindInMap [ TagMap, CrowdStrikeTag, Value ] + "ec2:CreateAction": [ "RunInstances" ] + + # Grant permissions to attach instance profile for EC2 service created by CrowdStrike + - Sid: passRoleToEc2Service + Action: + - iam:PassRole + Effect: Allow + Resource: + - !Join [ "/", [ !Sub "arn:aws:iam::${AWS::AccountId}:role", !Ref ScannerRoleName ] ] + Condition: + StringEquals: + iam:PassedToService: ec2.amazonaws.com + + - Sid: ssmAmiAliasPermissions + Action: + - ssm:GetParameters + Effect: Allow + Resource: + - '*' + + CrowdStrikeAWSScannerRole: + Type: AWS::IAM::Role + Metadata: + cfn-lint: + config: + ignore_checks: + - EIAMPolicyWildcardResource # Role has * to allow for future service monitoring without stack updates + - EIAMPolicyActionWildcard # Role has * to allow for future service monitoring without stack updates + checkov: + skip: + - id: CKV_AWS_109 + comment: IAM PassRole action is constrained by resource ARN. + - id: CKV_AWS_111 + comment: IAM PassRole action is constrained by resource ARN. + - id: CKV_AWS_108 + comment: Read only permissions required for integration to function. + - id: CKV_AWS_107 + comment: Read only permissions required for integration to function. + cfn_nag: + rules_to_suppress: + - id: W11 + reason: Wildcard necessary for undefined ARNs + - id: F3 + reason: Wildcard necessary for undefined ARNs + - id: W28 + reason: Leaving explicit RoleName to remain at parity with product templates + Properties: + RoleName: !Ref ScannerRoleName + Path: / + AssumeRolePolicyDocument: + Statement: + - Action: sts:AssumeRole + Effect: Allow + Principal: + Service: ec2.amazonaws.com + ManagedPolicyArns: + - "arn:aws:iam::aws:policy/CloudWatchLogsReadOnlyAccess" + - "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" + Tags: + - Key: ProvisionedBy + Value: !FindInMap [ TagMap, CrowdStrikeTag, Value ] + Policies: + # Grants logs reading to use inside our instance profile + - PolicyName: CrowdStrikeLogsReader + PolicyDocument: + Statement: + - Action: + - rds:DownloadDBLogFilePortion + - rds:DownloadCompleteDBLogFile + - rds:DescribeDBLogFile + - logs:ListTagsLogGroup + - logs:DescribeQueries + - logs:GetLogRecord + - logs:DescribeLogGroups + - logs:DescribeLogStreams + - logs:DescribeSubscriptionFilters + - logs:StartQuery + - logs:DescribeMetricFilters + - logs:StopQuery + - logs:TestMetricFilter + - logs:GetLogDelivery + - logs:ListLogDeliveries + - logs:DescribeExportTasks + - logs:GetQueryResults + - logs:GetLogEvents + - logs:FilterLogEvents + - logs:DescribeQueryDefinitions + - logs:GetLogGroupFields + - logs:DescribeResourcePolicies + - logs:DescribeDestinations + Effect: Allow + Resource: '*' + + # Grants bucket reading to use inside our instance profile + - PolicyName: CrowdStrikeBucketReader + PolicyDocument: + Statement: + - Action: + - s3:Get* + - s3:List* + Effect: Allow + Resource: '*' + + # Grants DynamoDB reading to use inside our instance profile + - PolicyName: CrowdStrikeDynamoDBReader + PolicyDocument: + Statement: + - Action: + - dynamodb:BatchGet* + - dynamodb:Describe* + - dynamodb:List* + - dynamodb:Get* + - dynamodb:Query + - dynamodb:Scan + - dynamodb:PartiQLSelect + Effect: Allow + Resource: '*' + + # Grants Redshift reading to use inside our instance profile + - PolicyName: CrowdStrikeRedshiftReader + PolicyDocument: + Statement: + - Action: + - redshift:List* + - redshift:Describe* + - redshift:View* + - redshift:Get* + - redshift-serverless:List* + - redshift-serverless:Get* + Effect: Allow + Resource: '*' + + # Grants SecretManager reading to get client id and secret + - PolicyName: CrowdStrikeSecretReader + PolicyDocument: + Statement: + - Action: + - secretsmanager:GetSecretValue + - secretsmanager:DescribeSecret + - secretsmanager:ListSecrets + Effect: Allow + Resource: '*' + + CloudformationServiceRole: + Type: AWS::IAM::Role + Metadata: + cfn-lint: + config: + ignore_checks: + - EIAMPolicyWildcardResource # Role has * to allow for future service monitoring without stack updates + - EIAMPolicyActionWildcard # Role has * to allow for future service monitoring without stack updates + checkov: + skip: + - id: CKV_AWS_109 + comment: IAM PassRole action is constrained by resource ARN. + - id: CKV_AWS_111 + comment: IAM PassRole action is constrained by resource ARN. + - id: CKV_AWS_108 + comment: Read only permissions required for integration to function. + cfn_nag: + rules_to_suppress: + - id: W11 + reason: Wildcard necessary for undefined ARNs + - id: F3 + reason: Wildcard necessary for undefined ARNs + - id: W28 + reason: Leaving explicit RoleName to remain at parity with product templates + Properties: + RoleName: "CrowdStrikeDSPMCloudformationServiceRole" + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: + - cloudformation.amazonaws.com + Action: + - "sts:AssumeRole" + Policies: + - PolicyName: createDSPMEnvironmentResources + PolicyDocument: + Version: "2012-10-17" + Statement: + - Sid: getPublicParams + Effect: Allow + Action: + - ssm:GetParameter* + Resource: + - !Sub "arn:${AWS::Partition}:ssm:*::parameter/aws*" + - Sid: manageRolePolicies + Effect: Allow + Action: + - iam:AttachRolePolicy + - iam:DeleteRolePolicy + - iam:CreatePolicy + - iam:DeletePolicy + - iam:PutRolePolicy + - iam:TagRole + - iam:ListRoleTags + - iam:UntagRole + + Resource: "*" + - Sid: createDeleteVpcs + Effect: Allow + Action: + - ec2:AllocateAddress + - ec2:AssociateRouteTable + - ec2:AttachInternetGateway + - ec2:CreateTags + - ec2:CreateVpc + - ec2:CreateInternetGateway + - ec2:CreateNatGateway + - ec2:CreateSecurityGroup + - ec2:CreateSubnet + - ec2:CreateRouteTable + - ec2:CreateRoute + - ec2:Describe* + - ec2:DisassociateRouteTable + - ec2:DeleteInternetGateway + - ec2:DeleteNatGateway + - ec2:DeleteRouteTable + - ec2:DeleteRoute + - ec2:DeleteSubnet + - ec2:DeleteSecurityGroup + - ec2:DeleteTags + - ec2:DeleteVpc + - ec2:DetachInternetGateway + - ec2:DetachNetworkInterface + - ec2:List* + - ec2:ModifyVpcAttribute + - ec2:ReleaseAddress + - ec2:AuthorizeSecurityGroupIngress + Resource: "*" + - Sid: dbScanningSupport + Effect: Allow + Action: + - rds:Describe* + - rds:CreateDBSubnetGroup + - rds:DeleteDBSubnetGroup + - rds:ModifyDBSubnetGroup + - rds:AddTagsToResource + - redshift:Describe* + - redshift:CreateClusterSubnetGroup + - redshift:DeleteClusterSubnetGroup + - redshift:CreateTags + - redshift:ModifyClusterSubnetGroup + Resource: "*" + - Sid: kmsPermissions + Effect: Allow + Action: + - kms:* + Resource: "*" + + CustomResourceRole: + Type: AWS::IAM::Role + Metadata: + cfn-lint: + config: + ignore_checks: + - EIAMPolicyWildcardResource # Role has * to allow for future service monitoring without stack updates + - EIAMPolicyActionWildcard # Role has * to allow for future service monitoring without stack updates + checkov: + skip: + - id: CKV_AWS_109 + comment: IAM PassRole action is constrained by resource ARN. + - id: CKV_AWS_111 + comment: IAM PassRole action is constrained by resource ARN. + - id: CKV_AWS_108 + comment: Read only permissions required for integration to function. + cfn_nag: + rules_to_suppress: + - id: W11 + reason: Wildcard necessary for undefined ARNs + - id: F3 + reason: Wildcard necessary for undefined ARNs + - id: W28 + reason: Leaving explicit RoleName to remain at parity with product templates + Properties: + RoleName: "DSPMLambdaRole" + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - "sts:AssumeRole" + Policies: + - PolicyName: customResourceRequirements + PolicyDocument: + Version: "2012-10-17" + Statement: + - Sid: createDeleteSubstacks + Effect: Allow + Action: + - cloudformation:CreateStack + - cloudformation:CreateChangeSet + - cloudformation:ExecuteChangeSet + - cloudformation:UpdateStack + - cloudformation:DeleteStack + - cloudformation:Describe* + - cloudformation:List* + Resource: "*" + - Sid: passrole + Effect: Allow + Action: + - iam:PassRole + Resource: !GetAtt CloudformationServiceRole.Arn + - Sid: writeLogs + Effect: Allow + Action: + - logs:* + Resource: "*" + - Sid: createDeleteKMS + Effect: Allow + Action: + - kms:CancelKeyDeletion + - kms:CreateAlias + - kms:CreateKey + - kms:DescribeKey + - kms:DeleteAlias + - kms:EnableKey + - kms:ReplicateKey + - kms:ScheduleKeyDeletion + - kms:TagResource + Resource: "*" + - Sid: manageState + Effect: Allow + Action: + - ssm:PutParameter + - ssm:GetParameter + - ssm:DeleteParameter + Resource: "*" + - Sid: tagRole + Effect: Allow + Action: + - iam:ListRoleTags + - iam:UntagRole + - iam:TagRole + Resource: "*" + - Sid: secretPermissions + Effect: Allow + Action: + - secretsmanager:TagResource + - secretsmanager:DescribeSecret + Resource: "*" + - Sid: s3permissions + Effect: Allow + Action: + - s3:* + Resource: "*" + + CreateEnvironmentLambda: + Type: AWS::Lambda::Function + Metadata: + cfn-lint: + config: + ignore_checks: + - W2531 # Engineering planning on upgrading this function soon + cfn_nag: + rules_to_suppress: + - id: W89 + reason: Lambda custom resource only run during stack lifecycle events. + - id: W92 + reason: Lambda custom resource only run during stack lifecycle events. + checkov: + skip: + - id: CKV_AWS_115 + comment: Lambda does not need reserved concurrent executions. + - id: CKV_AWS_116 + comment: DLQ not needed, as Lambda function only triggered by CloudFormation events. + - id: CKV_AWS_117 + comment: Lambda does not need to communicate with VPC resources. + - id: CKV_AWS_173 + comment: Environment variables are not sensitive. + - id: CKV_SECRET_6 + comment: False Positive, no secret + Properties: + FunctionName: CrowdStrikeDSPMCreateEnvironmentLambda + Description: Lambda function for the implementation of nested stacks + Handler: index.lambda_handler + MemorySize: 128 + Runtime: python3.12 + Role: !GetAtt 'CustomResourceRole.Arn' + Timeout: 900 + Code: + ZipFile: | + # https://repost.aws/knowledge-center/cloudformation-lambda-resource-delete + import boto3 + import cfnresponse + + failure_statuses = ['CREATE_FAILED', 'DELETE_FAILED', 'UPDATE_FAILED', 'UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS', + 'UPDATE_ROLLBACK_COMPLETE', 'ROLLBACK_COMPLETE', 'ROLLBACK_FAILED', 'UPDATE_ROLLBACK_FAILED'] + + + def create_env(event, client, stack_name, env_cloudformation_url): + # idempotence + try: + result = client.describe_stacks( + StackName=stack_name + ) + if len(result['Stacks']) != 0: + return result['Stacks'][0] + + except Exception as e: + # if the stack does not yet exist, proceed to create + pass + + params = create_template_params(event) + client.create_stack( + StackName=stack_name, + TemplateURL=env_cloudformation_url, + DisableRollback=True, + Capabilities=['CAPABILITY_IAM'], + Parameters=params, + RoleARN=event['ResourceProperties']['CloudformationRole'] + ) + + try: + waiter = client.get_waiter('stack_create_complete') + waiter.wait( + StackName=stack_name, + WaiterConfig={ + 'Delay': 15, + 'MaxAttempts': 100 + } + ) + + except Exception: + # if operation failed, we will see failure status when we describe stack + pass + + try: + result = client.describe_stacks( + StackName=stack_name + ) + + if len(result['Stacks']) == 0: + raise Exception(f"No stack {stack_name} found") + + return result['Stacks'][0] + + except Exception as e: + stack_status = "CREATE_FAILED" + stack_status_reason = f"Error retrieving stack {stack_name}: {str(e)}" + return {"StackStatus": stack_status, "StackStatusReason": stack_status_reason} + + + def delete_env(event, client, stack_name): + # idempotence + try: + result = client.describe_stacks( + StackName=stack_name + ) + + if len(result['Stacks']) == 0: + raise Exception(f"No stack {stack_name} found") + + except Exception: + # if stack does not exist + return + + try: + client.delete_stack( + StackName=stack_name, + RoleARN=event['ResourceProperties']['CloudformationRole'] + ) + + waiter = client.get_waiter('stack_delete_complete') + waiter.wait( + StackName=stack_name, + WaiterConfig={ + 'Delay': 15, + 'MaxAttempts': 100 + } + ) + + except Exception: + # if operation failed, we will see failure status when we describe stack + pass + + # check if stack still exists + try: + result = client.describe_stacks( + StackName=stack_name + ) + + # if operation failed, return stack will show failure status + if len(result['Stacks']) != 0: + return result['Stacks'][0] + + except Exception: + pass + + stack_status = "DELETE_COMPLETE" + stack_status_reason = f"Stack {stack_name} deleted successfully" + return {"StackStatus": stack_status, "StackStatusReason": stack_status_reason} + + + def update_env(event, client, stack_name, env_cloudformation_url): + # check if stack exists + try: + result = client.describe_stacks( + StackName=stack_name + ) + if len(result['Stacks']) == 0: + raise Exception("Stack does not exists") + + + except Exception: + # if stack does not exist, create stack + response = create_env(event, client, stack_name, env_cloudformation_url) + if response['StackStatus'] in failure_statuses: + # if creation fails, delete before update rollback + delete_env(event, client, stack_name) + return response + + try: + # update stack + params = create_template_params(event) + client.update_stack( + StackName=stack_name, + TemplateURL=env_cloudformation_url, + Capabilities=['CAPABILITY_IAM'], + Parameters=params, + RoleARN=event['ResourceProperties']['CloudformationRole'] + ) + + waiter = client.get_waiter('stack_update_complete') + waiter.wait( + StackName=stack_name, + WaiterConfig={ + 'Delay': 15, + 'MaxAttempts': 100 + } + ) + + except Exception: + # if there are no updates for this stack, continue + pass + + # get the stack + try: + result = client.describe_stacks( + StackName=stack_name + ) + + if len(result['Stacks']) == 0: + raise Exception(f"Stack {stack_name} not found after update") + + return result['Stacks'][0] + + except Exception as e: + stack_status = "UPDATE_FAILED" + stack_status_reason = str(e) + return {"StackStatus": stack_status, "StackStatusReason": stack_status_reason} + + + def update_regions_tag(event, regions_string): + sm_client = boto3.client('secretsmanager') + regions_string_formatted = regions_string.replace(",", "/") + tags = [ + { + 'Key': event['ResourceProperties']['RegionsTagKey'], + 'Value': regions_string_formatted + } + ] + sm_client.tag_resource(SecretId=event['ResourceProperties']['SecretId'], Tags=tags) + + + def get_regions_tag(event): + sm_client = boto3.client('secretsmanager') + response = sm_client.describe_secret(SecretId=event['ResourceProperties']['SecretId']) + tags = response['Tags'] + deployment_regions = None + for tag in tags: + if tag['Key'] == event['ResourceProperties']['RegionsTagKey']: + deployment_regions = tag['Value'] + if deployment_regions is None: + raise Exception(f"No {event['ResourceProperties']['RegionsTagKey']} tag found on client secret") + return deployment_regions + + + def create_template_params(event): + return [ + { + "ParameterKey": "CrowdStrikeIntegrationRoleName", + "ParameterValue": event['ResourceProperties']['RoleName'], + }, + { + "ParameterKey": "CrowdStrikeScannerRoleName", + "ParameterValue": event['ResourceProperties']['ScannerRoleName'], + } + ] + + + def create_regions_list(regions_string, char): + regions = regions_string.split(char) + return [region.strip() for region in regions if region != ""] + + + def send_response(message, context, cfn_status, event, reason): + if event['RequestType'] == 'Update': + cfnresponse.send(event, context, cfn_status, message, physicalResourceId=event['PhysicalResourceId'], reason=reason) + return + + cfnresponse.send(event, context, cfn_status, message, reason=reason) + + + def lambda_handler(event, context): + # create variables + failed_stacks = "" + stack_names = "" + source_stack_name = "" + success_message = {"Message": "Operation successful"} + failure_message = {"Message": "Operation failed"} + + try: + # get template URL + env_cloudformation_url = event['ResourceProperties']['CloudformationTemplateURL'] + + # get regions + regions_string = event['ResourceProperties']['Regions'] + regions = create_regions_list(regions_string, ',') + + # get source stack name + split_stack_id = event['StackId'].split('/') + if len(split_stack_id) == 3: + source_stack_name = split_stack_id[1] + else: + raise Exception("Parent stack ID format invalid") + + # delete previous deployment regions on update request + if event['RequestType'] == 'Update': + previous_regions_string = get_regions_tag(event) + previous_regions = create_regions_list(previous_regions_string, '/') + regions_to_delete = [region for region in previous_regions if region not in regions] + + # delete regions that have been removed in update request + for region in regions_to_delete: + client = boto3.client('cloudformation', region_name=region) + # get stack name to delete + stack_name = '{}-substack-env-{}'.format(source_stack_name, region) + stack_names += f"{stack_name}, " + response = delete_env(event, client, stack_name) + + if response is None: + continue + + if response['StackStatus'] in failure_statuses: + failed_stacks += f"The stack {stack_name} failed to {event['RequestType']}. Failure reason: {response['StackStatusReason']} " + + # for each region, create substack for scanning environment + for region in regions: + client = boto3.client('cloudformation', region_name=region) + + # get stack name + stack_name = '{}-substack-env-{}'.format(source_stack_name, region) + stack_names += f"{stack_name}, " + + if event['RequestType'] == 'Create': + response = create_env(event, client, stack_name, env_cloudformation_url) + if event['RequestType'] == 'Update': + response = update_env(event, client, stack_name, env_cloudformation_url) + elif event['RequestType'] == 'Delete': + response = delete_env(event, client, stack_name) + + if response is None: + continue + + if response['StackStatus'] in failure_statuses: + failed_stacks += f"The stack {stack_name} failed to {event['RequestType']}. Failure reason: {response['StackStatusReason']} " + + if failed_stacks != "": + send_response(failure_message, context, cfnresponse.FAILED, event, failed_stacks) + return + + if event['RequestType'] != 'Delete': + # update CrowdStrike deployment regions tag on secret + update_regions_tag(event, regions_string) + + send_response(success_message, context, cfnresponse.SUCCESS, event, f"{event['RequestType']} successful. Substacks: {stack_names}") + return + + except Exception as e: + send_response(failure_message, context, cfnresponse.FAILED, event, f"An error occurred during {event['RequestType']} request: {str(e)}") + return + + + CreateEnvironments: + Type: AWS::CloudFormation::CustomResource + DependsOn: + - CrowdStrikeAWSIntegrationRole + - CrowdStrikeAWSScannerRole + Properties: + ServiceToken: !GetAtt CreateEnvironmentLambda.Arn + RoleName: !Ref DSPMRoleName + ScannerRoleName: !Ref ScannerRoleName + CloudformationRole: !GetAtt CloudformationServiceRole.Arn + RegionsTagKey: !FindInMap [TagMap, DeploymentRegions, Key] + Regions: !Ref DSPMRegions + SourceRegion: !Ref "AWS::Region" + AccountId: AWS::AccountId + SecretId: !Ref ClientSecrets + CloudformationTemplateURL: !Sub https://${SourceS3BucketName}.s3.${S3BucketRegion}.amazonaws.com/${SourceS3BucketNamePrefix}/templates/aws_cspm_cloudformation_dspm_env.yml diff --git a/templates/ssm-association-stackset.yml b/templates/ssm-association-stackset.yml index cb4320b..5201f30 100644 --- a/templates/ssm-association-stackset.yml +++ b/templates/ssm-association-stackset.yml @@ -49,6 +49,11 @@ Parameters: Resources: CrowdStrikeSecrets: Type: AWS::SecretsManager::Secret + Metadata: + checkov: + skip: + - id: CKV_AWS_149 + comment: The default key aws/secretsmanager is sufficient to secure this resource Properties: Description: CrowdStrike Falcon Credentials for SSM Distributor Name: !Ref SecretsManagerSecretName