AWSTemplateFormatVersion: 2010-09-09
Transform:
- AWS::Serverless-2016-10-31
- AWS::CodeStar

Parameters:
  ProjectId:
    Type: String
    Description: CodeStar projectId used to associate new resources to team members
    
  # Name for ES Domain
  DOMAINNAME:
    Description: Name for the Amazon ES domain that this template will create. Domain names must start with a lowercase letter and must be between 3 and 28 characters. Valid characters are a-z (lowercase only), 0-9.
    Type: String
    Default: resumesearchapp


  # Email address for Cognito Admin user
  CognitoAdminEmail:
    Type: String
    Default: cognitoadmin@example.com
    AllowedPattern: '^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$'
    Description: E-mail address of the Cognito admin name

# Enable blue/green deployments using this Globals section. For instructions, see the AWS CodeStar User Guide:
# https://docs.aws.amazon.com/codestar/latest/userguide/how-to-modify-serverless-project.html?icmpid=docs_acs_rm_tr
#
# Globals:
#   Function:
#     AutoPublishAlias: live
#     DeploymentPreference:
#       Enabled: true
#       Type: Canary10Percent5Minutes


Mappings:
  # Lambda source code mapping
  SourceCode:
    General:
      S3Bucket: "solutions"
      KeyPrefix: "centralized-logging/v2.2.0"

