aws-cdk icon indicating copy to clipboard operation
aws-cdk copied to clipboard

feat(sns): add sns data protection policy

Open ykethan opened this issue 6 months ago • 1 comments

Issue # (if applicable)

Closes #34136.

Reason for this change

AWS SNS now supports Message Data Protection policies, which allow users to define rules to detect and handle sensitive data in SNS messages. This feature enables users to implement security controls like auditing, masking, redacting, or blocking messages containing sensitive data.

Description of changes

This PR adds support for SNS Data Protection policies to the AWS CDK, including:

Added dataProtectionPolicy property to TopicProps

Adds Core Data Protection:

  • DataProtectionPolicy - The main construct for defining an SNS data protection policy
  • DataProtectionPolicyStatement - Represents a rule within the policy
  • DataDirection - Enum to specify inbound or outbound message flow
  • Operations classes (AuditOperation, MaskOperation, RedactOperation, DenyOperation)
  • Custom data identifier support via CustomDataIdentifier

Adds Managed Data Identifier collections:

  • PersonalIdentifiers - For detecting PII (names, addresses, SSNs, etc.)
  • FinancialIdentifiers - For detecting financial information (credit cards, bank accounts)
  • CredentialsIdentifiers - For detecting sensitive credentials (AWS keys, private keys)
  • DeviceIdentifiers - For detecting device-related information (IP addresses)
  • HealthIdentifiers - For detecting health-related information (PHI)

Updated README with examples

Describe any new or updated permissions being added

Description of how you validated changes

