Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AV-2187: First draft of clamav scanner CDK port #2272

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
AV-2187: First draft of clamav scanner CDK port
  • Loading branch information
bzar committed Jun 20, 2024
commit 667b63ada7d3104991c3209b18ddd2367392a1c5
18 changes: 18 additions & 0 deletions cdk/bin/opendata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {ShieldStack} from "../lib/shield-stack";
import {CloudfrontParameterStack} from "../lib/cloudfront-parameter-stack";
import {undefined} from "zod";
import {ShieldParameterStack} from "../lib/shield-parameter-stack";
import { ClamavScannerStack } from '../lib/clamav-scanner-stack';

// load .env file, shared with docker setup
// mainly for ECR repo and image tag information
Expand All @@ -42,6 +43,7 @@ const envProps: EnvProps = {
SOLR_IMAGE_TAG: parseEnv('SOLR_IMAGE_TAG'),
DATAPUSHER_IMAGE_TAG: parseEnv('DATAPUSHER_IMAGE_TAG'),
NGINX_IMAGE_TAG: parseEnv('NGINX_IMAGE_TAG'),
CLAMAV_IMAGE_TAG: parseEnv('CLAMAV_IMAGE_TAG'),
// 3rd party images
FUSEKI_IMAGE_TAG: parseEnv('FUSEKI_IMAGE_TAG'),
};
Expand Down Expand Up @@ -376,6 +378,22 @@ const monitoringStackBeta = new MonitoringStack(app, 'MonitoringStack-beta', {
},
environment: betaProps.environment,
});
const clamavScannerStackBeta = new ClamavScannerStack(app, 'ClamavScannerStack-beta', {
environment: betaProps.environment,
envProps: envProps,
env: {
account: betaProps.account,
region: betaProps.region,
},
clamavTaskDef: {
taskCpu: 512,
taskMem: 1024,
taskMinCapacity: 0,
taskMaxCapacity: 1,
},
cluster: clusterStackBeta.cluster,
topic: lambdaStackBeta.sendToZulipTopic
});

//
// prod env
Expand Down
12 changes: 12 additions & 0 deletions cdk/lib/clamav-scan-props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {EnvStackProps} from "./env-stack-props";
import {ICluster, ITaskDefinition} from 'aws-cdk-lib/aws-ecs';
import { ITopic } from "aws-cdk-lib/aws-sns";

export interface ClamavScanProps extends EnvStackProps {
cluster: ICluster;
task: ITaskDefinition;
snsTopic: ITopic;
subnetIds: String[]
}


55 changes: 55 additions & 0 deletions cdk/lib/clamav-scan.function.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {Handler, S3Event} from 'aws-lambda';
import {ECSClient,RunTaskCommand} from '@aws-sdk/client-ecs';

const { CLUSTER_ID, SNS_TOPIC_ARN, TASK_DEFINITION, SUBNET_IDS } = process.env;

export const handler: Handler = async (event: S3Event) => {
if(!CLUSTER_ID || !SNS_TOPIC_ARN || !TASK_DEFINITION || !SUBNET_IDS) {
return {
statusCode: 500,
body: 'Missing configuration values',
}
}

let { bucket, object } = event.Records[0].s3

let ecsClient = new ECSClient()
let subnetIds = SUBNET_IDS.split(',')
let runTaskCommand = new RunTaskCommand({
cluster: CLUSTER_ID,
count: 1,
launchType: 'FARGATE',
networkConfiguration: {
'awsvpcConfiguration': {
'subnets': subnetIds,
'assignPublicIp': 'DISABLED'
}
},
platformVersion: 'LATEST',
taskDefinition: TASK_DEFINITION,
overrides: {
'containerOverrides': [
{
'name': 'clamav-scanner',
'environment': [
{
'name': 'BUCKET_NAME',
'value': bucket.name
},
{
'name': 'OBJECT_KEY',
'value': object.key
},
{
'name': 'SNS_TOPIC_ARN',
'value': SNS_TOPIC_ARN
}
]
}
]
}
})
let response = ecsClient.send(runTaskCommand)

return response
}
22 changes: 22 additions & 0 deletions cdk/lib/clamav-scan.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { aws_lambda as lambda,
aws_lambda_nodejs as lambda_nodejs
} from 'aws-cdk-lib';
import {Construct} from "constructs";
import {ClamavScanProps} from "./clamav-scan-props";

export class ClamavScan extends Construct {
readonly lambda: lambda_nodejs.NodejsFunction;
constructor(scope: Construct, id: string, props: ClamavScanProps) {
super(scope, id);

this.lambda = new lambda_nodejs.NodejsFunction(this, 'function', {
environment: {
CLUSTER_ID: props.cluster.clusterArn,
SNS_TOPIC_ARN: props.snsTopic.topicArn,
TASK_DEFINITION: props.task.taskDefinitionArn,
SUBNET_IDS: props.subnetIds.join(",")
},
runtime: lambda.Runtime.NODEJS_20_X,
});
}
}
13 changes: 13 additions & 0 deletions cdk/lib/clamav-scanner-stack-props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {EnvStackProps} from "./env-stack-props";
import {EnvProps} from "./env-props";
import { EcsStackPropsTaskDef } from "./ecs-stack-props";
import {ICluster} from 'aws-cdk-lib/aws-ecs';
import { ITopic } from "aws-cdk-lib/aws-sns";

