# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 Resources: Landingzone: Type: AWS::S3::Bucket UpdateReplacePolicy: Delete DeletionPolicy: Delete Metadata: aws:cdk:path: VirusScan/Bucket/Resource LandingBucketPolicy: Type: AWS::S3::BucketPolicy Properties: Bucket: Ref: Landingzone PolicyDocument: Statement: - Action: s3:GetObject Condition: StringNotEquals: s3:ExistingObjectTag/Virus_Scan_Result: CLEAN aws:PrincipalArn: - Fn::GetAtt: - workflowLambdaRole - Arn - Fn::GetAtt: - workflowExecutionRole - Arn - Fn::GetAtt: - authproviderUserRole - Arn Effect: Deny Principal: AWS: "*" Resource: - Fn::GetAtt: - Landingzone - Arn - Fn::Join: - "" - - Fn::GetAtt: - Landingzone - Arn - /* Version: "2012-10-17" Metadata: aws:cdk:path: VirusScan/Bucket/Policy/Resource authproviderAuthLambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Action: sts:AssumeRole Effect: Allow Principal: Service: lambda.amazonaws.com Version: "2012-10-17" ManagedPolicyArns: - Fn::Join: - "" - - "arn:" - Ref: AWS::Partition - :iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Metadata: aws:cdk:path: VirusScan/authprovider/AuthLambdaExecutionRole/Resource authprovidergetsecretspolicy: Type: AWS::IAM::Policy Properties: PolicyDocument: Statement: - Action: secretsmanager:GetSecretValue Effect: Allow Resource: arn:aws:secretsmanager:*:*:secret:SFTP/* Version: "2012-10-17" PolicyName: authprovidergetsecretspolicy Roles: - Ref: authproviderAuthLambdaExecutionRole Metadata: aws:cdk:path: VirusScan/authprovider/getsecretspolicy/Resource authproviderAuthHandler: Type: AWS::Lambda::Function Properties: Code: ZipFile: | import os import json import boto3 import base64 from botocore.exceptions import ClientError def lambda_handler(event, context): resp_data = {} if 'username' not in event or 'serverId' not in event: print("Incoming username or serverId missing - Unexpected") return resp_data # It is recommended to verify server ID against some value, this template does not verify server ID input_username = event['username'] print("Username: {}, ServerId: {}".format(input_username, event['serverId'])); if 'password' in event: input_password = event['password'] if input_password == '' and (event['protocol'] == 'FTP' or event['protocol'] == 'FTPS'): print("Empty password not allowed") return resp_data else: print("No password, checking for SSH public key") input_password = '' # Lookup user's secret which can contain the password or SSH public keys resp = get_secret("SFTP/" + input_username) if resp != None: resp_dict = json.loads(resp) else: print("Secrets Manager exception thrown") return {} if input_password != '': if 'Password' in resp_dict: resp_password = resp_dict['Password'] else: print("Unable to authenticate user - No field match in Secret for password") return {} if resp_password != input_password: print("Unable to authenticate user - Incoming password does not match stored") return {} else: # SSH Public Key Auth Flow - The incoming password was empty so we are trying ssh auth and need to return the public key data if we have it if 'PublicKey' in resp_dict: resp_data['PublicKeys'] = [resp_dict['PublicKey']] else: print("Unable to authenticate user - No public keys found") return {} # If we've got this far then we've either authenticated the user by password or we're using SSH public key auth and # we've begun constructing the data response. Check for each key value pair. # These are required so set to empty string if missing if 'Role' in resp_dict: resp_data['Role'] = resp_dict['Role'] else: print("No field match for role - Set empty string in response") resp_data['Role'] = '' # These are optional so ignore if not present # if 'Policy' in resp_dict: # resp_data['Policy'] = resp_dict['Policy'] if 'HomeDirectoryDetails' in resp_dict: print("HomeDirectoryDetails found - Applying setting for virtual folders") resp_data['HomeDirectoryDetails'] = resp_dict['HomeDirectoryDetails'] resp_data['HomeDirectoryType'] = "LOGICAL" elif 'HomeDirectory' in resp_dict: print("HomeDirectory found - Cannot be used with HomeDirectoryDetails") resp_data['HomeDirectory'] = resp_dict['HomeDirectory'] else: print("HomeDirectory not found - Defaulting to /") return resp_data def get_secret(id): region = os.environ['SecretsManagerRegion'] print("Secrets Manager Region: "+region) client = boto3.session.Session().client(service_name='secretsmanager', region_name=region) try: resp = client.get_secret_value(SecretId=id) # Decrypts secret using the associated KMS CMK.Depending on whether the secret is a string or binary, one of these fields will be populated. if 'SecretString' in resp: return resp['SecretString'] else: return base64.b64decode(resp['SecretBinary']) except ClientError as err: print('Error Talking to SecretsManager: ' + err.response['Error']['Code'] + ', Message: ' + str(err)) return None Role: Fn::GetAtt: - authproviderAuthLambdaExecutionRole - Arn Environment: Variables: SecretsManagerRegion: Ref: AWS::Region Handler: index.lambda_handler Runtime: python3.7 DependsOn: - authproviderAuthLambdaExecutionRole Metadata: aws:cdk:path: VirusScan/authprovider/AuthHandler/Resource aws:asset:path: asset.c9416685835cc09347c71c5563d25f9b9e8c901f1b0e7d73331c3435f511a8cb aws:asset:is-bundled: false aws:asset:property: Code authproviderAuthHandlerInvoke: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: Fn::GetAtt: - authproviderAuthHandler - Arn Principal: transfer.amazonaws.com SourceArn: Fn::GetAtt: - transferServer - Arn Metadata: aws:cdk:path: VirusScan/authprovider/AuthHandler/InvokezAo1J1AonYoIEbgR+cNHgK7I7z+MV9Cu3bBFwtblAkU= authproviderUserRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Action: sts:AssumeRole Effect: Allow Principal: Service: transfer.amazonaws.com Version: "2012-10-17" Description: VirusScan User Role Policies: - PolicyDocument: Statement: - Action: - s3:GetBucketLocation - s3:ListAllMyBuckets Effect: Allow Resource: "*" - Action: - s3:DeleteObject - s3:DeleteObjectVersion - s3:GetObject - s3:GetObjectACL - s3:GetObjectVersion - s3:PutObject - s3:PutObjectACL - s3:ListBucket Effect: Allow Resource: - Fn::GetAtt: - Landingzone - Arn - Fn::Join: - "" - - Fn::GetAtt: - Landingzone - Arn - /* Version: "2012-10-17" PolicyName: userpolicy Metadata: aws:cdk:path: VirusScan/authprovider/UserRole/Resource authproviderclient: Type: AWS::SecretsManager::Secret Properties: Name: Fn::Join: - "" - - 'SFTP/' - Ref: UserName GenerateSecretString: GenerateStringKey: Password SecretStringTemplate: Fn::Join: - "" - - '{"username": "' - Ref: UserName - '",' - '"Role": "' - Fn::GetAtt: - authproviderUserRole - Arn - '",' - '"HomeDirectory": "/' - Ref: Landingzone - '"}' UpdateReplacePolicy: Delete DeletionPolicy: Delete workflowLambdaRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Action: sts:AssumeRole Effect: Allow Principal: Service: lambda.amazonaws.com Version: "2012-10-17" ManagedPolicyArns: - Fn::Join: - "" - - "arn:" - Ref: AWS::Partition - :iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Metadata: aws:cdk:path: VirusScan/workflow/LambdaRole/Resource workflowLambdaRoleDefaultPolicy: Type: AWS::IAM::Policy Properties: PolicyDocument: Statement: - Action: s3:GetObject Effect: Allow Resource: Fn::Join: - "" - - Fn::GetAtt: - Landingzone - Arn - /* - Action: - xray:PutTelemetryRecords - xray:PutTraceSegments Effect: Allow Resource: "*" Version: "2012-10-17" PolicyName: workflowLambdaRoleDefaultPolicy Roles: - Ref: workflowLambdaRole Metadata: aws:cdk:path: VirusScan/workflow/LambdaRole/DefaultPolicy/Resource workflowScanFileHandler: Type: AWS::Lambda::Function Properties: Code: ImageUri: Fn::Join: - ":" - - Fn::ImportValue: Fn::Sub: '${CodeBuildStack}-repoUri' - "latest" Role: Fn::GetAtt: - workflowLambdaRole - Arn Architectures: - x86_64 Description: Scans incoming file from TransferFamily Server EphemeralStorage: Size: 10240 MemorySize: 2048 PackageType: Image Timeout: 600 TracingConfig: Mode: Active DependsOn: - workflowLambdaRoleDefaultPolicy - workflowLambdaRole Metadata: aws:cdk:path: VirusScan/workflow/ScanFile/Resource aws:asset:path: asset.92347b94739419f908a0059486902385f862c3f17caa320324cf9d6875dd3217 aws:asset:dockerfile-path: Dockerfile aws:asset:docker-build-args: --platform: linux/amd64 aws:asset:property: Code.ImageUri transferWorkflow: Type: AWS::Transfer::Workflow Properties: Steps: - CustomStepDetails: Name: VirusScan Target: Fn::GetAtt: - workflowScanFileHandler - Arn TimeoutSeconds: 600 Type: CUSTOM - TagStepDetails: Name: TAG_SUCCESS Tags: - Key: VIRUS_SCAN_RESULT Value: CLEAN Type: TAG OnExceptionSteps: - TagStepDetails: Name: TAG_FAILURE Tags: - Key: VIRUS_SCAN_RESULT Value: INFECTED Type: TAG - CopyStepDetails: Name: QUARANTINE_FILE DestinationFileLocation: S3FileLocation: Bucket: Ref: Landingzone Key: QUARANTINED/ SourceFileLocation: ${previous.file} Type: COPY - DeleteStepDetails: Name: DELETE_ORIGINAL_FILE SourceFileLocation: ${original.file} Type: DELETE Metadata: aws:cdk:path: VirusScan/workflow/VirusScanWorkflow workflowupdatepolicy: Type: AWS::IAM::Policy Properties: PolicyDocument: Statement: - Action: transfer:SendWorkflowStepState Effect: Allow Resource: Fn::GetAtt: - transferWorkflow - Arn Version: "2012-10-17" PolicyName: workflowupdatepolicy Roles: - Ref: workflowLambdaRole Metadata: aws:cdk:path: VirusScan/workflow/workflowupdate-policy/Resource workflowExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Action: sts:AssumeRole Effect: Allow Principal: Service: transfer.amazonaws.com Version: "2012-10-17" Metadata: aws:cdk:path: VirusScan/workflow/WorkflowExecutionRole/Resource workflowExecutionRoleDefaultPolicy: Type: AWS::IAM::Policy Properties: PolicyDocument: Statement: - Action: - s3:DeleteObject - s3:GetObject - s3:GetObjectTagging - s3:ListBucket - s3:PutObject - s3:PutObjectTagging - s3:PutObjectVersionTagging Effect: Allow Resource: - Fn::GetAtt: - Landingzone - Arn - Fn::Join: - "" - - Fn::GetAtt: - Landingzone - Arn - /* - Action: lambda:InvokeFunction Effect: Allow Resource: - Fn::GetAtt: - workflowScanFileHandler - Arn - Fn::Join: - "" - - Fn::GetAtt: - workflowScanFileHandler - Arn - :* Version: "2012-10-17" PolicyName: workflowExecutionRoleDefaultPolicy Roles: - Ref: workflowExecutionRole Metadata: aws:cdk:path: VirusScan/workflow/WorkflowExecutionRole/DefaultPolicy/Resource transferServerLoggingRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Action: sts:AssumeRole Effect: Allow Principal: Service: transfer.amazonaws.com Version: "2012-10-17" ManagedPolicyArns: - Fn::Join: - "" - - "arn:" - Ref: AWS::Partition - :iam::aws:policy/CloudWatchLogsFullAccess Metadata: aws:cdk:path: VirusScan/myserver/LoggingRole/Resource transferServer: Type: AWS::Transfer::Server Properties: Domain: S3 EndpointType: PUBLIC IdentityProviderDetails: Function: Fn::GetAtt: - authproviderAuthHandler - Arn IdentityProviderType: AWS_LAMBDA LoggingRole: Fn::GetAtt: - transferServerLoggingRole - Arn Protocols: - SFTP WorkflowDetails: OnUpload: - ExecutionRole: Fn::GetAtt: - workflowExecutionRole - Arn WorkflowId: Ref: transferWorkflow Metadata: aws:cdk:path: VirusScan/myserver/TransferServer #codebuiild success rule - target is the Lambda function codeBuildSuccessEvent: Type: AWS::Events::Rule Properties: EventPattern: source: - aws.codebuild detail: project-name: - Fn::ImportValue: Fn::Sub: '${CodeBuildStack}-projectName' build-status: - SUCCEEDED detail-type: - CodeBuild Build State Change State: ENABLED Targets: - Arn: Fn::GetAtt: - updateClamAVDefinitionsLambda - Arn Id: Target0 Metadata: aws:cdk:path: VirusScan/avcodebuild/AVScanUpdateDefinitions/eventsuccessid/Resource #permission for codebuild success event to trigger the lambda function codeBuildSuccessEventTriggerPermission: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: Fn::GetAtt: - updateClamAVDefinitionsLambda - Arn Principal: events.amazonaws.com SourceArn: Fn::GetAtt: - codeBuildSuccessEvent - Arn Metadata: aws:cdk:path: VirusScan/avcodebuild/AVScanUpdateDefinitions/eventsuccessid/AllowEventRuleVirusScanavcodebuildupdateAVdefinitions903ABFEF #role for Lambda func - dependent on transfer server workflow updateLambdaIAMRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Action: sts:AssumeRole Effect: Allow Principal: Service: lambda.amazonaws.com Version: "2012-10-17" Description: AV Definitions update role Policies: - PolicyDocument: Statement: - Action: lambda:UpdateFunctionCode Effect: Allow Resource: - Fn::ImportValue: Fn::Sub: '${CodeBuildStack}-repoArn' - Action: - ecr:GetRepositoryPolicy - ecr:SetRepositoryPolicy Effect: Allow Resource: Fn::GetAtt: - workflowScanFileHandler - Arn Version: "2012-10-17" PolicyName: userpolicy Metadata: aws:cdk:path: VirusScan/avcodebuild/Update_Defintions_Role/Resource #Lambda func updateClamAVDefinitionsLambda: Type: AWS::Lambda::Function Properties: Code: ZipFile: | import boto3 import os client = boto3.client('lambda') def lambda_handler(event,context): imageuri = os.environ['imageuri'] funcname = os.environ['funcname'] response = client.update_function_code( FunctionName=funcname, ImageUri=imageuri+':latest', Architectures=['x86_64']) return response Role: Fn::GetAtt: - updateLambdaIAMRole - Arn Environment: Variables: imageuri: Fn::Join: - ":" - - Fn::ImportValue: Fn::Sub: '${CodeBuildStack}-repoUri' - "latest" funcname: Fn::GetAtt: - workflowScanFileHandler - Arn Handler: index.lambda_handler Runtime: python3.7 DependsOn: - updateLambdaIAMRole Metadata: aws:cdk:path: VirusScan/avcodebuild/updateAVdefinitions/Resource aws:asset:path: asset.0e3a09a757e55b13e9367775272eb207506aa06bb9e09b3b654abf7b47e14abd aws:asset:is-bundled: false aws:asset:property: Code Outputs: SFTPEndpoint: Value: Fn::Join: - "" - - Fn::GetAtt: - transferServer - ServerId - .server.transfer. - Ref: AWS::Region - .amazonaws.com SFTPBucket: Value: Ref: Landingzone Parameters: UserName: Type: String Description: Transfer Family User CodeBuildStack: Type: String Description: Codebuild Stack Default: clamav-codebuild