AWSTemplateFormatVersion: "2010-09-09"
Description: "Kinesis Firehose stack for Redshift/Kinesis Demo."
Parameters:
  DeliveryStreamName:
    Type: String
    Description: The name of the Kinesis Firehose Delivery Stream.
    Default: "redshift-delivery-stream"
  SESConfigSetName:
    Type: String
    Default: "ses-to-firehose-config-set"
  ClusterName:
    Description: Name of the Redshift cluster
    Type: String
    Default: redshift-cluster
  DatabaseName:
    Description: The name of the first database to be created when the cluster is created.
    Type: String
    Default: dev
    AllowedPattern: '([a-z]|[0-9])+'
  NumberOfNodes:
    Description: The number of compute nodes in the cluster. For multi-node clusters, the NumberOfNodes parameter must be greater than 1.
    Type: Number
    Default: '2'
  NodeType:
    Description: The type of node to be provisioned
    Type: String
    Default: dc2.large
    AllowedValues:
      - ra3.xlplus
      - ra3.4xlarge
      - ra3.16xlarge
      - dc2.large
      - dc2.8xlarge
  MasterUsername:
    Description: The user name that is associated with the master user account for the cluster that is being created.
    Type: String
    Default: awsuser
    AllowedPattern: '([a-z])([a-z]|[0-9])*'
    ConstraintDescription: must start with a-z and contain only a-z or 0-9.
  Vpc:
    Description: "SSM parameter for vpc_id"
    Type: AWS::EC2::VPC::Id
  SubnetId:
    Description: Subnet ID where source redshift clusters and EC2 instances will be created
    Type: AWS::EC2::Subnet::Id
  OnPremisesCIDR:
    Description: IP range (CIDR notation) for your existing infrastructure to access the target and replica redshift clusters
    Type: String
    Default: 10.0.0.0/8
    MinLength: '9'
    MaxLength: '18'
    AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
    ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x.
  NotificationEmailId:
    Description: The email notification list that is used to configure an SNS topic for sending CloudWatch alarm and event notifications.
    Type: String
    Default: 'operations@company.com'
    AllowedPattern: '^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$'
    ConstraintDescription: provide a valid email address.

Conditions:
  RedshiftSingleNodeClusterCondition:
    Fn::Equals:
      - Ref: NumberOfNodes
      - '1'
Mappings:
  Redshift:
    Port:
      Number: 5439
    SnapshotRetention:
      Days: 10
    Accessible:
      Public: true
    Encrypted:
      Kms: true
    CloudWatch:
      MetricName: "KinesisRedshiftErrors"
      MetricNamespace: "FirehoseRedshiftMetrics"

  RegionMap:
    us-west-2:
      KinesisFirehoseCIDR: 52.89.255.224/27
    us-west-1:
      KinesisFirehoseCIDR: 13.57.135.192/27
    us-gov-west-1:
      KinesisFirehoseCIDR: 52.61.204.160/27
    us-gov-east-1:
      KinesisFirehoseCIDR: 18.253.138.96/27
    us-east-2:
      KinesisFirehoseCIDR: 13.58.135.96/27
    us-east-1:
      KinesisFirehoseCIDR: 52.70.63.192/27
    sa-east-1:
      KinesisFirehoseCIDR: 18.228.1.128/27
    me-south-1:
      KinesisFirehoseCIDR: 15.185.91.0/27
    eu-west-3:
      KinesisFirehoseCIDR: 35.180.1.96/27
    eu-west-2:
      KinesisFirehoseCIDR: 18.130.1.96/27
    eu-west-1:
      KinesisFirehoseCIDR: 52.19.239.192/27
    eu-south-1:
      KinesisFirehoseCIDR: 15.161.135.128/27
    eu-north-1:
      KinesisFirehoseCIDR: 13.53.63.224/27
    eu-central-1:
      KinesisFirehoseCIDR: 35.158.127.160/27
    cn-northwest-1:
      KinesisFirehoseCIDR: 161.189.23.64/27
    cn-north-1:
      KinesisFirehoseCIDR: 52.81.151.32/27
    ca-central-1:
      KinesisFirehoseCIDR: 35.183.92.128/27
    ap-southeast-2:
      KinesisFirehoseCIDR: 13.210.67.224/27
    ap-southeast-1:
      KinesisFirehoseCIDR: 13.228.64.192/27
    ap-south-1:
      KinesisFirehoseCIDR: 13.232.67.32/27
    ap-northeast-3:
      KinesisFirehoseCIDR: 13.208.177.192/27
    ap-northeast-2:
      KinesisFirehoseCIDR: 13.209.1.64/27
    ap-northeast-1:
      KinesisFirehoseCIDR: 13.113.196.224/27
    ap-east-1:
      KinesisFirehoseCIDR: 18.162.221.32/27
    af-south-1:
      KinesisFirehoseCIDR: 13.244.121.224/277

