text: # Centralized Logging Solution
  #
  # template for centralized-logging-solution
  # this template will deploy sample log sources
  #
  # author: aws-solutions-builder@
  AWSTemplateFormatVersion: 2010-09-09
  
  Description: (SO0009d) - AWS Centralized Logging Solution, sample sources template (Version %%VERSION%%)
  
  Parameters:
    # Log Streamer Function Arn
    LogStreamerArn:
      Description: Lambda Arn for Log Streamer function from primer template
      Type: String
  
    # VPC CIDR for proxy servers
    DemoVPCCidr:
      Description: CIDR block for VPC
      Type: String
      MinLength: 9
      MaxLength: 18
      AllowedPattern: '(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})'
      Default: 10.250.0.0/16
      ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x
  
    # Subnet for proxy web server
    DemoSubnet:
      Description: IP address range for subnet
      Type: String
      MinLength: 9
      MaxLength: 18
      AllowedPattern: '(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})'
      Default: 10.250.250.0/24
      ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x
  
    LatestAMIId:
      Description: >-
        Automatically selects the latest Amazon Linux AMI. Do not change this
        value
      Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
      Default: /aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-gp2
  
  Metadata:
    AWS::CloudFormation::Interface:
      ParameterGroups:
      - Label:
          default: Network Configuration
        Parameters:
        - DemoVPCCidr
        - DemoSubnet
      ParameterLabels:
        DemoVPCCidr:
          default: VPC for Sample Log Sources
        DemoSubnet:
          default: Subnet for Sample Web Server
        LogStreamerArn:
          default: Lambda Arn for Log Streaming
  
  Mappings:
  
    # Instance type for demo box
    EC2: 
      Instance: 
        Type: 't3.micro' 
  
    # CloudWatch logs pattern mapping
    FilterPatternLookup:
      Common:
        Pattern: '[host, ident, authuser, date, request, status, bytes, referrer, agent]'
      CloudTrail:
        Pattern: ''
      FlowLogs:
        Pattern: '[version, account_id, interface_id, srcaddr != "-", dstaddr != "-", srcport != "-", dstport != "-", protocol, packets, bytes, start, end, action, log_status]'
      Lambda:
        Pattern: '[timestamp=*Z, request_id="*-*", event]'
      SpaceDelimited:
        Pattern: '[]'
      Other:
        Pattern: ''
  
  Resources:
    #
    # Demo VPC resources
    # [DemoVPC, PublicSubnet, InternetGateway, GatewayAttachment, PublicRtb, PublicRoute, SubnetRtbAssoc]
    #
    DemoVPC:
      Type: AWS::EC2::VPC
      Properties:
        CidrBlock: !Sub ${DemoVPCCidr}
        Tags:
        - Key: Name
          Value: centralized-logging-demo VPC
  
    PublicSubnet:
      Type: AWS::EC2::Subnet
      Properties:
        VpcId: !Sub ${DemoVPC}
        AvailabilityZone: !Select [ 0, !GetAZs '' ]
        MapPublicIpOnLaunch: true
        CidrBlock: !Sub ${DemoSubnet}
        Tags:
        - Key: Name
          Value: centralized-logging-demo subnet
      Metadata:
        cfn_nag:
          rules_to_suppress:
            - id: W33
              reason: "This is where the test webserver is deployed for for a static page to demo the functionality of the solution by generating log for demo purposes. Hence requires a public IP"
  
    InternetGateway:
      Type: AWS::EC2::InternetGateway
      Properties: {}
  
    GatewayAttachment:
      Type: AWS::EC2::VPCGatewayAttachment
      Properties:
        VpcId: !Sub ${DemoVPC}
        InternetGatewayId: !Sub ${InternetGateway}
  
    PublicRtb:
      Type: AWS::EC2::RouteTable
      Properties:
        VpcId: !Sub ${DemoVPC}
  
    PublicRoute:
      Type: AWS::EC2::Route
      DependsOn: GatewayAttachment
      Properties:
        RouteTableId: !Sub ${PublicRtb}
        DestinationCidrBlock: 0.0.0.0/0
        GatewayId: !Sub ${InternetGateway}
  
    SubnetRtbAssoc:
      Type: AWS::EC2::SubnetRouteTableAssociation
      Properties:
        SubnetId: !Sub ${PublicSubnet}
        RouteTableId: !Sub ${PublicRtb}
  
  
    #
    # VPC flow log resources
    # [VPCFlowLogGroup, FlowlogsRole, VPCFlowLog, VPCFlowLogtoLambda]
    #
    VPCFlowLogGroup:
      Type: AWS::Logs::LogGroup
      Properties:
        RetentionInDays: 1
  
    FlowlogsRole:
      Type: AWS::IAM::Role 
      Properties:
        AssumeRolePolicyDocument:
          Version: 2012-10-17
          Statement:
          - Effect: Allow
            Principal:
              Service:
              - vpc-flow-logs.amazonaws.com
            Action:
            - sts:AssumeRole
        Path: /
        Policies:
        - PolicyName: LogRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
            - Effect: Allow
              Action:
              - logs:CreateLogGroup
              - logs:CreateLogStream
              - logs:DescribeLogGroups
              - logs:DescribeLogStreams
              - logs:PutLogEvents
              Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*
      Metadata:
        cfn_nag:
          rules_to_suppress:
            - id: W11
              reason: "Push logs from various resources to cloudwatch logs. Hence open"
  
    VPCFlowLog:
      Type: AWS::EC2::FlowLog
      Properties:
        DeliverLogsPermissionArn: !Sub ${FlowlogsRole.Arn}
        LogGroupName: !Sub ${VPCFlowLogGroup}
        ResourceId: !Sub ${DemoVPC}
        ResourceType: VPC
        TrafficType: ALL
  
    VPCFlowLogtoLambda:
      Type: AWS::Logs::SubscriptionFilter
      Properties:
        DestinationArn: !Sub ${LogStreamerArn}
        FilterPattern: !FindInMap [FilterPatternLookup, FlowLogs, Pattern]
        LogGroupName: !Sub ${VPCFlowLogGroup}
  
    S3LoggingBucket:
      DeletionPolicy: Retain
      Type: AWS::S3::Bucket
      Properties:
        BucketName: !Sub aws-trailbucket-s3-access-logs-${AWS::AccountId}-${AWS::Region}
        AccessControl: LogDeliveryWrite
        VersioningConfiguration:
          Status: Enabled
        BucketEncryption:
          ServerSideEncryptionConfiguration:
            - ServerSideEncryptionByDefault:
                SSEAlgorithm: AES256
        Tags:
          - Key: Name
            Value: 'AWS Centralized Logging Demo'
        PublicAccessBlockConfiguration:
          BlockPublicAcls: True
          BlockPublicPolicy: True
          IgnorePublicAcls: True
          RestrictPublicBuckets: True
      Metadata:
        cfn_nag:
          rules_to_suppress:
            - id: W35
              reason: "This S3 bucket is used as the destination for storing access logs"
            - id: W51
              reason: "Log delivery controlled by ACL, not bucket policy"
  
    #
    # CloudTrail resources
    # [TrailBucket, TrailBucketPolicy, TrailLogGroup, TrailLogGroupRole, TrailLogtoLambda]
    #
    TrailBucket:
      DeletionPolicy: Retain
      Type: AWS::S3::Bucket
      Properties:
        LoggingConfiguration:
          DestinationBucketName: !Ref S3LoggingBucket
          LogFilePrefix: access-logs
        BucketEncryption:
          ServerSideEncryptionConfiguration:
            - ServerSideEncryptionByDefault:
                SSEAlgorithm: AES256
        Tags:
          - Key: Name
            Value: 'AWS Centralized Logging Demo'
        PublicAccessBlockConfiguration:
          BlockPublicAcls: True
          BlockPublicPolicy: True
          IgnorePublicAcls: True
          RestrictPublicBuckets: True
  
    TrailBucketPolicy:
      Type: AWS::S3::BucketPolicy
      Properties:
        Bucket: !Ref TrailBucket
        PolicyDocument:
          Version: 2012-10-17
          Statement:
          - Effect: Allow
            Principal:
              Service: cloudtrail.amazonaws.com
            Action: s3:GetBucketAcl
            Resource: !Sub arn:aws:s3:::${TrailBucket}
          - Effect: Allow
            Principal:
              Service: cloudtrail.amazonaws.com
            Action: s3:PutObject
            Resource: !Sub arn:aws:s3:::${TrailBucket}/AWSLogs/${AWS::AccountId}/*
            Condition:
              StringEquals:
                s3:x-amz-acl: bucket-owner-full-control
  
    TrailLogGroup:
      Type: AWS::Logs::LogGroup
      Properties:
        RetentionInDays: 1
  
    TrailLogGroupRole:
      Type: AWS::IAM::Role
      Properties:
        AssumeRolePolicyDocument:
          Version: 2012-10-17
          Statement:
          - Effect: Allow
            Principal:
              Service: cloudtrail.amazonaws.com
            Action: sts:AssumeRole
        Policies:
        - PolicyName: cloudtrail-policy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
            - Effect: Allow
              Action:
              - logs:CreateLogStream
              - logs:PutLogEvents
              Resource: !Sub ${TrailLogGroup.Arn}
  
    Trail:
      Type: AWS::CloudTrail::Trail
      Properties:
        IncludeGlobalServiceEvents: true
        IsLogging: true
        IsMultiRegionTrail: false
        S3BucketName: !Sub ${TrailBucket}
        CloudWatchLogsLogGroupArn: !Sub ${TrailLogGroup.Arn}
        CloudWatchLogsRoleArn: !Sub ${TrailLogGroupRole.Arn}
      DependsOn: TrailBucketPolicy
  
    TrailLogtoLambda:
      Type: AWS::Logs::SubscriptionFilter
      Properties:
        DestinationArn: !Sub ${LogStreamerArn}
        FilterPattern: !FindInMap
          - FilterPatternLookup
          - CloudTrail
          - Pattern
        LogGroupName: !Sub ${TrailLogGroup}
  
    #
    # WebServer log resources
    # [EC2LogRole, InstanceProfile, WebServerSecurityGroup, WebServerSecurityGroupIngress, WebServerLogtoLambda]
    #
    EC2LogRole:
      Type: AWS::IAM::Role  
      Properties:
        AssumeRolePolicyDocument:
          Version: 2012-10-17
          Statement:
          - Effect: Allow
            Principal:
              Service:
              - ec2.amazonaws.com
            Action:
            - sts:AssumeRole
        Path: /
        Policies:
        - PolicyName: LogRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                - logs:Create*
                - logs:PutLogEvents
                - s3:GetObject
                Resource:
                - !Sub ${WebServerLogGroup.Arn}
                - !GetAtt TrailBucket.Arn
  
    InstanceProfile:
      Type: AWS::IAM::InstanceProfile
      Properties:
        Path: /
        Roles:
        - !Sub ${EC2LogRole}
  
    WebServerHost:
      Type: AWS::EC2::Instance
      Metadata:
        Comment: Install a simple PHP application
        AWS::CloudFormation::Init:
          config:
            packages:
              yum:
                httpd: []
                php: []
            files:
              /tmp/cwlogs/apacheaccess.conf:
                content: !Join
                  - ''
                  - - |
                      [general]
                    - |
                      state_file= /var/awslogs/agent-state
                    - |
                      [/var/log/httpd/access_log]
                    - |
                      file = /var/log/httpd/access_log
                    - 'log_group_name = '
                    - !Ref WebServerLogGroup
                    - |+
  
                    - |
                      log_stream_name = {instance_id}/apache.log
                    - 'datetime_format = %d/%b/%Y:%H:%M:%S'
                mode: '000400'
                owner: apache
                group: apache
              /var/www/html/index.php:
                content: !Join
                  - ''
                  - - |
                      <?php
                    - |
                      echo '<h1>AWS CloudFormation sample PHP application</h1>';
                    - |
                      ?>
                mode: '000644'
                owner: apache
                group: apache
              /etc/cfn/cfn-hup.conf:
                content: !Join
                  - ''
                  - - |
                      [main]
                    - stack=
                    - !Ref 'AWS::StackId'
                    - |+
  
                    - region=
                    - !Ref 'AWS::Region'
                    - |+
  
                mode: '000400'
                owner: root
                group: root
              /etc/cfn/hooks.d/cfn-auto-reloader.conf:
                content: !Join
                  - ''
                  - - |
                      [cfn-auto-reloader-hook]
                    - |
                      triggers=post.update
                    - >
                      path=Resources.WebServerHost.Metadata.AWS::CloudFormation::Init
                    - 'action=/opt/aws/bin/cfn-init -s '
                    - !Ref 'AWS::StackId'
                    - ' -r WebServerHost '
                    - ' --region     '
                    - !Ref 'AWS::Region'
                    - |+
  
                    - |
                      runas=root
            services:
              sysvinit:
                httpd:
                  enabled: 'true'
                  ensureRunning: 'true'
                sendmail:
                  enabled: 'false'
                  ensureRunning: 'false'
      CreationPolicy:
        ResourceSignal:
          Timeout: PT15M
      Properties:
        ImageId: !Ref LatestAMIId
        Tags:
        - Key: Name
          Value: Web Server centralized-logging-demo
        NetworkInterfaces:
        - GroupSet:
          - !Sub ${WebServerSecurityGroup}
          AssociatePublicIpAddress: true
          DeviceIndex: 0
          DeleteOnTermination: true
          SubnetId: !Sub ${PublicSubnet}
        InstanceType: !FindInMap [EC2, Instance, Type]
        IamInstanceProfile: !Sub ${InstanceProfile}
        UserData: !Base64
          'Fn::Join':
            - ''
            - - |
                #!/bin/bash -xe
              - |
                # Get the latest CloudFormation package
              - |
                yum update -y aws-cfn-bootstrap
              - |
                # Start cfn-init
              - '/opt/aws/bin/cfn-init -s '
              - !Ref 'AWS::StackId'
              - ' -r WebServerHost '
              - ' --region '
              - !Ref 'AWS::Region'
              - |2
                 || error_exit 'Failed to run cfn-init'
              - >
                # Start up the cfn-hup daemon to listen for changes to the EC2
                instance metadata
              - |
                /opt/aws/bin/cfn-hup || error_exit 'Failed to start cfn-hup'
              - |
                # Get the CloudWatch Logs agent
              - >
                wget
                https://s3.amazonaws.com/aws-cloudwatch/downloads/latest/awslogs-agent-setup.py
              - |
                # Install the CloudWatch Logs agent
              - 'python awslogs-agent-setup.py -n -r '
              - !Ref 'AWS::Region'
              - |2
                 -c /tmp/cwlogs/apacheaccess.conf || error_exit 'Failed to run CloudWatch Logs agent setup'
              - |
                # pre-warm the apache logs
              - |
                curl 127.0.0.1
              - |
                curl 127.0.0.1/404
              - |
                # All done so signal success
              - '/opt/aws/bin/cfn-signal -e $? '
              - '         --stack '
              - !Ref 'AWS::StackName'
              - '         --resource WebServerHost '
              - '         --region '
              - !Ref 'AWS::Region'
              - |+
  
    WebServerLogGroup:
      Type: AWS::Logs::LogGroup
      Properties:
        RetentionInDays: 1
  
    WebServerSecurityGroup:
      Type: AWS::EC2::SecurityGroup
      ##
      # V56617021 - 10/08/2018 - Suppress cfn_nag
      Metadata:
        cfn_nag:
          rules_to_suppress:
            - id: F1000
              reason: Override the SecurityGroup, egress rule defined as well
      Properties:
        GroupDescription: Enable HTTP access via port 80
        VpcId: !Sub ${DemoVPC}
  
    WebServerSecurityGroupIngress:
      Type: AWS::EC2::SecurityGroupIngress
      Properties:
        Description: Allow inbound access to the webserver from the internet
        GroupId: !Sub ${WebServerSecurityGroup}
        IpProtocol: tcp
        FromPort: 80
        ToPort: 80
        CidrIp: 0.0.0.0/0
      Metadata:
        cfn_nag:
          rules_to_suppress:
            - id: W9
              reason: "The server is a public webserver and hence CIDR mask is open"
            - id: W2
              reason: "To demo the functionality of this solution, there is public facing webserver with which the SecurityGroup will be associated. This webserver instance will be used only as a mechanism to generate logs. Hence no ELB has been added to the infrastructure"
  
    WebServerSecurityGroupEgress:
      Type: AWS::EC2::SecurityGroupEgress
      Properties:
        Description: Allow outbound access to the internet for all requests coming in
        CidrIp: 0.0.0.0/0
        FromPort: 0
        GroupId: !Sub ${WebServerSecurityGroup}
        IpProtocol: tcp
        ToPort: 65535
      Metadata:
        cfn_nag:
          rules_to_suppress:
            - id: W5
              reason: "The webserver is a public access server and hence CIDR range is open"
            - id: W29
              reason: "The TCP/IP protocol requires the ephemeral port range for communication over the internet for a public server"
  
    WebServerLogtoLambda:
      Type: AWS::Logs::SubscriptionFilter
      Properties:
        DestinationArn: !Sub ${LogStreamerArn}
        FilterPattern: !FindInMap [FilterPatternLookup, Common, Pattern]
        LogGroupName: !Sub ${WebServerLogGroup}
  
  Outputs:
    PublicIP:
      Description: Public IP of sample web server
      Value: !Sub http://${WebServerHost.PublicIp}
  