AWSTemplateFormatVersion: '2010-09-09'
Description: 'Dynatrace Data Acquisition - AWS logs ingest via Firehose  (v1.0.7 2026-05-13 16:55)'

Metadata:
  Version:
    Number: v1.0.7
    ReleaseDate: 2026-05-13 16:55

Parameters:
  pDynatraceUrl:
    Type: String
    Description: 'Your Dynatrace environment url'
    AllowedPattern: '^https:\/\/.+[^\/]$'
  pDtIngestToken:
    Type: String
    Description: 'Dynatrace platform token for logs ingest'
    AllowedPattern: '(^dt0s16\.[A-Z0-9]{8}\.[A-Z0-9]{64}$)'
    NoEcho: true
  pUseCMK:
    Type: String
    Description: 'Create and use a Customer Managed Key (CMK) for encryption'
    AllowedValues: ['TRUE', 'FALSE']
    Default: 'FALSE'
  pStackSetExecutionRoleName:
    Type: String
    Description: 'Name of the StackSet execution role used to deploy this stack'
  pMonitoringConfigId:
    Type: String
    Description: 'ID of the Dynatrace AWS connection. Used only for visibility purposes.'
    Default: 'unknown'

Conditions:
  cUseCMK: !Equals [ !Ref pUseCMK, 'TRUE' ]
  cProductionEnvironment: !Equals [ !Select [ 2, !Split [ ".", !Select [ 1, !Split [ "://", !Ref pDynatraceUrl ] ] ] ], 'dynatrace' ]

