diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/LogGroupIntegDefaultTestDeployAssertA9999A13.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/LogGroupIntegDefaultTestDeployAssertA9999A13.assets.json new file mode 100644 index 0000000000000..aea8c96c649cf --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/LogGroupIntegDefaultTestDeployAssertA9999A13.assets.json @@ -0,0 +1,19 @@ +{ + "version": "31.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "LogGroupIntegDefaultTestDeployAssertA9999A13.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/LogGroupIntegDefaultTestDeployAssertA9999A13.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/LogGroupIntegDefaultTestDeployAssertA9999A13.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/LogGroupIntegDefaultTestDeployAssertA9999A13.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/aws-cdk-log-group-integ.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/aws-cdk-log-group-integ.assets.json new file mode 100644 index 0000000000000..847c4304187f4 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/aws-cdk-log-group-integ.assets.json @@ -0,0 +1,19 @@ +{ + "version": "31.0.0", + "files": { + "cadd724ef1cce56f77546968b304b105422abec3535dfa2a9c10aca7f84f9811": { + "source": { + "path": "aws-cdk-log-group-integ.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "cadd724ef1cce56f77546968b304b105422abec3535dfa2a9c10aca7f84f9811.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/aws-cdk-log-group-integ.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/aws-cdk-log-group-integ.template.json new file mode 100644 index 0000000000000..86157f4999268 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/aws-cdk-log-group-integ.template.json @@ -0,0 +1,145 @@ +{ + "Resources": { + "LogGroupLambdaAuditF8F47F46": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": 731 + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "auditbucketidE6660EBD": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "LogGroupLambdaAC756C5B": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "DataProtectionPolicy": { + "name": "policy-name", + "description": "policy description", + "version": "2021-06-01", + "statement": [ + { + "sid": "audit-statement-cdk", + "dataIdentifier": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":dataprotection::aws:data-identifier/DriversLicense-US" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":dataprotection::aws:data-identifier/EmailAddress" + ] + ] + } + ], + "operation": { + "audit": { + "findingsDestination": { + "cloudWatchLogs": { + "logGroup": { + "Ref": "LogGroupLambdaAuditF8F47F46" + } + }, + "s3": { + "bucket": { + "Ref": "auditbucketidE6660EBD" + } + } + } + } + } + }, + { + "sid": "redact-statement-cdk", + "dataIdentifier": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":dataprotection::aws:data-identifier/DriversLicense-US" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":dataprotection::aws:data-identifier/EmailAddress" + ] + ] + } + ], + "operation": { + "deidentify": { + "maskConfig": {} + } + } + } + ] + }, + "RetentionInDays": 731 + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/cdk.out new file mode 100644 index 0000000000000..7925065efbcc4 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"31.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/integ.json new file mode 100644 index 0000000000000..6ba158b4716ca --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "31.0.0", + "testCases": { + "LogGroupInteg/DefaultTest": { + "stacks": [ + "aws-cdk-log-group-integ" + ], + "assertionStack": "LogGroupInteg/DefaultTest/DeployAssert", + "assertionStackName": "LogGroupIntegDefaultTestDeployAssertA9999A13" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/manifest.json new file mode 100644 index 0000000000000..6287f323cc401 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/manifest.json @@ -0,0 +1,123 @@ +{ + "version": "31.0.0", + "artifacts": { + "aws-cdk-log-group-integ.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "aws-cdk-log-group-integ.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "aws-cdk-log-group-integ": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "aws-cdk-log-group-integ.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/cadd724ef1cce56f77546968b304b105422abec3535dfa2a9c10aca7f84f9811.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "aws-cdk-log-group-integ.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "aws-cdk-log-group-integ.assets" + ], + "metadata": { + "/aws-cdk-log-group-integ/LogGroupLambdaAudit/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "LogGroupLambdaAuditF8F47F46" + } + ], + "/aws-cdk-log-group-integ/audit-bucket-id/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "auditbucketidE6660EBD" + } + ], + "/aws-cdk-log-group-integ/LogGroupLambda/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "LogGroupLambdaAC756C5B" + } + ], + "/aws-cdk-log-group-integ/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/aws-cdk-log-group-integ/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "aws-cdk-log-group-integ" + }, + "LogGroupIntegDefaultTestDeployAssertA9999A13.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "LogGroupIntegDefaultTestDeployAssertA9999A13.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "LogGroupIntegDefaultTestDeployAssertA9999A13": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "LogGroupIntegDefaultTestDeployAssertA9999A13.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "LogGroupIntegDefaultTestDeployAssertA9999A13.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "LogGroupIntegDefaultTestDeployAssertA9999A13.assets" + ], + "metadata": { + "/LogGroupInteg/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/LogGroupInteg/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "LogGroupInteg/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/tree.json new file mode 100644 index 0000000000000..8c2e70be9b477 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.js.snapshot/tree.json @@ -0,0 +1,257 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "aws-cdk-log-group-integ": { + "id": "aws-cdk-log-group-integ", + "path": "aws-cdk-log-group-integ", + "children": { + "LogGroupLambdaAudit": { + "id": "LogGroupLambdaAudit", + "path": "aws-cdk-log-group-integ/LogGroupLambdaAudit", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-log-group-integ/LogGroupLambdaAudit/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Logs::LogGroup", + "aws:cdk:cloudformation:props": { + "retentionInDays": 731 + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_logs.CfnLogGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_logs.LogGroup", + "version": "0.0.0" + } + }, + "audit-bucket-id": { + "id": "audit-bucket-id", + "path": "aws-cdk-log-group-integ/audit-bucket-id", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-log-group-integ/audit-bucket-id/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::Bucket", + "aws:cdk:cloudformation:props": {} + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3.CfnBucket", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3.Bucket", + "version": "0.0.0" + } + }, + "LogGroupLambda": { + "id": "LogGroupLambda", + "path": "aws-cdk-log-group-integ/LogGroupLambda", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-log-group-integ/LogGroupLambda/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Logs::LogGroup", + "aws:cdk:cloudformation:props": { + "dataProtectionPolicy": { + "name": "policy-name", + "description": "policy description", + "version": "2021-06-01", + "statement": [ + { + "sid": "audit-statement-cdk", + "dataIdentifier": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":dataprotection::aws:data-identifier/DriversLicense-US" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":dataprotection::aws:data-identifier/EmailAddress" + ] + ] + } + ], + "operation": { + "audit": { + "findingsDestination": { + "cloudWatchLogs": { + "logGroup": { + "Ref": "LogGroupLambdaAuditF8F47F46" + } + }, + "s3": { + "bucket": { + "Ref": "auditbucketidE6660EBD" + } + } + } + } + } + }, + { + "sid": "redact-statement-cdk", + "dataIdentifier": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":dataprotection::aws:data-identifier/DriversLicense-US" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":dataprotection::aws:data-identifier/EmailAddress" + ] + ] + } + ], + "operation": { + "deidentify": { + "maskConfig": {} + } + } + } + ] + }, + "retentionInDays": 731 + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_logs.CfnLogGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_logs.LogGroup", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "aws-cdk-log-group-integ/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "aws-cdk-log-group-integ/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + }, + "LogGroupInteg": { + "id": "LogGroupInteg", + "path": "LogGroupInteg", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "LogGroupInteg/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "LogGroupInteg/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.270" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "LogGroupInteg/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "LogGroupInteg/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "LogGroupInteg/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.270" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.ts new file mode 100644 index 0000000000000..c865f5238af2e --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-logs/test/integ.log-group.ts @@ -0,0 +1,31 @@ +import { Bucket } from 'aws-cdk-lib/aws-s3'; +import { App, Stack, StackProps } from 'aws-cdk-lib'; +import { IntegTest } from '@aws-cdk/integ-tests-alpha'; +import { LogGroup, DataProtectionPolicy, DataIdentifier } from 'aws-cdk-lib/aws-logs'; + +class LogGroupIntegStack extends Stack { + constructor(scope: App, id: string, props?: StackProps) { + super(scope, id, props); + + var audit = new LogGroup(this, 'LogGroupLambdaAudit', {}); + + var bucket = new Bucket(this, 'audit-bucket-id'); + + const dataProtectionPolicy = new DataProtectionPolicy({ + name: 'policy-name', + description: 'policy description', + identifiers: [DataIdentifier.DRIVERSLICENSE_US, new DataIdentifier('EmailAddress')], + logGroupAuditDestination: audit, + s3BucketAuditDestination: bucket, + }); + + new LogGroup(this, 'LogGroupLambda', { + dataProtectionPolicy: dataProtectionPolicy, + }); + } +} + +const app = new App(); +const stack = new LogGroupIntegStack(app, 'aws-cdk-log-group-integ'); +new IntegTest(app, 'LogGroupInteg', { testCases: [stack] }); +app.synth(); \ No newline at end of file diff --git a/packages/aws-cdk-lib/aws-logs/README.md b/packages/aws-cdk-lib/aws-logs/README.md index a5cd44de73b31..2b5283f287ab1 100644 --- a/packages/aws-cdk-lib/aws-logs/README.md +++ b/packages/aws-cdk-lib/aws-logs/README.md @@ -334,6 +334,51 @@ new logs.QueryDefinition(this, 'QueryDefinition', { }); ``` +## Data Protection Policy + +Creates a data protection policy and assigns it to the log group. A data protection policy can help safeguard sensitive data that's ingested by the log group by auditing and masking the sensitive log data. When a user who does not have permission to view masked data views a log event that includes masked data, the sensitive data is replaced by asterisks. + +For more information, see [Protect sensitive log data with masking](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/mask-sensitive-log-data.html). + +For a list of types of identifiers that can be audited and masked, see [Types of data that you can protect](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/protect-sensitive-log-data-types.html) + +If a new identifier is supported but not yet in the `DataIdentifiers` enum, the full ARN of the identifier can be supplied in `identifierArnStrings` instead. + +Each policy may consist of a log group, S3 bucket, and/or Firehose delivery stream audit destination. + +Example: + +```ts +import { Bucket } from '@aws-cdk/aws-s3'; +import { LogGroup } from '@aws-cdk/logs'; +import * as kinesisfirehose from '@aws-cdk/aws-kinesisfirehose'; + + +const logGroupDestination = new LogGroup(this, 'LogGroupLambdaAudit', { + logGroupName: 'auditDestinationForCDK', +}); + +const s3Destination = new Bucket(this, 'audit-bucket-id'); + +const deliveryStream = new firehose.DeliveryStream(this, 'Delivery Stream', { + destinations: [s3Destination], +}); + +const dataProtectionPolicy = new DataProtectionPolicy({ + name: 'data protection policy', + description: 'policy description', + identifiers: [DataIdentifier.DRIVERSLICENSE_US, new DataIdentifier('EmailAddress')], + logGroupAuditDestination: logGroupDestination, + s3BucketAuditDestination: s3Destination, + deliveryStreamAuditDestination: deliveryStream.deliveryStreamName, +}); + +new LogGroup(this, 'LogGroupLambda', { + logGroupName: 'cdkIntegLogGroup', + dataProtectionPolicy: dataProtectionPolicy, +}); +``` + ## Notes Be aware that Log Group ARNs will always have the string `:*` appended to diff --git a/packages/aws-cdk-lib/aws-logs/lib/data-protection-policy.ts b/packages/aws-cdk-lib/aws-logs/lib/data-protection-policy.ts new file mode 100644 index 0000000000000..903a8d71a7b1d --- /dev/null +++ b/packages/aws-cdk-lib/aws-logs/lib/data-protection-policy.ts @@ -0,0 +1,284 @@ +import { Stack } from '../../core'; +import { Construct } from 'constructs'; +import { ILogGroup } from './log-group'; +import { IBucket } from '../../aws-s3'; +/** + * Creates a data protection policy for CloudWatch Logs log groups. + */ +export class DataProtectionPolicy { + + private readonly dataProtectionPolicyProps: DataProtectionPolicyProps; + + constructor(props: DataProtectionPolicyProps) { + if (props.identifiers.length == 0) { + throw new Error("DataIdentifier cannot be empty"); + } + this.dataProtectionPolicyProps = props; + } + + /** + * @internal + */ + public _bind(_scope: Construct): DataProtectionPolicyConfig { + const name = this.dataProtectionPolicyProps.name || 'data-protection-policy-cdk'; + const description = this.dataProtectionPolicyProps.description || 'cdk generated data protection policy'; + const version = '2021-06-01'; + + const findingsDestination: FindingsDestination = {}; + if (this.dataProtectionPolicyProps.logGroupAuditDestination) { + findingsDestination.cloudWatchLogs = { + logGroup: this.dataProtectionPolicyProps.logGroupAuditDestination.logGroupName, + }; + } + + if (this.dataProtectionPolicyProps.s3BucketAuditDestination) { + findingsDestination.s3 = { + bucket: this.dataProtectionPolicyProps.s3BucketAuditDestination.bucketName, + }; + } + + if (this.dataProtectionPolicyProps.deliveryStreamNameAuditDestination) { + findingsDestination.firehose = { + deliveryStream: this.dataProtectionPolicyProps.deliveryStreamNameAuditDestination, + }; + } + + const identifierArns: string[] = []; + for (let identifier of this.dataProtectionPolicyProps.identifiers) { + identifierArns.push(Stack.of(_scope).formatArn({ + resource: 'data-identifier', + region: '', + account: 'aws', + service: 'dataprotection', + resourceName: identifier.toString(), + })); + }; + + const statement = [ + { + sid: 'audit-statement-cdk', + dataIdentifier: identifierArns, + operation: { + audit: { + findingsDestination: findingsDestination, + }, + }, + }, + { + sid: 'redact-statement-cdk', + dataIdentifier: identifierArns, + operation: { + deidentify: { + maskConfig: {}, + }, + }, + }, + ]; + return {name, description, version, statement}; + } +} + +interface FindingsDestination { + cloudWatchLogs?: CloudWatchLogsDestination; + firehose?: FirehoseDestination; + s3?: S3Destination; +} + +interface CloudWatchLogsDestination { + logGroup: string; +} + +interface FirehoseDestination { + deliveryStream: string; +} + +interface S3Destination { + bucket: string; +} + +/** + * Interface representing a data protection policy + */ +export interface DataProtectionPolicyConfig { + /** + * Name of the data protection policy + * + * @default - 'data-protection-policy-cdk' + */ + readonly name: string; + + /** + * Description of the data protection policy + * + * @default - 'cdk generated data protection policy' + */ + readonly description: string; + + /** + * Version of the data protection policy + */ + readonly version: string; + + /** + * Statements within the data protection policy. Must contain one Audit and one Redact statement + */ + readonly statement: any; +} + +/** + * Properties for creating a data protection policy + */ +export interface DataProtectionPolicyProps { + /** + * Name of the data protection policy + * + * @default - 'data-protection-policy-cdk' + */ + readonly name?: string; + + /** + * Description of the data protection policy + * + * @default - 'cdk generated data protection policy' + */ + readonly description?: string; + + /** + * List of data protection identifiers. Must be in the following list: https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/protect-sensitive-log-data-types.html + * + */ + readonly identifiers: DataIdentifier[]; + + /** + * CloudWatch Logs log group to send audit findings to. The log group must already exist prior to creating the data protection policy. + * + * @default - no CloudWatch Logs audit destination + */ + readonly logGroupAuditDestination?: ILogGroup; + + /** + * S3 bucket to send audit findings to. The bucket must already exist. + * + * @default - no S3 bucket audit destination + */ + readonly s3BucketAuditDestination?: IBucket; + + /** + * Amazon Kinesis Data Firehose delivery stream to send audit findings to. The delivery stream must already exist. + * + * @default - no firehose delivery stream audit destination + */ + readonly deliveryStreamNameAuditDestination?: string; +} + + +/** + * A data protection identifier. If an identifier is supported but not in this class, it can be passed in the constructor instead. + */ +export class DataIdentifier { + public static readonly ADDRESS = new DataIdentifier('Address'); + public static readonly AWSSECRETKEY = new DataIdentifier('AwsSecretKey'); + public static readonly BANKACCOUNTNUMBER_DE = new DataIdentifier('BankAccountNumber-DE'); + public static readonly BANKACCOUNTNUMBER_ES = new DataIdentifier('BankAccountNumber-ES'); + public static readonly BANKACCOUNTNUMBER_FR = new DataIdentifier('BankAccountNumber-FR'); + public static readonly BANKACCOUNTNUMBER_GB = new DataIdentifier('BankAccountNumber-GB'); + public static readonly BANKACCOUNTNUMBER_IT = new DataIdentifier('BankAccountNumber-IT'); + public static readonly BANKACCOUNTNUMBER_US = new DataIdentifier('BankAccountNumber-US'); + public static readonly CEPCODE_BR = new DataIdentifier('CepCode-BR'); + public static readonly CNPJ_BR = new DataIdentifier('Cnpj-BR'); + public static readonly CPFCODE_BR = new DataIdentifier('CpfCode-BR'); + public static readonly CREDITCARDEXPIRATION = new DataIdentifier('CreditCardExpiration'); + public static readonly CREDITCARDNUMBER = new DataIdentifier('CreditCardNumber'); + public static readonly CREDITCARDSECURITYCODE = new DataIdentifier('CreditCardSecurityCode'); + public static readonly DRIVERSLICENSE_AT = new DataIdentifier('DriversLicense-AT'); + public static readonly DRIVERSLICENSE_AU = new DataIdentifier('DriversLicense-AU'); + public static readonly DRIVERSLICENSE_BE = new DataIdentifier('DriversLicense-BE'); + public static readonly DRIVERSLICENSE_BG = new DataIdentifier('DriversLicense-BG'); + public static readonly DRIVERSLICENSE_CA = new DataIdentifier('DriversLicense-CA'); + public static readonly DRIVERSLICENSE_CY = new DataIdentifier('DriversLicense-CY'); + public static readonly DRIVERSLICENSE_CZ = new DataIdentifier('DriversLicense-CZ'); + public static readonly DRIVERSLICENSE_DE = new DataIdentifier('DriversLicense-DE'); + public static readonly DRIVERSLICENSE_DK = new DataIdentifier('DriversLicense-DK'); + public static readonly DRIVERSLICENSE_EE = new DataIdentifier('DriversLicense-EE'); + public static readonly DRIVERSLICENSE_ES = new DataIdentifier('DriversLicense-ES'); + public static readonly DRIVERSLICENSE_FI = new DataIdentifier('DriversLicense-FI'); + public static readonly DRIVERSLICENSE_FR = new DataIdentifier('DriversLicense-FR'); + public static readonly DRIVERSLICENSE_GB = new DataIdentifier('DriversLicense-GB'); + public static readonly DRIVERSLICENSE_GR = new DataIdentifier('DriversLicense-GR'); + public static readonly DRIVERSLICENSE_HR = new DataIdentifier('DriversLicense-HR'); + public static readonly DRIVERSLICENSE_HU = new DataIdentifier('DriversLicense-HU'); + public static readonly DRIVERSLICENSE_IE = new DataIdentifier('DriversLicense-IE'); + public static readonly DRIVERSLICENSE_IT = new DataIdentifier('DriversLicense-IT'); + public static readonly DRIVERSLICENSE_LT = new DataIdentifier('DriversLicense-LT'); + public static readonly DRIVERSLICENSE_LU = new DataIdentifier('DriversLicense-LU'); + public static readonly DRIVERSLICENSE_LV = new DataIdentifier('DriversLicense-LV'); + public static readonly DRIVERSLICENSE_MT = new DataIdentifier('DriversLicense-MT'); + public static readonly DRIVERSLICENSE_NL = new DataIdentifier('DriversLicense-NL'); + public static readonly DRIVERSLICENSE_PL = new DataIdentifier('DriversLicense-PL'); + public static readonly DRIVERSLICENSE_PT = new DataIdentifier('DriversLicense-PT'); + public static readonly DRIVERSLICENSE_RO = new DataIdentifier('DriversLicense-RO'); + public static readonly DRIVERSLICENSE_SE = new DataIdentifier('DriversLicense-SE'); + public static readonly DRIVERSLICENSE_SI = new DataIdentifier('DriversLicense-SI'); + public static readonly DRIVERSLICENSE_SK = new DataIdentifier('DriversLicense-SK'); + public static readonly DRIVERSLICENSE_US = new DataIdentifier('DriversLicense-US'); + public static readonly DRUGENFORCEMENTAGENCYNUMBER_US = new DataIdentifier('DrugEnforcementAgencyNumber-US'); + public static readonly ELECTORALROLLNUMBER_GB = new DataIdentifier('ElectoralRollNumber-GB'); + public static readonly EMAILADDRESS = new DataIdentifier('EmailAddress'); + public static readonly HEALTHINSURANCECARDNUMBER_EU = new DataIdentifier('HealthInsuranceCardNumber-EU'); + public static readonly HEALTHINSURANCECLAIMNUMBER_US = new DataIdentifier('HealthInsuranceClaimNumber-US'); + public static readonly HEALTHINSURANCENUMBER_FR = new DataIdentifier('HealthInsuranceNumber-FR'); + public static readonly HEALTHCAREPROCEDURECODE_US = new DataIdentifier('HealthcareProcedureCode-US'); + public static readonly INDIVIDUALTAXIDENTIFICATIONNUMBER_US = new DataIdentifier('IndividualTaxIdentificationNumber-US'); + public static readonly INSEECODE_FR = new DataIdentifier('InseeCode-FR'); + public static readonly IPADDRESS = new DataIdentifier('IpAddress'); + public static readonly LATLONG = new DataIdentifier('LatLong'); + public static readonly MEDICAREBENEFICIARYNUMBER_US = new DataIdentifier('MedicareBeneficiaryNumber-US'); + public static readonly NAME = new DataIdentifier('Name'); + public static readonly NATIONALDRUGCODE_US = new DataIdentifier('NationalDrugCode-US'); + public static readonly NATIONALIDENTIFICATIONNUMBER_DE = new DataIdentifier('NationalIdentificationNumber-DE'); + public static readonly NATIONALIDENTIFICATIONNUMBER_ES = new DataIdentifier('NationalIdentificationNumber-ES'); + public static readonly NATIONALIDENTIFICATIONNUMBER_IT = new DataIdentifier('NationalIdentificationNumber-IT'); + public static readonly NATIONALINSURANCENUMBER_GB = new DataIdentifier('NationalInsuranceNumber-GB'); + public static readonly NATIONALPROVIDERID_US = new DataIdentifier('NationalProviderId-US'); + public static readonly NHSNUMBER_GB = new DataIdentifier('NhsNumber-GB'); + public static readonly NIENUMBER_ES = new DataIdentifier('NieNumber-ES'); + public static readonly NIFNUMBER_ES = new DataIdentifier('NifNumber-ES'); + public static readonly OPENSSHPRIVATEKEY = new DataIdentifier('OpenSshPrivateKey'); + public static readonly PASSPORTNUMBER_CA = new DataIdentifier('PassportNumber-CA'); + public static readonly PASSPORTNUMBER_DE = new DataIdentifier('PassportNumber-DE'); + public static readonly PASSPORTNUMBER_ES = new DataIdentifier('PassportNumber-ES'); + public static readonly PASSPORTNUMBER_FR = new DataIdentifier('PassportNumber-FR'); + public static readonly PASSPORTNUMBER_GB = new DataIdentifier('PassportNumber-GB'); + public static readonly PASSPORTNUMBER_IT = new DataIdentifier('PassportNumber-IT'); + public static readonly PASSPORTNUMBER_US = new DataIdentifier('PassportNumber-US'); + public static readonly PERMANENTRESIDENCENUMBER_CA = new DataIdentifier('PermanentResidenceNumber-CA'); + public static readonly PERSONALHEALTHNUMBER_CA = new DataIdentifier('PersonalHealthNumber-CA'); + public static readonly PGPPRIVATEKEY = new DataIdentifier('PgpPrivateKey'); + public static readonly PHONENUMBER = new DataIdentifier('PhoneNumber'); + public static readonly PHONENUMBER_BR = new DataIdentifier('PhoneNumber-BR'); + public static readonly PHONENUMBER_DE = new DataIdentifier('PhoneNumber-DE'); + public static readonly PHONENUMBER_ES = new DataIdentifier('PhoneNumber-ES'); + public static readonly PHONENUMBER_FR = new DataIdentifier('PhoneNumber-FR'); + public static readonly PHONENUMBER_GB = new DataIdentifier('PhoneNumber-GB'); + public static readonly PHONENUMBER_IT = new DataIdentifier('PhoneNumber-IT'); + public static readonly PHONENUMBER_US = new DataIdentifier('PhoneNumber-US'); + public static readonly PKCSPRIVATEKEY = new DataIdentifier('PkcsPrivateKey'); + public static readonly POSTALCODE_CA = new DataIdentifier('PostalCode-CA'); + public static readonly PUTTYPRIVATEKEY = new DataIdentifier('PuttyPrivateKey'); + public static readonly RGNUMBER_BR = new DataIdentifier('RgNumber-BR'); + public static readonly SOCIALINSURANCENUMBER_CA = new DataIdentifier('SocialInsuranceNumber-CA'); + public static readonly SSN_ES = new DataIdentifier('Ssn-ES'); + public static readonly SSN_US = new DataIdentifier('Ssn-US'); + public static readonly TAXID_DE = new DataIdentifier('TaxId-DE'); + public static readonly TAXID_ES = new DataIdentifier('TaxId-ES'); + public static readonly TAXID_FR = new DataIdentifier('TaxId-FR'); + public static readonly TAXID_GB = new DataIdentifier('TaxId-GB'); + public static readonly VEHICLEIDENTIFICATIONNUMBER = new DataIdentifier('VehicleIdentificationNumber'); + public static readonly ZIPCODE_US = new DataIdentifier('ZipCode-US'); + + constructor(private readonly identifier: string) { } + + public toString(): string { + return this.identifier; + } +} diff --git a/packages/aws-cdk-lib/aws-logs/lib/index.ts b/packages/aws-cdk-lib/aws-logs/lib/index.ts index d8df4e7b189e5..71f2717cc4447 100644 --- a/packages/aws-cdk-lib/aws-logs/lib/index.ts +++ b/packages/aws-cdk-lib/aws-logs/lib/index.ts @@ -7,6 +7,7 @@ export * from './subscription-filter'; export * from './log-retention'; export * from './policy'; export * from './query-definition'; +export * from './data-protection-policy'; // AWS::Logs CloudFormation Resources: export * from './logs.generated'; diff --git a/packages/aws-cdk-lib/aws-logs/lib/log-group.ts b/packages/aws-cdk-lib/aws-logs/lib/log-group.ts index 48c5f25bf632f..6a9a0452e9e3b 100644 --- a/packages/aws-cdk-lib/aws-logs/lib/log-group.ts +++ b/packages/aws-cdk-lib/aws-logs/lib/log-group.ts @@ -3,6 +3,7 @@ import * as iam from '../../aws-iam'; import * as kms from '../../aws-kms'; import { Arn, ArnFormat, RemovalPolicy, Resource, Stack, Token } from '../../core'; import { Construct } from 'constructs'; +import { DataProtectionPolicy } from './data-protection-policy'; import { LogStream } from './log-stream'; import { CfnLogGroup } from './logs.generated'; import { MetricFilter } from './metric-filter'; @@ -385,6 +386,13 @@ export interface LogGroupProps { */ readonly logGroupName?: string; + /** + * Data Protection Policy for this log group. + * + * @default - no data protection policy + */ + readonly dataProtectionPolicy?: DataProtectionPolicy; + /** * How long, in days, the log contents will be retained. * @@ -473,6 +481,7 @@ export class LogGroup extends LogGroupBase { kmsKeyId: props.encryptionKey?.keyArn, logGroupName: this.physicalName, retentionInDays, + dataProtectionPolicy: props.dataProtectionPolicy?._bind(this), }); resource.applyRemovalPolicy(props.removalPolicy); diff --git a/packages/aws-cdk-lib/aws-logs/test/loggroup.test.ts b/packages/aws-cdk-lib/aws-logs/test/loggroup.test.ts index 1520cec0dadd0..37dd28338e6d0 100644 --- a/packages/aws-cdk-lib/aws-logs/test/loggroup.test.ts +++ b/packages/aws-cdk-lib/aws-logs/test/loggroup.test.ts @@ -1,8 +1,9 @@ import { Template } from '../../assertions'; import * as iam from '../../aws-iam'; import * as kms from '../../aws-kms'; +import { Bucket } from '../../aws-s3'; import { CfnParameter, Fn, RemovalPolicy, Stack } from '../../core'; -import { LogGroup, RetentionDays } from '../lib'; +import { LogGroup, RetentionDays , DataProtectionPolicy, DataIdentifier} from '../lib'; describe('log group', () => { test('set kms key when provided', () => { @@ -454,6 +455,235 @@ describe('log group', () => { LogGroupName: 'my-log-group', }); }); + + test('set data protection policy with custom name and description and no audit destinations', () => { + // GIVEN + const stack = new Stack(); + + const dataProtectionPolicy = new DataProtectionPolicy({ + name: 'test-policy-name', + description: 'test description', + identifiers: [DataIdentifier.EMAILADDRESS], + }); + + // WHEN + const logGroupName = 'test-log-group'; + new LogGroup(stack, 'LogGroup', { + logGroupName: logGroupName, + dataProtectionPolicy: dataProtectionPolicy, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Logs::LogGroup', { + LogGroupName: logGroupName, + DataProtectionPolicy: { + name: 'test-policy-name', + description: 'test description', + version: '2021-06-01', + statement: [ + { + sid: 'audit-statement-cdk', + dataIdentifier: [ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':dataprotection::aws:data-identifier/EmailAddress', + ], + ], + }, + ], + operation: { + audit: { + findingsDestination: {}, + }, + }, + }, + { + sid: 'redact-statement-cdk', + dataIdentifier: [ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':dataprotection::aws:data-identifier/EmailAddress', + ], + ], + }, + ], + operation: { + deidentify: { + maskConfig: {}, + }, + }, + }, + ], + }, + }); + }); + + test('set data protection policy string-based data identifier', () => { + // GIVEN + const stack = new Stack(); + + const dataProtectionPolicy = new DataProtectionPolicy({ + name: 'test-policy-name', + description: 'test description', + identifiers: [new DataIdentifier('NewIdentifier')], + }); + + // WHEN + const logGroupName = 'test-log-group'; + new LogGroup(stack, 'LogGroup', { + logGroupName: logGroupName, + dataProtectionPolicy: dataProtectionPolicy, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Logs::LogGroup', { + LogGroupName: logGroupName, + DataProtectionPolicy: { + name: 'test-policy-name', + description: 'test description', + version: '2021-06-01', + statement: [ + { + sid: 'audit-statement-cdk', + dataIdentifier: [ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':dataprotection::aws:data-identifier/NewIdentifier', + ], + ], + }, + ], + operation: { + audit: { + findingsDestination: {}, + }, + }, + }, + { + sid: 'redact-statement-cdk', + dataIdentifier: [ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':dataprotection::aws:data-identifier/NewIdentifier', + ], + ], + }, + ], + operation: { + deidentify: { + maskConfig: {}, + }, + }, + }, + ], + }, + }); + }); + + test('set data protection policy with audit destinations', () => { + // GIVEN + const stack = new Stack(); + + const auditLogGroup = new LogGroup(stack, 'LogGroupAudit', {logGroupName: 'audit-log-group'}); + const auditS3Bucket = new Bucket(stack, 'BucketAudit', {bucketName: 'audit-bucket'}); + const auditDeliveryStreamName = 'delivery-stream-name'; + + const dataProtectionPolicy = new DataProtectionPolicy({ + identifiers: [DataIdentifier.EMAILADDRESS], + logGroupAuditDestination: auditLogGroup, + s3BucketAuditDestination: auditS3Bucket, + deliveryStreamNameAuditDestination: auditDeliveryStreamName, + }); + + // WHEN + const logGroupName = 'test-log-group'; + new LogGroup(stack, 'LogGroup', { + logGroupName: logGroupName, + dataProtectionPolicy: dataProtectionPolicy, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Logs::LogGroup', { + LogGroupName: logGroupName, + DataProtectionPolicy: { + name: 'data-protection-policy-cdk', + description: 'cdk generated data protection policy', + version: '2021-06-01', + statement: [ + { + sid: 'audit-statement-cdk', + dataIdentifier: [ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':dataprotection::aws:data-identifier/EmailAddress', + ], + ], + }, + ], + operation: { + audit: { + findingsDestination: { + cloudWatchLogs: { + logGroup: { + Ref: "LogGroupAudit2C8B7F73" + } + }, + firehose: { + deliveryStream: auditDeliveryStreamName, + }, + s3: { + bucket: { + Ref: "BucketAudit1DED3529" + } + }, + }, + }, + }, + }, + { + sid: 'redact-statement-cdk', + dataIdentifier: [ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':dataprotection::aws:data-identifier/EmailAddress', + ], + ], + }, + ], + operation: { + deidentify: { + maskConfig: {}, + }, + }, + }, + ], + }, + }); + }); }); function dataDrivenTests(cases: string[], body: (suffix: string) => void): void {