Created unit tests for all new components Created integration test Manually tested deployment using


    // Create a CloudWatch log group for SNS data protection findings
    const logGroup = new logs.LogGroup(this, 'DataProtectionLogGroup', {
      logGroupName: '/aws/vendedlogs/sns-data-protection',
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    // Create 10 custom data identifiers (maximum allowed per policy)
    const employeeIdPattern = new sns.CustomDataIdentifier({
      name: 'EmployeeID',
      regex: 'EMP-[0-9]{6}',
    });

    const projectCodePattern = new sns.CustomDataIdentifier({
      name: 'ProjectCode',
      regex: 'PRJ-[A-Z]{3}-[0-9]{4}',
    });

    const customerIdPattern = new sns.CustomDataIdentifier({
      name: 'CustomerID',
      regex: 'CUST-[0-9]{8}',
    });

    const orderIdPattern = new sns.CustomDataIdentifier({
      name: 'OrderID',
      regex: 'ORD-[0-9]{10}',
    });

    const caseIdPattern = new sns.CustomDataIdentifier({
      name: 'CaseID',
      regex: 'CASE-[0-9]{7}',
    });

    const accountIdPattern = new sns.CustomDataIdentifier({
      name: 'AccountID',
      regex: 'ACC-[0-9]{8}',
    });

    const productCodePattern = new sns.CustomDataIdentifier({
      name: 'ProductCode',
      regex: 'PROD-[A-Z]{2}-[0-9]{4}',
    });

    const contractIdPattern = new sns.CustomDataIdentifier({
      name: 'ContractID',
      regex: 'CNTR-[0-9]{6}-[A-Z]{2}',
    });

    const invoiceIdPattern = new sns.CustomDataIdentifier({
      name: 'InvoiceID',
      regex: 'INV-[0-9]{8}',
    });

    const ticketIdPattern = new sns.CustomDataIdentifier({
      name: 'TicketID',
      regex: 'TKT-[A-Z]{3}-[0-9]{6}',
    });

    // Create a comprehensive data protection policy
    const dataProtectionPolicy = new sns.DataProtectionPolicy({
      name: 'ComprehensiveProtectionPolicy',
      description:
        'Complete example of all SNS Message Data Protection capabilities',
      statements: [
        // Statement 1: Audit for all PII data types
        new sns.DataProtectionPolicyStatement({
          sid: 'AuditAllPII',
          dataDirection: sns.DataDirection.INBOUND,
          dataIdentifiers: [
            // Basic personal identifiers
            sns.PersonalIdentifiers.NAME,
            sns.PersonalIdentifiers.EMAIL_ADDRESS,
            sns.PersonalIdentifiers.VEHICLE_IDENTIFICATION_NUMBER,
            sns.PersonalIdentifiers.ADDRESS,

            // Drivers license across multiple regions
            sns.PersonalIdentifiers.driversLicense('US'),
            sns.PersonalIdentifiers.driversLicense('GB'),
            sns.PersonalIdentifiers.driversLicense('CA'),
            sns.PersonalIdentifiers.driversLicense('AU'),
            sns.PersonalIdentifiers.driversLicense('FR'),
            sns.PersonalIdentifiers.driversLicense('DE'),
            sns.PersonalIdentifiers.driversLicense('IT'),
            sns.PersonalIdentifiers.driversLicense('ES'),

            // Phone numbers across multiple regions
            sns.PersonalIdentifiers.phoneNumber('US'),
            sns.PersonalIdentifiers.phoneNumber('GB'),
            sns.PersonalIdentifiers.phoneNumber('FR'),
            sns.PersonalIdentifiers.phoneNumber('DE'),
            sns.PersonalIdentifiers.phoneNumber('IT'),
            sns.PersonalIdentifiers.phoneNumber('ES'),
            sns.PersonalIdentifiers.phoneNumber('BR'),

            // Passport numbers across multiple regions
            sns.PersonalIdentifiers.passportNumber('US'),
            sns.PersonalIdentifiers.passportNumber('GB'),
            sns.PersonalIdentifiers.passportNumber('CA'),
            sns.PersonalIdentifiers.passportNumber('DE'),
            sns.PersonalIdentifiers.passportNumber('FR'),
            sns.PersonalIdentifiers.passportNumber('IT'),
            sns.PersonalIdentifiers.passportNumber('ES'),

            // SSNs across regions
            sns.PersonalIdentifiers.ssn('US'),
            sns.PersonalIdentifiers.ssn('ES'),
          ],
          operation: new sns.AuditOperation({
            sampleRate: 99,
            findingsDestination: {
              cloudWatchLogs: {
                logGroup: logGroup.logGroupName,
              },
            },
          }),
        }),

        // Statement 2: Mask financial information
        new sns.DataProtectionPolicyStatement({
          sid: 'MaskFinancialData',
          dataDirection: sns.DataDirection.INBOUND,
          dataIdentifiers: [
            // Credit card related
            sns.FinancialIdentifiers.CREDIT_CARD_NUMBER,
            sns.FinancialIdentifiers.CREDIT_CARD_EXPIRATION,
            sns.FinancialIdentifiers.CREDIT_CARD_CVV,

            // Bank accounts across multiple regions
            sns.FinancialIdentifiers.bankAccountNumber('US'),
            sns.FinancialIdentifiers.bankAccountNumber('GB'),
            sns.FinancialIdentifiers.bankAccountNumber('DE'),
            sns.FinancialIdentifiers.bankAccountNumber('FR'),
            sns.FinancialIdentifiers.bankAccountNumber('IT'),
            sns.FinancialIdentifiers.bankAccountNumber('ES'),
          ],
          operation: new sns.MaskOperation({
            maskWithCharacter: '#',
          }),
        }),

        // Statement 3: Redact custom organization-specific patterns
        new sns.DataProtectionPolicyStatement({
          sid: 'RedactCustomIdentifiers',
          dataDirection: sns.DataDirection.INBOUND,
          dataIdentifiers: [
            employeeIdPattern,
            projectCodePattern,
            customerIdPattern,
            orderIdPattern,
            caseIdPattern,
          ],
          operation: new sns.RedactOperation(),
        }),

        // Statement 4: Additional redact statement for remaining custom identifiers
        new sns.DataProtectionPolicyStatement({
          sid: 'RedactMoreCustomIdentifiers',
          dataDirection: sns.DataDirection.INBOUND,
          dataIdentifiers: [
            accountIdPattern,
            productCodePattern,
            contractIdPattern,
            invoiceIdPattern,
            ticketIdPattern,
          ],
          operation: new sns.RedactOperation(),
        }),

        // Statement 5: Block messages containing credentials
        new sns.DataProtectionPolicyStatement({
          sid: 'BlockCredentials',
          dataDirection: sns.DataDirection.INBOUND,
          dataIdentifiers: [
            // All credential types
            sns.CredentialsIdentifiers.AWS_SECRET_KEY,
            sns.CredentialsIdentifiers.OPENSSH_PRIVATE_KEY,
            sns.CredentialsIdentifiers.PGP_PRIVATE_KEY,
            sns.CredentialsIdentifiers.PRIVATE_KEY,
            sns.CredentialsIdentifiers.PUTTY_PRIVATE_KEY,
          ],
          operation: new sns.DenyOperation(),
        }),

        // Statement 6: Block messages containing device information
        new sns.DataProtectionPolicyStatement({
          sid: 'BlockDeviceInfo',
          dataDirection: sns.DataDirection.INBOUND,
          dataIdentifiers: [sns.DeviceIdentifiers.IP_ADDRESS],
          operation: new sns.DenyOperation(),
        }),

        // Statement 7: Mask health information in outbound messages
        new sns.DataProtectionPolicyStatement({
          sid: 'MaskHealthData',
          dataDirection: sns.DataDirection.OUTBOUND,
          dataIdentifiers: [
            // US health identifiers
            sns.HealthIdentifiers.DRUG_ENFORCEMENT_AGENCY_NUMBER_US,
            sns.HealthIdentifiers.HEALTHCARE_PROCEDURE_CODE_US,
            sns.HealthIdentifiers.HEALTH_INSURANCE_CLAIM_NUMBER_US,
            sns.HealthIdentifiers.MEDICARE_BENEFICIARY_NUMBER_US,
            sns.HealthIdentifiers.NATIONAL_DRUG_CODE_US,
            sns.HealthIdentifiers.NATIONAL_PROVIDER_ID_US,

            // EU/FR health identifiers
            sns.HealthIdentifiers.HEALTH_INSURANCE_CARD_NUMBER_EU,
            sns.HealthIdentifiers.HEALTH_INSURANCE_NUMBER_FR,

            // GB health identifiers
            sns.HealthIdentifiers.NATIONAL_INSURANCE_NUMBER_GB,
            sns.HealthIdentifiers.NHS_NUMBER_GB,

            // CA health identifiers
            sns.HealthIdentifiers.PERSONAL_HEALTH_NUMBER_CA,
          ],
          operation: new sns.MaskOperation({
            maskWithCharacter: '*',
          }),
        }),
      ],
    });

    // Create an SNS topic with the comprehensive data protection policy
    const protectedTopic = new sns.Topic(this, 'ProtectedTopic', {
      topicName: 'protected-data-topic',
      dataProtectionPolicy: dataProtectionPolicy,
    });

    // Output the topic ARN
    new cdk.CfnOutput(this, 'ProtectedTopicArn', {
      value: protectedTopic.topicArn,
      description: 'ARN of the SNS topic with comprehensive data protection',
      exportName: 'ProtectedTopicArn',
    });

Checklist


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license

ykethan avatar Jun 05 '25 17:06 ykethan

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildv2Project1C6BFA3F-wQm2hXv2jqQv
  • Commit ID: 68c9086649cf8a1294fc14c4a40638cfc75c949f
  • Result: SUCCEEDED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

aws-cdk-automation avatar Jun 06 '25 18:06 aws-cdk-automation