Resources:
  DynatraceLogKMSKey:
    Type: AWS::KMS::Key
    Condition: cUseCMK
    Properties:
      Description: 'Dynatrace Data Acquisition logs encryption key'
      KeyPolicy:
        Version: '2012-10-17'
        Id: dynatrace-log-key-policy
        Statement:
          - Sid: Enable IAM User Permissions
            Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
            Action: 'kms:*'
            Resource: '*'
          - Sid: Allow StackSet Execution Role to use the key
            Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:role/${pStackSetExecutionRoleName}'
            Action:
              - 'kms:Encrypt'
              - 'kms:Decrypt'
              - 'kms:ReEncrypt*'
              - 'kms:GenerateDataKey*'
              - 'kms:CreateGrant'
              - 'kms:DescribeKey'
            Resource: '*'
          - Sid: Allow CloudWatch Logs use of the key
            Effect: Allow
            Principal:
              Service: !Sub 'logs.${AWS::Region}.amazonaws.com'
            Action:
              - 'kms:Encrypt'
              - 'kms:Decrypt'
              - 'kms:ReEncrypt*'
              - 'kms:GenerateDataKey*'
              - 'kms:CreateGrant'
              - 'kms:DescribeKey'
            Resource: '*'
            Condition:
              ArnLike:
                'kms:EncryptionContext:aws:logs:arn': !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/datafirehose/dynatrace/*'
              StringEquals:
                'aws:SourceAccount': !Ref AWS::AccountId
          - Sid: Allow KMS in AWS Services
            Effect: Allow
            Principal:
              Service:
                - 'firehose.amazonaws.com'
                - 's3.amazonaws.com'
                - 'secretsmanager.amazonaws.com'
            Action:
              - 'kms:Decrypt'
              - 'kms:GenerateDataKey'
              - 'kms:CreateGrant'
            Resource: '*'
            Condition:
              StringEquals:
                'aws:SourceAccount': !Ref AWS::AccountId
              StringEqualsIfExists:
                'kms:ViaService':
                  - !Sub 'firehose.${AWS::Region}.amazonaws.com'
                  - !Sub 's3.${AWS::Region}.amazonaws.com'
                  - !Sub 'secretsmanager.${AWS::Region}.amazonaws.com'
              ArnLikeIfExists:
                'kms:EncryptionContext:aws:s3:arn': !Sub 'arn:aws:s3:::dynatrace-logs-backup-*'
                'kms:EncryptionContext:SecretARN': !Sub 'arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:/dynatrace/*'
      Tags:
        - Key: 'dt:CreatedBy'
          Value: 'Dynatrace'
        - Key: 'dt:EnvironmentID'
          Value: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]

  DynatraceLogKMSAlias:
    Type: AWS::KMS::Alias
    Condition: cUseCMK
    Properties:
      TargetKeyId: !GetAtt DynatraceLogKMSKey.Arn
      AliasName: !Sub
        - 'alias/dynatrace/${DynatraceEnvironmentId}/keys/aws/ingest/${DeploymentHash}'
        - { DeploymentHash: !Select [ 0, !Split [ '-', !Select [ 2, !Split [ '/', !Ref AWS::StackId ] ] ] ], DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ] }

  DynatraceLogIngestTokenSecret:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: !Sub
        - '/dynatrace/${DynatraceEnvironmentId}/tokens/aws/ingest/${DeploymentHash}'
        - { DeploymentHash: !Select [ 0, !Split [ '-', !Select [ 2, !Split [ '/', !Ref AWS::StackId ] ] ] ], DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ] }
      Description: Dynatrace ingest platform token secret
      SecretString: !Sub '{"api_key": "${pDtIngestToken}"}'
      KmsKeyId: !If [ cUseCMK, !GetAtt DynatraceLogKMSKey.Arn, !Ref AWS::NoValue ]
      Tags:
        - Key: 'dt:CreatedBy'
          Value: 'Dynatrace'
        - Key: 'dt:EnvironmentID'
          Value: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]

  DynatraceLogSubscription:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub
        - 'DynatraceLogSubscription-${DynatraceEnvironmentId}-${AWS::Region}-${DeploymentHash}'
        - { DeploymentHash: !Select [ 0, !Split [ '-', !Select [ 2, !Split [ '/', !Ref AWS::StackId ] ] ] ], DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ] }
      Description: 'Role for Subscription Filters (to write to Firehose)'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: 'logs.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
      Policies:
        - PolicyName: root
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 'firehose:PutRecord'
                  - 'firehose:PutRecordBatch'
                Resource: !GetAtt DynatraceLogDeliveryStream.Arn
      Tags:
        - Key: 'dt:CreatedBy'
          Value: 'Dynatrace'
        - Key: 'dt:EnvironmentID'
          Value: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]

  DynatraceLogDeliveryStream:
    Type: AWS::KinesisFirehose::DeliveryStream
    Properties:
      DeliveryStreamName: !Sub
        - 'DynatraceLogDeliveryStream-${DynatraceEnvironmentId}-${AWS::Region}-${DeploymentHash}'
        - { DeploymentHash: !Select [ 0, !Split [ '-', !Select [ 2, !Split [ '/', !Ref AWS::StackId ] ] ] ], DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ] }
      DeliveryStreamType: 'DirectPut'
      DeliveryStreamEncryptionConfigurationInput: !If
        - cUseCMK
        - KeyType: CUSTOMER_MANAGED_CMK
          KeyARN: !GetAtt DynatraceLogKMSKey.Arn
        - !Ref AWS::NoValue
      HttpEndpointDestinationConfiguration:
        EndpointConfiguration:
          Url: !If
            - cProductionEnvironment
            - !Sub
              - 'https://${Subdomain}.da.dynatrace.com/api/aws/firehose/v1/logs'
              - Subdomain: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "://", !Ref pDynatraceUrl ] ] ] ]
            - !Sub
              - 'https://${Subdomain}.da.dynatracelabs.com/api/aws/firehose/v1/logs'
              - Subdomain: !Select [ 0, !Split [ ".apps", !Select [ 0, !Split [ ".dynatracelabs.com", !Select [ 1, !Split [ "://", !Ref pDynatraceUrl ] ] ] ] ] ]
          Name: Dynatrace delivery endpoint
        SecretsManagerConfiguration:
          Enabled: true
          SecretARN: !Ref DynatraceLogIngestTokenSecret
        RequestConfiguration:
          ContentEncoding: GZIP
        BufferingHints:
          IntervalInSeconds: 60
          SizeInMBs: 1
        RetryOptions:
          DurationInSeconds: 7200
        CloudWatchLoggingOptions:
          Enabled: true
          LogGroupName: !Ref DynatraceFirehoseLogGroup
          LogStreamName: !Ref DynatraceFirehoseLogStream
        RoleARN: !GetAtt DynatraceDeliveryStreamRole.Arn
        S3BackupMode: FailedDataOnly
        S3Configuration:
          BucketARN: !GetAtt DynatraceFirehoseBackupS3Bucket.Arn
          RoleARN: !GetAtt DynatraceDeliveryStreamRole.Arn
      Tags:
        - Key: 'dt:CreatedBy'
          Value: 'Dynatrace'
        - Key: 'dt:EnvironmentID'
          Value: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]

  DynatraceFirehoseLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub
        - '/aws/datafirehose/dynatrace/${DynatraceEnvironmentId}/${AWS::Region}/${DeploymentHash}'
        - { DeploymentHash: !Select [ 0, !Split [ '-', !Select [ 2, !Split [ '/', !Ref AWS::StackId ] ] ] ], DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ] }
      RetentionInDays: 14
      KmsKeyId: !If [ cUseCMK, !GetAtt DynatraceLogKMSKey.Arn, !Ref AWS::NoValue ]
      Tags:
        - Key: 'dt:CreatedBy'
          Value: 'Dynatrace'
        - Key: 'dt:EnvironmentID'
          Value: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]

  DynatraceFirehoseLogStream:
    Type: AWS::Logs::LogStream
    Properties:
      LogStreamName: !Sub
        - 'DynatraceFirehoseLogStream-${DynatraceEnvironmentId}-${AWS::Region}-${DeploymentHash}'
        - { DeploymentHash: !Select [ 0, !Split [ '-', !Select [ 2, !Split [ '/', !Ref AWS::StackId ] ] ] ], DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ] }
      LogGroupName: !Ref DynatraceFirehoseLogGroup

  DynatraceFirehoseBackupS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub
        - 'dynatrace-logs-backup-${DynatraceEnvironmentId}-${AWS::Region}-${DeploymentHash}'
        - { DeploymentHash: !Select [ 0, !Split [ '-', !Select [ 2, !Split [ '/', !Ref AWS::StackId ] ] ] ], DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ] }
      LifecycleConfiguration:
        Rules:
          - ExpirationInDays: 14
            Status: 'Enabled'
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: aws:kms
              KMSMasterKeyID: !If [ cUseCMK, !GetAtt DynatraceLogKMSKey.Arn, 'alias/aws/s3' ]
      Tags:
        - Key: 'dt:CreatedBy'
          Value: 'Dynatrace'
        - Key: 'dt:EnvironmentID'
          Value: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]

  DynatraceDeliveryStreamRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub
        - 'DynatraceDeliveryStreamRole-${DynatraceEnvironmentId}-${AWS::Region}-${DeploymentHash}'
        - { DeploymentHash: !Select [ 0, !Split [ '-', !Select [ 2, !Split [ '/', !Ref AWS::StackId ] ] ] ], DynatraceEnvironmentId: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ] }
      Description: 'Dynatrace Logs Firehose delivery stream role'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: ''
            Effect: Allow
            Principal:
              Service: 'firehose.amazonaws.com'
            Action: 'sts:AssumeRole'
      Policies:
        - PolicyName: FirehoseSecretAccessPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 'secretsmanager:GetSecretValue'
                Resource:
                  - !Ref DynatraceLogIngestTokenSecret
        - PolicyName: FirehoseLoggingPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 'logs:PutLogEvents'
                Resource:
                  - !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${DynatraceFirehoseLogGroup}:log-stream:${DynatraceFirehoseLogStream}'
        - PolicyName: FirehoseDeliveryPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 's3:AbortMultipartUpload'
                  - 's3:GetBucketLocation'
                  - 's3:GetObject'
                  - 's3:ListBucket'
                  - 's3:ListBucketMultipartUploads'
                  - 's3:PutObject'
                Resource:
                  - !GetAtt DynatraceFirehoseBackupS3Bucket.Arn
                  - !Join [ '/', [ !GetAtt DynatraceFirehoseBackupS3Bucket.Arn, '*' ] ]
        - !If
          - cUseCMK
          - PolicyName: KMSAccessPolicy
            PolicyDocument:
              Version: 2012-10-17
              Statement:
                - Effect: Allow
                  Action:
                    - 'kms:Decrypt'
                    - 'kms:GenerateDataKey'
                    - 'kms:CreateGrant'
                  Resource:
                    - !GetAtt DynatraceLogKMSKey.Arn
          - !Ref AWS::NoValue
      Tags:
        - Key: 'dt:CreatedBy'
          Value: 'Dynatrace'
        - Key: 'dt:EnvironmentID'
          Value: !Select [ 0, !Split [ ".", !Select [ 1, !Split [ "https://", !Ref pDynatraceUrl ] ] ] ]

Outputs:
  FirehoseDeliveryStreamArn:
    Value: !GetAtt DynatraceLogDeliveryStream.Arn
  CloudWatchLogSubscriptionRoleArn:
    Value: !GetAtt DynatraceLogSubscription.Arn
  DynatraceLogIngestTokenSecret:
    Value: !Ref DynatraceLogIngestTokenSecret
    Description: ARN of SecretsManager secret storing Dynatrace logs ingest platform token
  DynatraceLogKMSAliasName:
    Condition: cUseCMK
    Value: !Ref DynatraceLogKMSAlias
    Description: Name of the KMS alias for the Dynatrace log encryption key