Resources:
  ComprehendKeyPhraseAnalysis:
    Properties:
      Description: "Triggered by S3 review upload to the repo bucket and start the key phrase analysis via Amazon Comprehend"
      Handler: comprehend.handler
      MemorySize: 128
      Policies:
        Statement:
          -
            Sid: "comprehend"
            Effect: Allow
            Action:
              - comprehend:*
            Resource: "*"
          -
            Sid: "s3"
            Effect: Allow
            Action:
              - s3:*
            Resource: !Sub "arn:aws:s3:::${S3}/*"
          -
            Sid: "es"
            Effect: Allow
            Action:
              - es:*
            Resource: "*"
      Environment:
        Variables:
          bucket: !Ref S3
          esDomain: !GetAtt ElasticsearchDomain.DomainEndpoint
          
      Runtime: python3.6
      Timeout: 300
    Type: AWS::Serverless::Function
 
  S3:
    Type: AWS::S3::Bucket
 
    
  TestS3BucketEventPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:invokeFunction
      SourceAccount: !Ref 'AWS::AccountId'
      FunctionName: !Ref 'ComprehendKeyPhraseAnalysis'
      SourceArn: !GetAtt
        - S3
        - Arn
      Principal: s3.amazonaws.com


  ApplyNotificationFunctionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Path: /
      Policies:
        - PolicyName: S3BucketNotificationPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Sid: AllowBucketNotification
                Effect: Allow
                Action: s3:PutBucketNotification
                Resource:
                  - !Sub 'arn:aws:s3:::${S3}'
                  - !Sub 'arn:aws:s3:::${S3}/*'
                  
                  
  ApplyBucketNotificationFunction:
    Type: AWS::Lambda::Function
    Properties:
      Description: Dummy function, just logs the received event
      Handler: index.handler
      Runtime: python2.7
      Role: !GetAtt 'ApplyNotificationFunctionRole.Arn'
      Timeout: 240
      Code:
        ZipFile: |
          import boto3
          import logging
          import json
          import cfnresponse

          s3Client = boto3.client('s3')
          logger = logging.getLogger()
          logger.setLevel(logging.DEBUG)

          def addBucketNotification(bucketName, notificationId, functionArn):
            notificationResponse = s3Client.put_bucket_notification_configuration(
              Bucket=bucketName,
              NotificationConfiguration={
                'LambdaFunctionConfigurations': [
                  {
                    'Id': notificationId,
                    'LambdaFunctionArn': functionArn,
                    'Events': [
                      's3:ObjectCreated:*'
                    ]
                  },
                ]
              }
            )
            return notificationResponse

          def create(properties, physical_id):
            bucketName = properties['S3Bucket']
            notificationId = properties['NotificationId']
            functionArn = properties['FunctionARN']
            response = addBucketNotification(bucketName, notificationId, functionArn)
            logger.info('AddBucketNotification response: %s' % json.dumps(response))
            return cfnresponse.SUCCESS, physical_id

          def update(properties, physical_id):
            return cfnresponse.SUCCESS, None

          def delete(properties, physical_id):
            return cfnresponse.SUCCESS, None

          def handler(event, context):
            logger.info('Received event: %s' % json.dumps(event))

            status = cfnresponse.FAILED
            new_physical_id = None

            try:
              properties = event.get('ResourceProperties')
              physical_id = event.get('PhysicalResourceId')

              status, new_physical_id = {
                'Create': create,
                'Update': update,
                'Delete': delete
              }.get(event['RequestType'], lambda x, y: (cfnresponse.FAILED, None))(properties, physical_id)
            except Exception as e:
              logger.error('Exception: %s' % e)
              status = cfnresponse.FAILED
            finally:
              cfnresponse.send(event, context, status, {}, new_physical_id)
     
   #
  # Cognito and IAM
  #
  # Creates a user pool in cognito to auth against
  UserPool:
    Type: 'AWS::Cognito::UserPool'
    Properties:
      UserPoolName: !Sub ${DOMAINNAME}_kibana_access
      AutoVerifiedAttributes:
        - email
      MfaConfiguration: 'OFF'
      EmailVerificationSubject: !Ref AWS::StackName
      Schema:
        - Name: name
          AttributeDataType: String
          Mutable: true
          Required: true
        - Name: email
          AttributeDataType: String
          Mutable: false
          Required: true

  # Creates a needed group in Cognito for Kibana access
  UserPoolGroup:
    Type: "AWS::Cognito::UserPoolGroup"
    Properties:
      Description: 'User pool group for Kibana access'
      GroupName: !Sub ${DOMAINNAME}_kibana_access_group
      Precedence: 0
      UserPoolId: !Ref UserPool

  # Creates a User Pool Client to be used by the identity pool
  UserPoolClient:
    Type: 'AWS::Cognito::UserPoolClient'
    Properties:
      ClientName: !Sub ${DOMAINNAME}-client
      GenerateSecret: false
      UserPoolId: !Ref UserPool

  # Creates a federated Identity pool
  IdentityPool:
    Type: 'AWS::Cognito::IdentityPool'
    Properties:
      IdentityPoolName: !Sub ${DOMAINNAME}Identity
      AllowUnauthenticatedIdentities: true
      CognitoIdentityProviders:
        - ClientId: !Ref UserPoolClient
          ProviderName: !GetAtt UserPool.ProviderName

  # Create a role for unauthorized access to AWS resources. Very limited access.
  # Only allows users in the previously created Identity Pool
  CognitoUnAuthorizedRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: 'Allow'
            Principal:
              Federated: 'cognito-identity.amazonaws.com'
            Action:
              - 'sts:AssumeRoleWithWebIdentity'
            Condition:
              StringEquals:
                'cognito-identity.amazonaws.com:aud': !Ref IdentityPool
              'ForAnyValue:StringLike':
                'cognito-identity.amazonaws.com:amr': unauthenticated
      Policies:
        - PolicyName: 'CognitoUnauthorizedPolicy'
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: 'Allow'
                Action:
                  - 'mobileanalytics:PutEvents'
                  - 'cognito-sync:BulkPublish'
                  - 'cognito-sync:DescribeIdentityPoolUsage'
                  - 'cognito-sync:GetBulkPublishDetails'
                  - 'cognito-sync:GetCognitoEvents'
                  - 'cognito-sync:GetIdentityPoolConfiguration'
                  - 'cognito-sync:ListIdentityPoolUsage'
                  - 'cognito-sync:SetCognitoEvents'
                  - 'congito-sync:SetIdentityPoolConfiguration'
                Resource: !Sub 'arn:aws:cognito-identity:${AWS::Region}:${AWS::AccountId}:identitypool/${IdentityPool}'

  # Create a role for authorized access to AWS resources.
  # Only allows users in the previously created Identity Pool
  CognitoAuthorizedRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: 'Allow'
            Principal:
              Federated: 'cognito-identity.amazonaws.com'
            Action:
              - 'sts:AssumeRoleWithWebIdentity'
            Condition:
              StringEquals:
                'cognito-identity.amazonaws.com:aud': !Ref IdentityPool
              'ForAnyValue:StringLike':
                'cognito-identity.amazonaws.com:amr': authenticated
      Policies:
        - PolicyName: 'CognitoAuthorizedPolicy'
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: 'Allow'
                Action:
                  - 'mobileanalytics:PutEvents'
                  - 'cognito-sync:BulkPublish'
                  - 'cognito-sync:DescribeIdentityPoolUsage'
                  - 'cognito-sync:GetBulkPublishDetails'
                  - 'cognito-sync:GetCognitoEvents'
                  - 'cognito-sync:GetIdentityPoolConfiguration'
                  - 'cognito-sync:ListIdentityPoolUsage'
                  - 'cognito-sync:SetCognitoEvents'
                  - 'congito-sync:SetIdentityPoolConfiguration'
                  - 'cognito-identity:DeleteIdentityPool'
                  - 'cognito-identity:DescribeIdentityPool'
                  - 'cognito-identity:GetIdentityPoolRoles'
                  - 'cognito-identity:GetOpenIdTokenForDeveloperIdentity'
                  - 'cognito-identity:ListIdentities'
                  - 'cognito-identity:LookupDeveloperIdentity'
                  - 'cognito-identity:MergeDeveloperIdentities'
                  - 'cognito-identity:UnlikeDeveloperIdentity'
                  - 'cognito-identity:UpdateIdentityPool'
                Resource: !Sub 'arn:aws:cognito-identity:${AWS::Region}:${AWS::AccountId}:identitypool/${IdentityPool}'

  CognitoESAccessRole:
    Type: 'AWS::IAM::Role'
    Properties:
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonESCognitoAccess
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: 'Allow'
            Principal:
              Service: 'es.amazonaws.com'
            Action:
              - 'sts:AssumeRole'

  # Assigns the roles to the Identity Pool
  IdentityPoolRoleMapping:
    Type: 'AWS::Cognito::IdentityPoolRoleAttachment'
    Properties:
      IdentityPoolId: !Ref IdentityPool
      Roles:
        authenticated: !GetAtt CognitoAuthorizedRole.Arn
        unauthenticated: !GetAtt CognitoUnAuthorizedRole.Arn
        
  AdminUser:
    Type: 'AWS::Cognito::UserPoolUser'
    Properties:
      DesiredDeliveryMediums:
        - 'EMAIL'
      UserAttributes:
        - Name: email
          Value: !Ref CognitoAdminEmail
      Username: !Ref CognitoAdminEmail
      UserPoolId: !Ref UserPool

  # Custom resource to configure Cognito and ES
  SetupESCognito:
    Type: 'Custom::SetupESCognito'
    Version: 1.0
    Properties:
      ServiceToken: !GetAtt LambdaESCognito.Arn
      Domain: !Ref DOMAINNAME
      CognitoDomain: !Sub ${DOMAINNAME}-${AWS::AccountId}
      UserPoolId: !Ref UserPool
      IdentityPoolId: !Ref IdentityPool
      RoleArn: !GetAtt CognitoESAccessRole.Arn

  LambdaESCognito:
    Type: 'AWS::Lambda::Function'
    Properties:
      Description: Centralized Logging - Lambda function to enable cognito authentication for kibana
      Environment:
        Variables:
          # V56536055 - 10/08/2018 - better logging capabilities
          LOG_LEVEL: 'INFO' #change to WARN, ERROR or DEBUG as needed
      Handler: index.handler
      Runtime: nodejs12.x
      Timeout: 600
      Role: !GetAtt LambdaESCognitoRole.Arn
      Code:
        S3Bucket: !Join ["-", [!FindInMap ["SourceCode", "General", "S3Bucket"], Ref: "AWS::Region"]]
        S3Key: !Join ["/", [!FindInMap ["SourceCode", "General", "KeyPrefix"],  "clog-auth.zip"]]

  LambdaESCognitoRole:
    Type: AWS::IAM::Role
    DependsOn: ElasticsearchDomain
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - lambda.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: "/"
      Policies:
      - PolicyName: root
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
            - logs:CreateLogGroup
            - logs:CreateLogStream
            - logs:PutLogEvents
            Resource: arn:aws:logs:*:*:*
          - Effect: Allow
            Action:
            - es:UpdateElasticsearchDomainConfig
            Resource: !Sub 'arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${DOMAINNAME}'
          - Effect: Allow
            Action:
            - cognito-idp:CreateUserPoolDomain
            - cognito-idp:DeleteUserPoolDomain
            Resource: !GetAtt UserPool.Arn
          - Effect: Allow
            Action:
            - iam:PassRole
            Resource: !GetAtt CognitoESAccessRole.Arn

  
    
  ElasticsearchDomain: 
    Type: AWS::Elasticsearch::Domain
    Properties:
      DomainName: !Ref DOMAINNAME
      ElasticsearchVersion: "6.3"
      ElasticsearchClusterConfig: 
        InstanceCount: "1"
        InstanceType: "t2.small.elasticsearch"
      EBSOptions: 
        EBSEnabled: true
        Iops: 0
        VolumeSize: 10
        VolumeType: "gp2"
      SnapshotOptions: 
        AutomatedSnapshotStartHour: "0"
      AccessPolicies: 
        Version: "2012-10-17"
        Statement: 
        - Action: 'es:*'
          Principal:
            AWS: !Sub
              - arn:aws:sts::${AWS::AccountId}:assumed-role/${AuthRole}/CognitoIdentityCredentials
              - { AuthRole: !Ref CognitoAuthorizedRole }
          Effect: Allow
          Resource: !Sub 'arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${DOMAINNAME}/*' 
    

  
  ApplyNotification:
    Type: Custom::ApplyNotification
    Properties:
      ServiceToken: !GetAtt 'ApplyBucketNotificationFunction.Arn'
      S3Bucket: !Ref 'S3'
      FunctionARN: !GetAtt 'ComprehendKeyPhraseAnalysis.Arn'
      NotificationId: S3ObjectCreatedEvent
 
  
 

Outputs:
  S3KeyPhraseBucket:
    Value:
      Ref: "S3"
      
  KibanaLoginURL:
    Description: Kibana login URL
    Value: !Sub https://${ElasticsearchDomain.DomainEndpoint}/_plugin/kibana/