export interface ClamavScannerStackProps extends EnvStackProps {
envProps: EnvProps;
cluster: ICluster;
topic: ITopic;
clamavTaskDef: EcsStackPropsTaskDef,
}

75 changes: 75 additions & 0 deletions cdk/lib/clamav-scanner-stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Fn, Stack,
aws_ecr as ecr,
aws_ecs as ecs,
aws_iam as iam,
aws_ssm as ssm,
aws_s3 as s3,
aws_s3_notifications as s3n,
aws_lambda_nodejs as lambda_nodejs
} from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { ClamavScannerStackProps } from './clamav-scanner-stack-props';
import { parseEcrAccountId, parseEcrRegion } from './common-stack-funcs';
import { ClamavScan } from './clamav-scan';

export class ClamavScannerStack extends Stack {

readonly lambda: lambda_nodejs.NodejsFunction;

constructor(scope: Construct, id: string, props: ClamavScannerStackProps) {
super(scope, id, props);

const clamavRepo = ecr.Repository.fromRepositoryArn(this, 'clamavRepo', `arn:aws:ecr:${parseEcrRegion(props.envProps.REGISTRY)}:${parseEcrAccountId(props.envProps.REGISTRY)}:repository/${props.envProps.REPOSITORY}/clamav`);

const clamavTaskDef = new ecs.FargateTaskDefinition(this, 'clamavTaskDef', {
cpu: props.clamavTaskDef.taskCpu,
memoryLimitMiB: props.clamavTaskDef.taskMem,
});

// Allow clamavScan to manage S3 files
const pCkanCloudstorageContainerName = ssm.StringParameter.fromStringParameterAttributes(this, 'pCkanCloudstorageContainerName', {
parameterName: `/${props.environment}/opendata/ckan/cloudstorage_container_name`,
});
const bucketArn = `arn:aws:s3:::${pCkanCloudstorageContainerName.stringValue}`;

const clamavScanPolicyAllowCloudstorage = new iam.PolicyStatement({
actions: [
"s3:DeleteObject",
"s3:DeleteObjectTagging",
"s3:GetObject",
"s3:GetObjectTagging",
"s3:HeadBucket",
"s3:ListObjects",
"s3:PutObject",
"s3:PutObjectTagging",
],
resources: [
bucketArn,
`${bucketArn}/*`,
],
effect: iam.Effect.ALLOW,
});

clamavTaskDef.addToTaskRolePolicy(clamavScanPolicyAllowCloudstorage)

const clamavContainer = clamavTaskDef.addContainer('clamav', {
image: ecs.ContainerImage.fromEcrRepository(clamavRepo, props.envProps.CLAMAV_IMAGE_TAG),
});

const privateSubnetA = Fn.importValue('vpc-SubnetPrivateA')
const privateSubnetB = Fn.importValue('vpc-SubnetPrivateB')

const clamavScan = new ClamavScan(this, 'clamavScan', {
environment: props.environment,
cluster: props.cluster,
task: clamavTaskDef,
snsTopic: props.topic,
subnetIds: [privateSubnetA, privateSubnetB]
})

// S3 events to clamavScan lambda
const bucket = s3.Bucket.fromBucketArn(this, 'bucket', bucketArn);
bucket.addEventNotification(s3.EventType.OBJECT_CREATED, new s3n.LambdaDestination(clamavScan.lambda))
}
}

1 change: 1 addition & 0 deletions cdk/lib/env-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface EnvProps {
SOLR_IMAGE_TAG: string,
DATAPUSHER_IMAGE_TAG: string,
NGINX_IMAGE_TAG: string,
CLAMAV_IMAGE_TAG: string,
// 3rd party images
FUSEKI_IMAGE_TAG: string,
}
Expand Down
6 changes: 6 additions & 0 deletions cdk/lib/lambda-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import {LambdaStackProps} from "./lambda-stack-props";
import { SendToZulip } from "./send-to-zulip";
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";
import {Credentials} from "aws-cdk-lib/aws-rds";
import { Topic } from "aws-cdk-lib/aws-sns";
import { LambdaSubscription } from 'aws-cdk-lib/aws-sns-subscriptions';

export class LambdaStack extends Stack {
readonly sendToZulipLambda: NodejsFunction;
readonly sendToZulipTopic: Topic;
readonly datastoreJobsCredentials: Credentials;
readonly datastoreReadCredentials: Credentials;
readonly datastoreUserCredentials: Credentials;
Expand Down Expand Up @@ -35,5 +38,8 @@ export class LambdaStack extends Stack {
environment: props.environment,
});
this.sendToZulipLambda = sendToZulip.lambda;

this.sendToZulipTopic = new Topic(this, 'monitoringTopic');
this.sendToZulipTopic.addSubscription(new LambdaSubscription(this.sendToZulipLambda));
}
}
2 changes: 1 addition & 1 deletion cdk/lib/monitoring-stack.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Stack } from 'aws-cdk-lib';
import { ITopic, Topic } from "aws-cdk-lib/aws-sns";
import { MonitoringStackProps } from './monitoring-stack-props';

import * as events from 'aws-cdk-lib/aws-events';
import * as assertions from 'aws-cdk-lib/assertions';
import * as eventsTargets from 'aws-cdk-lib/aws-events-targets';
import * as logs from 'aws-cdk-lib/aws-logs';

Expand Down
Loading
Loading