Resources:

  SNSTopicForNotifications:
    Type: AWS::SNS::Topic
    Properties:
      Subscription:
        - Endpoint: !Ref NotificationEmailId
          Protocol: email

  S3BucketFirehoseToRedshiftData:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256

  SesS3BucketAccessPolicy:
    Type: 'AWS::IAM::ManagedPolicy'
    Properties:
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: RedshiftWhatIfBucketAccess
            Effect: Allow
            Action:
              - s3:GetBucketLocation
              - s3:GetObject
              - s3:PutObject
              - s3:ListMultipartUploadParts
              - s3:ListBucket
              - s3:ListBucketMultipartUploads
            Resource:
              - !Sub "arn:aws:s3:::${S3BucketFirehoseToRedshiftData}"
              - !Sub "arn:aws:s3:::${S3BucketFirehoseToRedshiftData}/*"

  KMSAccessPolicy:
    Type: 'AWS::IAM::ManagedPolicy'
    Properties:
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: 'KMSAccess'
            Effect: 'Allow'
            Action:
              - 'kms:Encrypt'
              - 'kms:Decrypt'
              - 'kms:GenerateDataKey'
            Resource: !GetAtt CMKeyFirehoseS3.Arn


  IamRoleRedshiftCluster:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: 'Allow'
            Principal:
              Service:
                - 'redshift.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
      Path: '/'
      ManagedPolicyArns:
        - !Ref SesS3BucketAccessPolicy

  SecurityGroupRedshift:
    Type: 'AWS::EC2::SecurityGroup'
    Properties:
      GroupDescription: security group associated to Amazon Redshift
      VpcId: !Ref Vpc
      SecurityGroupIngress:
        - CidrIp: !Ref OnPremisesCIDR
          FromPort: !FindInMap [ Redshift, Port, Number]
          ToPort: !FindInMap [ Redshift, Port, Number]
          IpProtocol: tcp
          Description: 'Redshift Access to on prem users CIDR'
        - CidrIp: !FindInMap
            - RegionMap
            - !Ref AWS::Region
            - KinesisFirehoseCIDR
          FromPort: !FindInMap [ Redshift, Port, Number]
          ToPort: !FindInMap [ Redshift, Port, Number]
          IpProtocol: tcp
          Description: 'Redshift Access from Kinesis Firehose'

  SecurityGroupSelfReference:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      Description: Self Referencing Rule
      IpProtocol: -1
      FromPort: -1
      ToPort: -1
      GroupId: !GetAtt [SecurityGroupRedshift, GroupId]
      SourceSecurityGroupId: !GetAtt [SecurityGroupRedshift, GroupId]

  RedshiftClusterSubnetGroup:
    Type: 'AWS::Redshift::ClusterSubnetGroup'
    Properties:
      Description: Cluster subnet group
      SubnetIds:
        - !Ref SubnetId

  CMKeyRedshiftCluster:
    Type: AWS::KMS::Key
    Properties:
      Description: 'Customer managed key to be used for encryption at rest'
      Enabled: Yes
      EnableKeyRotation: Yes
      KeyPolicy:
        Version: 2012-10-17
        Id: key-default-1
        Statement:
          - Sid: Enable KMS Permissions for root account user
            Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
            Action: 'kms:*'
            Resource: '*'
          - Sid: Enable IAM User Permissions
            Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
            Action: 'kms:*'
            Resource: '*'
          - Sid: 'Allow access through RedShift for all principals in the account that are authorized to use RedShift'
            Effect: 'Allow'
            Principal:
              AWS: '*'
            Action:
              - 'kms:Encrypt'
              - 'kms:Decrypt'
              - 'kms:ReEncrypt*'
              - 'kms:GenerateDataKey*'
              - 'kms:CreateGrant'
              - 'kms:ListGrants'
              - 'kms:DescribeKey'
            Resource: '*'
            Condition:
              StringEquals:
                'kms:CallerAccount': !Sub '${AWS::AccountId}'
                'kms:ViaService': !Sub 'redshift.${AWS::Region}.amazonaws.com'


  CMKeyFirehoseS3:
    Type: AWS::KMS::Key
    Properties:
      Description: 'Customer managed key to be used for S3 encryption'
      Enabled: Yes
      EnableKeyRotation: Yes
      KeyPolicy:
        Version: 2012-10-17
        Id: key-default-2
        Statement:
          - Sid: Enable KMS Permissions for root account user
            Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
            Action: 'kms:*'
            Resource: '*'
          - Sid: Enable IAM User Permissions
            Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
            Action: 'kms:*'
            Resource: '*'
          - Sid: 'Allow access'
            Effect: 'Allow'
            Principal:
              AWS: '*'
            Action:
              - 'kms:Encrypt'
              - 'kms:Decrypt'
              - 'kms:ReEncrypt*'
              - 'kms:GenerateDataKey*'
              - 'kms:CreateGrant'
              - 'kms:ListGrants'
              - 'kms:DescribeKey'
            Resource: '*'
            Condition:
              StringEquals:
                'kms:CallerAccount': !Sub '${AWS::AccountId}'
                'kms:ViaService': !Sub 's3.${AWS::Region}.amazonaws.com'

  SecretRedshiftMasterUser:
    Type: "AWS::SecretsManager::Secret"
    Properties:
      Description: "Secrets Manager to store Redshift master user credentials"
      GenerateSecretString:
        SecretStringTemplate: !Sub
        - '{"username": "${MasterUsername}"}'
        - {MasterUsername: !Ref MasterUsername}
        GenerateStringKey: "password"
        PasswordLength: 32
        ExcludePunctuation: true

  RedshiftCluster:
    Type: 'AWS::Redshift::Cluster'
    DeletionPolicy: 'Delete'
    UpdateReplacePolicy: 'Delete'
    Properties:
      ClusterType:
        !If [RedshiftSingleNodeClusterCondition, 'single-node', 'multi-node']
      ClusterIdentifier: !Ref ClusterName
      NumberOfNodes:
        !If [
          RedshiftSingleNodeClusterCondition,
          !Ref 'AWS::NoValue',
          !Ref NumberOfNodes,
        ]
      NodeType: !Ref NodeType
      DBName: !Ref DatabaseName
      KmsKeyId: !Ref CMKeyRedshiftCluster
      Encrypted: !FindInMap [ Redshift, Encrypted, Kms]
      Port: !FindInMap [ Redshift, Port, Number]
      MasterUsername: !Join ['', ['{{resolve:secretsmanager:', !Ref SecretRedshiftMasterUser, ':SecretString:username}}' ]]
      MasterUserPassword: !Join ['', ['{{resolve:secretsmanager:', !Ref SecretRedshiftMasterUser, ':SecretString:password}}' ]]
      VpcSecurityGroupIds:
        - !Ref SecurityGroupRedshift
      AutomatedSnapshotRetentionPeriod: !FindInMap [ Redshift, SnapshotRetention, Days]
      PubliclyAccessible: !FindInMap [ Redshift, Accessible, Public]
      ClusterSubnetGroupName: !Ref RedshiftClusterSubnetGroup
      IamRoles:
        - !GetAtt IamRoleRedshiftCluster.Arn

  SecretAttachmentRedshiftMasterUser:
    Type: "AWS::SecretsManager::SecretTargetAttachment"
    Properties:
      SecretId: !Ref SecretRedshiftMasterUser
      TargetId: !Ref RedshiftCluster
      TargetType: AWS::Redshift::Cluster


  SESConfigSet:
    Type: 'AWS::SES::ConfigurationSet'
    Properties:
      Name: !Ref SESConfigSetName


  TransactionalConfigurationSetEventDestination:
    DependsOn:
      - SESEventDestinationPolicy
    Type: AWS::SES::ConfigurationSetEventDestination
    Properties:
      ConfigurationSetName: !Ref SESConfigSet
      EventDestination:
        Name: Metrics
        Enabled: true
        KinesisFirehoseDestination:
          IAMRoleARN: !GetAtt SESEventDestinationRole.Arn
          DeliveryStreamARN: !GetAtt DeliveryStream.Arn
        MatchingEventTypes:
          - send
          - reject
          - bounce
          - complaint
          - delivery
          - open
          - click

  SESEventDestinationRole:
    Type: AWS::IAM::Role
    Properties:
      Path: "/"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service: !Sub "ses.${AWS::URLSuffix}"
            Action: sts:AssumeRole
            Condition:
              StringEquals:
                sts:ExternalId: !Ref AWS::AccountId

  SESEventDestinationPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: LambdaPermissions
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
              - firehose:PutRecordBatch
            Resource:
              - !GetAtt DeliveryStream.Arn
      Roles:
        - !Ref SESEventDestinationRole

  DeliveryStream:
    Type: AWS::KinesisFirehose::DeliveryStream
    Properties:
      DeliveryStreamEncryptionConfigurationInput:
        KeyARN: !GetAtt CMKeyFirehoseS3.Arn
        KeyType: CUSTOMER_MANAGED_CMK
      DeliveryStreamName: !Ref DeliveryStreamName
      RedshiftDestinationConfiguration:
        CloudWatchLoggingOptions:
          Enabled: true
          LogGroupName: !Ref KinesisLogGroup
          LogStreamName: !Ref RedshiftLogStream
        ClusterJDBCURL:
          !Sub "jdbc:redshift://${RedshiftCluster.Endpoint.Address}:${RedshiftCluster.Endpoint.Port}/${DatabaseName}"
        CopyCommand:
          CopyOptions: !Sub "json 's3://${S3BucketFirehoseToRedshiftData}/config/jsonpaths.json' region '${AWS::Region}' GZIP"
          DataTableName: "ses"
        Password: !Join ['', ['{{resolve:secretsmanager:', !Ref SecretRedshiftMasterUser, ':SecretString:password}}' ]]
        RoleARN: !GetAtt FirehoseDeliveryRole.Arn
        S3Configuration:
          BucketARN: !GetAtt S3BucketFirehoseToRedshiftData.Arn
          CompressionFormat: "GZIP"
          BufferingHints:
            IntervalInSeconds: 60
            SizeInMBs: 1
          CloudWatchLoggingOptions:
            Enabled: true
            LogGroupName: !Ref KinesisLogGroup
            LogStreamName: !Ref S3LogStream
          Prefix: "ses_message/"
          RoleARN: !GetAtt FirehoseDeliveryRole.Arn
        Username: !Join ['', ['{{resolve:secretsmanager:', !Ref SecretRedshiftMasterUser, ':SecretString:username}}' ]]

  FirehoseDeliveryRole:
    Type: AWS::IAM::Role
    Properties:
      Path: "/"
      ManagedPolicyArns:
            - !Ref SesS3BucketAccessPolicy
            - !Ref KMSAccessPolicy
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: ""
            Effect: Allow
            Principal:
              Service: firehose.amazonaws.com
            Action: "sts:AssumeRole"
            Condition:
              StringEquals:
                "sts:ExternalId": !Ref "AWS::AccountId"

  KinesisDeliveryPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: FirehoseDeliveryPolicy
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
              - 'kinesis:DescribeStream'
              - 'kinesis:GetShardIterator'
              - 'kinesis:GetRecords'
              - 'kinesis:ListShards'
            Resource: !GetAtt DeliveryStream.Arn
          - Effect: Allow
            Action:
              - 'logs:PutLogEvents'
            Resource: !GetAtt 'KinesisLogGroup.Arn'
      Roles:
        - !Ref FirehoseDeliveryRole

  KinesisLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub "/aws/kinesisfirehose/${DeliveryStreamName}"
      RetentionInDays: 7
  RedshiftLogStream:
    Type: AWS::Logs::LogStream
    Properties:
      LogGroupName: !Ref KinesisLogGroup
      LogStreamName: "RedshiftDelivery"
  S3LogStream:
    Type: AWS::Logs::LogStream
    Properties:
      LogGroupName: !Ref KinesisLogGroup
      LogStreamName: "S3Delivery"
  KinesisRedshiftErrorsMetricFilter:
    Type: AWS::Logs::MetricFilter
    Properties:
      LogGroupName:
        Ref: KinesisLogGroup
      FilterPattern: "[w1!=errorCode&&w1!=Redshift]"
      MetricTransformations:
        -
          MetricValue: "1"
          MetricNamespace: !FindInMap [ Redshift, CloudWatch, MetricNamespace]
          MetricName: !FindInMap [ Redshift, CloudWatch, MetricName]

  KinesisRedshiftErrorsAlarm:
    Type: 'AWS::CloudWatch::Alarm'
    Properties:
      AlarmActions:
        - !Ref SNSTopicForNotifications
      AlarmDescription: !Sub "Error in data delivery from Firehose stream ${DeliveryStreamName} to RedShift cluster ${ClusterName} "
      MetricName: !FindInMap [ Redshift, CloudWatch, MetricName]
      Namespace: !FindInMap [ Redshift, CloudWatch, MetricNamespace]
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: 1
      Period: 10
      Statistic: Sum
      Threshold: 1

  LambdaInitializationStepsIAMRole:
    Type: AWS::IAM::Role
    Properties:
      Description : LambdaInitializationStepsIAMRole
      AssumeRolePolicyDocument:
          Version: 2012-10-17
          Statement:
            -
              Effect: Allow
              Principal:
                Service:
                  - lambda.amazonaws.com
              Action:
                - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
            - !Ref SesS3BucketAccessPolicy
      Policies:
          - PolicyName: LogingRedshiftDataPolicy
            PolicyDocument :
              Version: 2012-10-17
              Statement:
                -
                  Effect: "Allow"
                  Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                  - redshift-data:ExecuteStatement
                  - redshift-data:ListStatements
                  - redshift-data:GetStatementResult
                  - redshift-data:DescribeStatement
                  Resource: "*"
          - PolicyName: RedshiftAccessPolicy
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: Allow
                  Action:
                    - redshift:describeClusters
                    - redshift:GetClusterCredentials
                  Resource:
                    - !Sub "arn:aws:redshift:${AWS::Region}:${AWS::AccountId}:cluster:${RedshiftCluster}"
                    - !Sub "arn:aws:redshift:${AWS::Region}:${AWS::AccountId}:dbname:${RedshiftCluster}/${DatabaseName}"
                    - !Sub "arn:aws:redshift:${AWS::Region}:${AWS::AccountId}:dbuser:${RedshiftCluster}/${MasterUsername}"


  LambdaInitializationSteps:
    Type: AWS::Lambda::Function
    Properties:
      Description: lambda to add iam role with access on simple replay S3 bucket to source redshift cluster
      Handler: index.handler
      Runtime: python3.8
      Role: !GetAtt 'LambdaInitializationStepsIAMRole.Arn'
      Timeout: 900
      Environment:
        Variables:
          S3_BUCKET: !Ref S3BucketFirehoseToRedshiftData
          CLUSTER_IDENTIFIER: !Ref RedshiftCluster
          DB: !Ref DatabaseName
          USER: !Ref MasterUsername
          JSON_PATH_SUFFIX: 'config/jsonpaths.json'

      Code:
        ZipFile: |
          import boto3
          import time
          import traceback
          import botocore.exceptions as be
          import os
          import cfnresponse

          def handler(event, context):
              print(event)
              if event['RequestType'] != 'Delete':
                  try:

                      s3_bucket = os.environ['S3_BUCKET']
                      cluster_identifier = os.environ['CLUSTER_IDENTIFIER']
                      db = os.environ['DB']
                      user = os.environ['USER']
                      json_path_suffix = os.environ['JSON_PATH_SUFFIX']

                      sql_script = 'create table if not exists ses (message_id varchar(200) not null,event_type varchar(20) not null,sending_account_id char(12),timestamp varchar(50),destination text,json_data super);'
                      jsonpaths_script = '{"jsonpaths": ["$.mail.messageId","$.eventType","$.mail.sendingAccountId","$.mail.timestamp","$.mail.destination","$"]}'

                      for _ in range(900):
                          time.sleep(1)
                          if cluster_status(cluster_identifier) == 'availableAvailable':
                              sql_id = run_sql(cluster_identifier, db, user, sql_script)
                              s3_put(s3_bucket, json_path_suffix, jsonpaths_script)
                              break
                  except:
                      print(traceback.format_exc())
                      cfnresponse.send(event, context, cfnresponse.FAILED, input)
                      raise
              cfnresponse.send(event, context, cfnresponse.SUCCESS, {})


          def cluster_status(clusterid):
              try:
                  desc = boto3.client("redshift").describe_clusters(ClusterIdentifier=clusterid)['Clusters'][0]
                  if isinstance(desc, dict):
                      status = desc.get('ClusterStatus') + desc.get('ClusterAvailabilityStatus')
                  else:
                      status = 'Unavailable'
              except be.ClientError as e:
                  msg = e.response['Error']['Code']
                  if msg == 'ClusterNotFound':
                      status = 'nonExistent'
                  else:
                      print(desc)
                      raise
              return status


          def run_sql(clusterid, db, user, script):
              client = boto3.client("redshift-data")
              res = client.execute_statement(Database=db, DbUser=user, Sql=script,
                                             ClusterIdentifier=clusterid)
              query_id = res["Id"]
              done = False
              while not done:
                  time.sleep(1)
                  status_description = client.describe_statement(Id=query_id)
                  status = status_description["Status"]
                  if status == "FAILED":
                      raise Exception('SQL query failed:' + query_id + ": " + status_description["Error"])
                  elif status == "FINISHED":
                      return query_id


          def s3_put(bucket, key, object):
              s3 = boto3.client('s3')
              if object != None:
                  s3.put_object(Bucket=bucket, Key=key, Body=object)
              else:
                  s3.put_object(Bucket=bucket, Key=key)


  InitializationSteps:
    Type: Custom::LambdaInitializationSteps
    Properties:
      ServiceToken: !GetAtt [LambdaInitializationSteps, Arn]

Outputs:
  DeliveryStreamName:
    Description: Name of the Kinesis Firehose delivery stream
    Value:
      Ref: DeliveryStream
