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: MyLayer: Type: AWS::Serverless::LayerVersion Properties: LayerName: Boto3 Description: My Lambda Layer with Deserialize Function, Objectpath Module & AWS Regions JSON ContentUri: lambda-layers/boto3-layer.zip CompatibleRuntimes: - python3.6 - python3.7 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: "textract" Effect: Allow Action: - textract:* 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 Layers: - !Ref MyLayer 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: nodejs8.10 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/