anchore/anchore-fargate-ee.yml [87:678]:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        PrivilegedMode: True
        Type: LINUX_CONTAINER
        ImagePullCredentialsType: CODEBUILD
        EnvironmentVariables:
          - Name: IMAGE
            Value: !Sub
              - ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${Repo}
              - { Repo: !Ref AnchoreRepository}
      Name: !Join [ '-', [ !Ref ResourceName, 'anchore', 'build'  ] ]
      ServiceRole: !GetAtt AnchoreCodeBuildRole.Arn
      Source: 
        Type: S3
        Location: !Join [ '/', [ !Ref BucketName, 'devsecops/containers/anchore-build.zip'  ] ]
        BuildSpec: |
          version: 0.2
          phases:
            install:
              runtime-versions:
                docker: 18
                python: 3.7
            pre_build: 
              commands:
                - curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
                - apt-get update && apt-get install -y python-dev
                - curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py  
                - python get-pip.py
                - pip install awscli
                - $(aws ecr get-login --no-include-email)
            build: 
              commands:
                - docker build $CODEBUILD_SRC_DIR -t $IMAGE
                - docker push $IMAGE

  CodeBuildStarter:
    Type: Custom::CodeBuildStarter
    Properties:
        ServiceToken: !GetAtt CodeBuildStarterLambda.Arn
        ProjectName: !Ref AnchoreCodeBuild

  CodeBuildStarterLambdaRole:
    Type: AWS::IAM::Role
    Properties: 
      RoleName: CodeBuildStarterLambdaRole
      AssumeRolePolicyDocument: 
        Version: 2012-10-17
        Statement: 
          - 
            Effect: Allow
            Principal: 
              Service: 
                - lambda.amazonaws.com
            Action: 
              - sts:AssumeRole
      Path: /
      Policies: 
        - 
          PolicyName: Policy
          PolicyDocument: 
            Version: 2012-10-17
            Statement: 
              - 
                Effect: Allow
                Action:
                  - codebuild:*
                Resource: '*'

  CodeBuildStarterLambda:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Join [ '-', [ !Ref ResourceName, 'codebuild', 'starter'  ] ]
      Role: !GetAtt CodeBuildStarterLambdaRole.Arn
      Runtime: python3.7
      Handler: index.handler
      Code:
        ZipFile: |
          import json
          import boto3
          from botocore.vendored import requests

          codebuild_client = boto3.client('codebuild')

          def handler(event, context):
              try:
                  if event['RequestType'] == 'Create':
                      response = codebuild_client.start_build(
                        projectName=event['ResourceProperties']['ProjectName']
                      )
                      send(event, context, "SUCCESS")
                  elif event['RequestType'] == 'Update':
                      response = codebuild_client.start_build(
                        projectName=event['ResourceProperties']['ProjectName']
                      )
                      send(event, context, "SUCCESS")
                  elif event['RequestType'] == 'Delete':
                      send(event, context, "SUCCESS")
                  else:
                      send(event, context, "FAILED")
              except:
                  send(event, context, "FAILED")
          
          def send(event, context, responseStatus):
            responseUrl = event['ResponseURL']

            print(responseUrl)

            responseBody = {}
            responseBody['Status'] = responseStatus
            responseBody['Reason'] = 'See the details in CloudWatch Log Stream: ' + context.log_stream_name
            responseBody['PhysicalResourceId'] = context.log_stream_name
            responseBody['StackId'] = event['StackId']
            responseBody['RequestId'] = event['RequestId']
            responseBody['LogicalResourceId'] = event['LogicalResourceId']

            json_responseBody = json.dumps(responseBody,
              indent=4,
              sort_keys=True,
              default=str)

            print("Response body:\n" + json_responseBody)

            headers = {
                'content-type' : '',
                'content-length' : str(len(json_responseBody))
            }

            try:
                response = requests.put(responseUrl,
                                        data=json_responseBody,
                                        headers=headers)
                print("Status code: " + response.reason)
            except Exception as e:
                print("send(..) failed executing requests.put(..): " + str(e))

  dbPassword:
    Type: AWS::SecretsManager::Secret
    Properties:
      GenerateSecretString:
        ExcludePunctuation: True

  AdminPassword:
    Type: AWS::SecretsManager::Secret
    Properties:
      GenerateSecretString:
        ExcludePunctuation: True

  EcsSlr:
    Type: AWS::IAM::ServiceLinkedRole
    Properties:
      AWSServiceName: ecs.amazonaws.com
      Description: Role to enable Amazon ECS to manage your cluster.

  Cluster:
    Type: AWS::ECS::Cluster
    DependsOn: EcsSlr

  AnchoreTaskTaskRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service:
                !Join
                  - ""
                  - - ecs-tasks.
                    - !Ref AWS::URLSuffix
        Version: 2012-10-17

  AnchoreTaskTaskRoleDefaultPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyDocument:
        Statement:
          - Action:
              - ecr:GetAuthorizationToken
              - ecr:BatchCheckLayerAvailability
              - ecr:GetDownloadUrlForLayer
              - ecr:GetRepositoryPolicy
              - ecr:DescribeRepositories
              - ecr:ListImages
              - ecr:DescribeImages
              - ecr:BatchGetImage
              - ecr:InitiateLayerUpload
              - ecr:UploadLayerPart
              - ecr:CompleteLayerUpload
              - ecr:PutImage
            Effect: Allow
            Resource: "*"
        Version: 2012-10-17
      PolicyName: AnchoreTaskTaskRoleDefaultPolicy
      Roles:
        - !Ref AnchoreTaskTaskRole
  
  TaskLoggingLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      # LogGroupName: !Join [ '', [ '/aws/ecs/task',!Ref ResourceName, '-anchore-engine'  ] ]
      RetentionInDays: 365
    DeletionPolicy: Retain

  AnchoreTaskExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service:
                Fn::Join:
                  - ""
                  - - ecs-tasks.
                    - !Ref AWS::URLSuffix
        Version: 2012-10-17

  AnchoreTaskExecutionRoleDefaultPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyDocument:
        Statement:
          - Action:
              - ecr:BatchCheckLayerAvailability
              - ecr:GetDownloadUrlForLayer
              - ecr:BatchGetImage
            Effect: Allow
            Resource: "*"
          - Action: ecr:GetAuthorizationToken
            Effect: Allow
            Resource: "*"
          - Action:
              - logs:CreateLogStream
              - logs:PutLogEvents
            Effect: Allow
            Resource:
              !GetAtt
                - TaskLoggingLogGroup
                - Arn
        Version: 2012-10-17
      PolicyName: AnchoreTaskExecutionRoleDefaultPolicy
      Roles:
        - !Ref AnchoreTaskExecutionRole

  AnchoreTask:
    Type: AWS::ECS::TaskDefinition
    Properties:
      ContainerDefinitions:
        - Environment:
            - Name: ANCHORE_DB_PASSWORD
              Value:
                !Join
                  - ""
                  - - "{{resolve:secretsmanager:"
                    - !Ref dbPassword
                    - ::::}}
            - Name: ANCHORE_ADMIN_PASSWORD
              Value:
                !Join
                  - ""
                  - - "{{resolve:secretsmanager:"
                    - !Ref AdminPassword
                    - ::::}}
            - Name: ANCHORE_DB_HOST
              Value: 127.0.0.1
          Essential: True
          Image: !Sub
            - ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${Repo}
            - { Repo: !Ref AnchoreRepository}
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref TaskLoggingLogGroup
              awslogs-stream-prefix: Anchore
              awslogs-region:
                Ref: AWS::Region
          Name: AnchoreContainer
          PortMappings:
            - ContainerPort: 8228
              Protocol: tcp
        - Environment:
            - Name: POSTGRES_PASSWORD
              Value:
                !Join
                  - ""
                  - - "{{resolve:secretsmanager:"
                    - !Ref dbPassword
                    - ::::}}
          Essential: True
          Image: postgres
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref TaskLoggingLogGroup
              awslogs-stream-prefix: AnchoreDb
              awslogs-region:
                Ref: AWS::Region
          Name: postgres
          PortMappings:
            - ContainerPort: 5432
              Protocol: tcp
      Cpu: "1024"
      ExecutionRoleArn:
        !GetAtt
          - AnchoreTaskExecutionRole
          - Arn
      Family: AnchoreFargateTask
      Memory: "6144"
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      TaskRoleArn:
        !GetAtt
          - AnchoreTaskTaskRole
          - Arn
    DependsOn: 
      - CodeBuildStarter

  LBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security Group for ELB
      SecurityGroupEgress: []
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          Description: Allow from anyone on port 80
          FromPort: 80
          IpProtocol: tcp
          ToPort: 80
      VpcId: !ImportValue 'AnchoreVpcStack:VpcID'

  LB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      LoadBalancerAttributes: []
      Scheme: internet-facing
      SecurityGroups:
        - !GetAtt 
            - LBSecurityGroup
            - GroupId
      Subnets: 
        !Split
          - ","
          - !ImportValue 'AnchoreVpcStack:PublicSubnets'
      Type: application

  AnchoreFargateServiceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: AnchoreFargateService/SecurityGroup
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          Description: Allow all outbound traffic by default
          IpProtocol: "-1"
      SecurityGroupIngress: []
      VpcId: !ImportValue 'AnchoreVpcStack:VpcID'

  LBSecurityGrouptoAnchoreFargateServiceSecurityGroup:
    Type: AWS::EC2::SecurityGroupEgress
    Properties:
      GroupId:
        !GetAtt
          - LBSecurityGroup
          - GroupId
      IpProtocol: tcp
      Description: Load balancer to target
      DestinationSecurityGroupId:
        !GetAtt
          - AnchoreFargateServiceSecurityGroup
          - GroupId
      FromPort: 8228
      ToPort: 8228

  LBPublicListenerECSGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckIntervalSeconds: 60
      HealthCheckPath: /
      HealthCheckPort: "8228"
      HealthCheckTimeoutSeconds: 5
      Matcher:
        HttpCode: 200,404
      Port: 80
      Protocol: HTTP
      TargetGroupAttributes: []
      Targets: []
      TargetType: ip
      VpcId: !ImportValue 'AnchoreVpcStack:VpcID'

  LBPublicListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - TargetGroupArn:
            Ref: LBPublicListenerECSGroup
          Type: forward
      LoadBalancerArn: !Ref LB
      Port: 80
      Protocol: HTTP
      Certificates: []

  AnchoreFargateService:
    Type: AWS::ECS::Service
    Properties:
      TaskDefinition: !Ref AnchoreTask
      Cluster: !Ref Cluster
      DeploymentConfiguration:
        MaximumPercent: 200
        MinimumHealthyPercent: 50
      DesiredCount: 1
      LaunchType: FARGATE
      LoadBalancers:
        - ContainerName: AnchoreContainer
          ContainerPort: 8228
          TargetGroupArn: !Ref LBPublicListenerECSGroup
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: DISABLED
          SecurityGroups:
            - !GetAtt
                - AnchoreFargateServiceSecurityGroup
                - GroupId
          Subnets:
            !Split
              - ","
              - !ImportValue 'AnchoreVpcStack:PrivateSubnets'
    DependsOn:
      - LBPublicListener
  
  AnchoreFargateServiceSecurityGroupfromAnchoreFargateCdkStackLBSecurityGroup:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      IpProtocol: tcp
      Description: Load balancer to target
      FromPort: 8228
      GroupId:
        !GetAtt
          - AnchoreFargateServiceSecurityGroup
          - GroupId
      SourceSecurityGroupId:
        !GetAtt
          - LBSecurityGroup
          - GroupId
      ToPort: 8228

  SendFindingsLambdaRole:
    Type: AWS::IAM::Role
    Properties: 
      RoleName: SendFindingsLambdaRole
      AssumeRolePolicyDocument: 
        Version: 2012-10-17
        Statement: 
          - 
            Effect: Allow
            Principal: 
              Service: 
                - lambda.amazonaws.com
            Action: 
              - sts:AssumeRole
      Path: /
      Policies: 
        - 
          PolicyName: Policy
          PolicyDocument: 
            Version: 2012-10-17
            Statement: 
              - 
                Effect: Allow
                Action:
                  - securityhub:BatchImportFindings
                Resource: '*'

  Boto3LambdaLayer:
    Type: AWS::Lambda::LayerVersion
    Properties:
      LayerName: boto3-layer
      Content:
        S3Bucket: sa-security-specialist-workshops-us-east-2
        S3Key: devsecops/layers/boto3.zip
      CompatibleRuntimes:
        - python3.7
      LicenseInfo: Available under the MIT-0 license.

  SendFindingsLambda: 
    Type: AWS::Lambda::Function
    Properties:
      Layers:
        - !Ref Boto3LambdaLayer
      FunctionName: !Join [ '-', [ !Ref ResourceName, 'sendfindings', 'securityhub'  ] ]
      Description: Sends payload with vulnerabilities findings to Security Hub
      Handler: index.lambda_handler
      Role: !GetAtt SendFindingsLambdaRole.Arn
      Environment:
        Variables:
          ACCOUNT_ID: !Ref AWS::AccountId
          REGION: !Ref AWS::Region
      Code:
        ZipFile: |
          from __future__ import print_function
          import boto3
          import traceback
          import os
          import uuid
          import datetime
          from enum import Enum
          from itertools import zip_longest

          client = boto3.client('securityhub')
          account_id = os.environ['ACCOUNT_ID']
          region = os.environ['REGION']
          uuid = str(uuid.uuid4())

          def chunker(seq, size):
              """Chunk it up"""
              return (seq[pos:pos + size] for pos in range(0, len(seq), size))

          class Severity(Enum):
              NONE = 0
              UNKNOWN = 0
              LOW =  39
              NEGLIGIBLE = 39
              MEDIUM = 69
              HIGH = 89
              CRITICAL = 100

          def lambda_handler(event, context):
              """Get the results of the scan and send them to Security Hub"""
              if 'vulnerabilities' in event:

                  # product arn format arn:aws:securityhub:us-west-2:123456789012:product/123456789012/default
                  product_arn = "arn:aws:securityhub:{0}:{1}:product/{2}/default".format(
                      region,
                      account_id,
                      account_id
                  )

                  image_id = event['image_id']
                  image_arn = event['image_arn']
                  curr_time = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).isoformat()
                  
                  for group in chunker(event['vulnerabilities'], 100):
                      findings = []
                      i = 0
                      for vuln in group:
                          finding = {
                              'SchemaVersion': '2018-10-08',
                              'Id': '{0}-{1}-{2}'.format(image_id,
                                  vuln['vuln'],
                                  str(i)),
                              'ProductArn': product_arn,
                              'ProductFields': {
                                'ProviderName': 'Anchore',
                                'ProviderVersion': 'v0.8.2',
                              },
                              'GeneratorId': uuid,
                              'AwsAccountId': account_id,
                              'Types': [
                                  "Software and Configuration Checks/Vulnerabilities/CVE"
                              ],
                              'CreatedAt': curr_time,
                              'UpdatedAt': curr_time,
                              'Severity': {
                                  'Normalized': Severity[vuln['severity'].upper()].value
                              },
                              'Title': 'Vulnerability {0} found in {1}'.format(vuln['vuln'],
                                  image_id),
                              'Description': 'Vulnerability {0} found in {1} for {2}'.format(vuln['vuln'],
                                  vuln['package'],
                                  image_id),
                              'SourceUrl': vuln['url'],
                              'Resources': [
                                  {
                                      'Type': 'Container',
                                      'Id': image_arn,
                                      'Details': {
                                          'Container': {
                                              'ImageId': image_id,
                                          }
                                      }
                                  }
                              ],
                              'Remediation': {
                                  'Recommendation': {
                                      'Text': 'Update {0} to version {1}'.format(vuln['package'],
                                                                                  vuln['fix'])
                                  }
                              },
                              'UserDefinedFields': {
                                  'feedGroup': vuln['feed_group'],
                                  'package': vuln['package'],
                                  'packageType': vuln['package_type'],
                                  'severity': vuln['severity'],
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -



anchore/anchore-fargate.yml [86:677]:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        PrivilegedMode: True
        Type: LINUX_CONTAINER
        ImagePullCredentialsType: CODEBUILD
        EnvironmentVariables:
          - Name: IMAGE
            Value: !Sub
              - ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${Repo}
              - { Repo: !Ref AnchoreRepository}
      Name: !Join [ '-', [ !Ref ResourceName, 'anchore', 'build'  ] ]
      ServiceRole: !GetAtt AnchoreCodeBuildRole.Arn
      Source: 
        Type: S3
        Location: !Join [ '/', [ !Ref BucketName, 'devsecops/containers/anchore-build.zip'  ] ]
        BuildSpec: |
          version: 0.2
          phases:
            install:
              runtime-versions:
                docker: 18
                python: 3.7
            pre_build: 
              commands:
                - curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
                - apt-get update && apt-get install -y python-dev
                - curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py  
                - python get-pip.py
                - pip install awscli
                - $(aws ecr get-login --no-include-email)
            build: 
              commands:
                - docker build $CODEBUILD_SRC_DIR -t $IMAGE
                - docker push $IMAGE

  CodeBuildStarter:
    Type: Custom::CodeBuildStarter
    Properties:
        ServiceToken: !GetAtt CodeBuildStarterLambda.Arn
        ProjectName: !Ref AnchoreCodeBuild

  CodeBuildStarterLambdaRole:
    Type: AWS::IAM::Role
    Properties: 
      RoleName: CodeBuildStarterLambdaRole
      AssumeRolePolicyDocument: 
        Version: 2012-10-17
        Statement: 
          - 
            Effect: Allow
            Principal: 
              Service: 
                - lambda.amazonaws.com
            Action: 
              - sts:AssumeRole
      Path: /
      Policies: 
        - 
          PolicyName: Policy
          PolicyDocument: 
            Version: 2012-10-17
            Statement: 
              - 
                Effect: Allow
                Action:
                  - codebuild:*
                Resource: '*'

  CodeBuildStarterLambda:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Join [ '-', [ !Ref ResourceName, 'codebuild', 'starter'  ] ]
      Role: !GetAtt CodeBuildStarterLambdaRole.Arn
      Runtime: python3.7
      Handler: index.handler
      Code:
        ZipFile: |
          import json
          import boto3
          from botocore.vendored import requests

          codebuild_client = boto3.client('codebuild')

          def handler(event, context):
              try:
                  if event['RequestType'] == 'Create':
                      response = codebuild_client.start_build(
                        projectName=event['ResourceProperties']['ProjectName']
                      )
                      send(event, context, "SUCCESS")
                  elif event['RequestType'] == 'Update':
                      response = codebuild_client.start_build(
                        projectName=event['ResourceProperties']['ProjectName']
                      )
                      send(event, context, "SUCCESS")
                  elif event['RequestType'] == 'Delete':
                      send(event, context, "SUCCESS")
                  else:
                      send(event, context, "FAILED")
              except:
                  send(event, context, "FAILED")
          
          def send(event, context, responseStatus):
            responseUrl = event['ResponseURL']

            print(responseUrl)

            responseBody = {}
            responseBody['Status'] = responseStatus
            responseBody['Reason'] = 'See the details in CloudWatch Log Stream: ' + context.log_stream_name
            responseBody['PhysicalResourceId'] = context.log_stream_name
            responseBody['StackId'] = event['StackId']
            responseBody['RequestId'] = event['RequestId']
            responseBody['LogicalResourceId'] = event['LogicalResourceId']

            json_responseBody = json.dumps(responseBody,
              indent=4,
              sort_keys=True,
              default=str)

            print("Response body:\n" + json_responseBody)

            headers = {
                'content-type' : '',
                'content-length' : str(len(json_responseBody))
            }

            try:
                response = requests.put(responseUrl,
                                        data=json_responseBody,
                                        headers=headers)
                print("Status code: " + response.reason)
            except Exception as e:
                print("send(..) failed executing requests.put(..): " + str(e))

  dbPassword:
    Type: AWS::SecretsManager::Secret
    Properties:
      GenerateSecretString:
        ExcludePunctuation: True

  AdminPassword:
    Type: AWS::SecretsManager::Secret
    Properties:
      GenerateSecretString:
        ExcludePunctuation: True

  EcsSlr:
    Type: AWS::IAM::ServiceLinkedRole
    Properties:
      AWSServiceName: ecs.amazonaws.com
      Description: Role to enable Amazon ECS to manage your cluster.

  Cluster:
    Type: AWS::ECS::Cluster
    DependsOn: EcsSlr

  AnchoreTaskTaskRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service:
                !Join
                  - ""
                  - - ecs-tasks.
                    - !Ref AWS::URLSuffix
        Version: 2012-10-17

  AnchoreTaskTaskRoleDefaultPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyDocument:
        Statement:
          - Action:
              - ecr:GetAuthorizationToken
              - ecr:BatchCheckLayerAvailability
              - ecr:GetDownloadUrlForLayer
              - ecr:GetRepositoryPolicy
              - ecr:DescribeRepositories
              - ecr:ListImages
              - ecr:DescribeImages
              - ecr:BatchGetImage
              - ecr:InitiateLayerUpload
              - ecr:UploadLayerPart
              - ecr:CompleteLayerUpload
              - ecr:PutImage
            Effect: Allow
            Resource: "*"
        Version: 2012-10-17
      PolicyName: AnchoreTaskTaskRoleDefaultPolicy
      Roles:
        - !Ref AnchoreTaskTaskRole
  
  TaskLoggingLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      # LogGroupName: !Join [ '', [ '/aws/ecs/task',!Ref ResourceName, '-anchore-engine'  ] ]
      RetentionInDays: 365
    DeletionPolicy: Retain

  AnchoreTaskExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service:
                Fn::Join:
                  - ""
                  - - ecs-tasks.
                    - !Ref AWS::URLSuffix
        Version: 2012-10-17

  AnchoreTaskExecutionRoleDefaultPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyDocument:
        Statement:
          - Action:
              - ecr:BatchCheckLayerAvailability
              - ecr:GetDownloadUrlForLayer
              - ecr:BatchGetImage
            Effect: Allow
            Resource: "*"
          - Action: ecr:GetAuthorizationToken
            Effect: Allow
            Resource: "*"
          - Action:
              - logs:CreateLogStream
              - logs:PutLogEvents
            Effect: Allow
            Resource:
              !GetAtt
                - TaskLoggingLogGroup
                - Arn
        Version: 2012-10-17
      PolicyName: AnchoreTaskExecutionRoleDefaultPolicy
      Roles:
        - !Ref AnchoreTaskExecutionRole

  AnchoreTask:
    Type: AWS::ECS::TaskDefinition
    Properties:
      ContainerDefinitions:
        - Environment:
            - Name: ANCHORE_DB_PASSWORD
              Value:
                !Join
                  - ""
                  - - "{{resolve:secretsmanager:"
                    - !Ref dbPassword
                    - ::::}}
            - Name: ANCHORE_ADMIN_PASSWORD
              Value:
                !Join
                  - ""
                  - - "{{resolve:secretsmanager:"
                    - !Ref AdminPassword
                    - ::::}}
            - Name: ANCHORE_DB_HOST
              Value: 127.0.0.1
          Essential: True
          Image: !Sub
            - ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${Repo}
            - { Repo: !Ref AnchoreRepository}
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref TaskLoggingLogGroup
              awslogs-stream-prefix: Anchore
              awslogs-region:
                Ref: AWS::Region
          Name: AnchoreContainer
          PortMappings:
            - ContainerPort: 8228
              Protocol: tcp
        - Environment:
            - Name: POSTGRES_PASSWORD
              Value:
                !Join
                  - ""
                  - - "{{resolve:secretsmanager:"
                    - !Ref dbPassword
                    - ::::}}
          Essential: True
          Image: postgres
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref TaskLoggingLogGroup
              awslogs-stream-prefix: AnchoreDb
              awslogs-region:
                Ref: AWS::Region
          Name: postgres
          PortMappings:
            - ContainerPort: 5432
              Protocol: tcp
      Cpu: "1024"
      ExecutionRoleArn:
        !GetAtt
          - AnchoreTaskExecutionRole
          - Arn
      Family: AnchoreFargateTask
      Memory: "6144"
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      TaskRoleArn:
        !GetAtt
          - AnchoreTaskTaskRole
          - Arn
    DependsOn: 
      - CodeBuildStarter

  LBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security Group for ELB
      SecurityGroupEgress: []
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          Description: Allow from anyone on port 80
          FromPort: 80
          IpProtocol: tcp
          ToPort: 80
      VpcId: !ImportValue 'AnchoreVpcStack:VpcID'

  LB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      LoadBalancerAttributes: []
      Scheme: internet-facing
      SecurityGroups:
        - !GetAtt 
            - LBSecurityGroup
            - GroupId
      Subnets: 
        !Split
          - ","
          - !ImportValue 'AnchoreVpcStack:PublicSubnets'
      Type: application

  AnchoreFargateServiceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: AnchoreFargateService/SecurityGroup
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          Description: Allow all outbound traffic by default
          IpProtocol: "-1"
      SecurityGroupIngress: []
      VpcId: !ImportValue 'AnchoreVpcStack:VpcID'

  LBSecurityGrouptoAnchoreFargateServiceSecurityGroup:
    Type: AWS::EC2::SecurityGroupEgress
    Properties:
      GroupId:
        !GetAtt
          - LBSecurityGroup
          - GroupId
      IpProtocol: tcp
      Description: Load balancer to target
      DestinationSecurityGroupId:
        !GetAtt
          - AnchoreFargateServiceSecurityGroup
          - GroupId
      FromPort: 8228
      ToPort: 8228

  LBPublicListenerECSGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckIntervalSeconds: 60
      HealthCheckPath: /
      HealthCheckPort: "8228"
      HealthCheckTimeoutSeconds: 5
      Matcher:
        HttpCode: 200,404
      Port: 80
      Protocol: HTTP
      TargetGroupAttributes: []
      Targets: []
      TargetType: ip
      VpcId: !ImportValue 'AnchoreVpcStack:VpcID'

  LBPublicListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - TargetGroupArn:
            Ref: LBPublicListenerECSGroup
          Type: forward
      LoadBalancerArn: !Ref LB
      Port: 80
      Protocol: HTTP
      Certificates: []

  AnchoreFargateService:
    Type: AWS::ECS::Service
    Properties:
      TaskDefinition: !Ref AnchoreTask
      Cluster: !Ref Cluster
      DeploymentConfiguration:
        MaximumPercent: 200
        MinimumHealthyPercent: 50
      DesiredCount: 1
      LaunchType: FARGATE
      LoadBalancers:
        - ContainerName: AnchoreContainer
          ContainerPort: 8228
          TargetGroupArn: !Ref LBPublicListenerECSGroup
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: DISABLED
          SecurityGroups:
            - !GetAtt
                - AnchoreFargateServiceSecurityGroup
                - GroupId
          Subnets:
            !Split
              - ","
              - !ImportValue 'AnchoreVpcStack:PrivateSubnets'
    DependsOn:
      - LBPublicListener
  
  AnchoreFargateServiceSecurityGroupfromAnchoreFargateCdkStackLBSecurityGroup:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      IpProtocol: tcp
      Description: Load balancer to target
      FromPort: 8228
      GroupId:
        !GetAtt
          - AnchoreFargateServiceSecurityGroup
          - GroupId
      SourceSecurityGroupId:
        !GetAtt
          - LBSecurityGroup
          - GroupId
      ToPort: 8228

  SendFindingsLambdaRole:
    Type: AWS::IAM::Role
    Properties: 
      RoleName: SendFindingsLambdaRole
      AssumeRolePolicyDocument: 
        Version: 2012-10-17
        Statement: 
          - 
            Effect: Allow
            Principal: 
              Service: 
                - lambda.amazonaws.com
            Action: 
              - sts:AssumeRole
      Path: /
      Policies: 
        - 
          PolicyName: Policy
          PolicyDocument: 
            Version: 2012-10-17
            Statement: 
              - 
                Effect: Allow
                Action:
                  - securityhub:BatchImportFindings
                Resource: '*'

  Boto3LambdaLayer:
    Type: AWS::Lambda::LayerVersion
    Properties:
      LayerName: boto3-layer
      Content:
        S3Bucket: sa-security-specialist-workshops-us-east-2
        S3Key: devsecops/layers/boto3.zip
      CompatibleRuntimes:
        - python3.7
      LicenseInfo: Available under the MIT-0 license.

  SendFindingsLambda: 
    Type: AWS::Lambda::Function
    Properties:
      Layers:
        - !Ref Boto3LambdaLayer
      FunctionName: !Join [ '-', [ !Ref ResourceName, 'sendfindings', 'securityhub'  ] ]
      Description: Sends payload with vulnerabilities findings to Security Hub
      Handler: index.lambda_handler
      Role: !GetAtt SendFindingsLambdaRole.Arn
      Environment:
        Variables:
          ACCOUNT_ID: !Ref AWS::AccountId
          REGION: !Ref AWS::Region
      Code:
        ZipFile: |
          from __future__ import print_function
          import boto3
          import traceback
          import os
          import uuid
          import datetime
          from enum import Enum
          from itertools import zip_longest

          client = boto3.client('securityhub')
          account_id = os.environ['ACCOUNT_ID']
          region = os.environ['REGION']
          uuid = str(uuid.uuid4())

          def chunker(seq, size):
              """Chunk it up"""
              return (seq[pos:pos + size] for pos in range(0, len(seq), size))

          class Severity(Enum):
              NONE = 0
              UNKNOWN = 0
              LOW =  39
              NEGLIGIBLE = 39
              MEDIUM = 69
              HIGH = 89
              CRITICAL = 100

          def lambda_handler(event, context):
              """Get the results of the scan and send them to Security Hub"""
              if 'vulnerabilities' in event:

                  # product arn format arn:aws:securityhub:us-west-2:123456789012:product/123456789012/default
                  product_arn = "arn:aws:securityhub:{0}:{1}:product/{2}/default".format(
                      region,
                      account_id,
                      account_id
                  )

                  image_id = event['image_id']
                  image_arn = event['image_arn']
                  curr_time = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).isoformat()
                  
                  for group in chunker(event['vulnerabilities'], 100):
                      findings = []
                      i = 0
                      for vuln in group:
                          finding = {
                              'SchemaVersion': '2018-10-08',
                              'Id': '{0}-{1}-{2}'.format(image_id,
                                  vuln['vuln'],
                                  str(i)),
                              'ProductArn': product_arn,
                              'ProductFields': {
                                'ProviderName': 'Anchore',
                                'ProviderVersion': 'v0.8.2',
                              },
                              'GeneratorId': uuid,
                              'AwsAccountId': account_id,
                              'Types': [
                                  "Software and Configuration Checks/Vulnerabilities/CVE"
                              ],
                              'CreatedAt': curr_time,
                              'UpdatedAt': curr_time,
                              'Severity': {
                                  'Normalized': Severity[vuln['severity'].upper()].value
                              },
                              'Title': 'Vulnerability {0} found in {1}'.format(vuln['vuln'],
                                  image_id),
                              'Description': 'Vulnerability {0} found in {1} for {2}'.format(vuln['vuln'],
                                  vuln['package'],
                                  image_id),
                              'SourceUrl': vuln['url'],
                              'Resources': [
                                  {
                                      'Type': 'Container',
                                      'Id': image_arn,
                                      'Details': {
                                          'Container': {
                                              'ImageId': image_id,
                                          }
                                      }
                                  }
                              ],
                              'Remediation': {
                                  'Recommendation': {
                                      'Text': 'Update {0} to version {1}'.format(vuln['package'],
                                                                                  vuln['fix'])
                                  }
                              },
                              'UserDefinedFields': {
                                  'feedGroup': vuln['feed_group'],
                                  'package': vuln['package'],
                                  'packageType': vuln['package_type'],
                                  'severity': vuln['severity'],
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -



