diff --git a/.github/workflows/build_and_push.yml b/.github/workflows/build_and_push.yml index adab4067..de1b965f 100644 --- a/.github/workflows/build_and_push.yml +++ b/.github/workflows/build_and_push.yml @@ -25,6 +25,7 @@ jobs: api: 'api/**' scanners/axe-core: 'scanners/axe-core/**' scanners/owasp-zap: 'scanners/owasp-zap/**' + runners/owasp-zap: 'runners/owasp-zap/**' build-push-and-deploy: if: ${{ needs.changes.outputs.images != '[]' }} @@ -68,6 +69,7 @@ jobs: run: docker logout ${{ steps.login-ecr.outputs.registry }} - name: Deploy lambda + if: ${{ matrix.image == 'api' }} || ${{ contains(matrix.image, 'scanners' }} run: | FNAME = $(sed 's/\//-/g' <<< "${{ matrix.image }}") aws lambda update-function-code \ @@ -78,4 +80,4 @@ jobs: if: ${{ matrix.image == 'api' }} run: | source .github/workflows/scripts/migrate.sh - migrate + migrate \ No newline at end of file diff --git a/.github/workflows/ci_build_continers.yml b/.github/workflows/ci_build_continers.yml index eec0f394..699ecaaa 100644 --- a/.github/workflows/ci_build_continers.yml +++ b/.github/workflows/ci_build_continers.yml @@ -24,6 +24,7 @@ jobs: api: 'api/**' scanners/axe-core: 'scanners/axe-core/**' scanners/owasp-zap: 'scanners/owasp-zap/**' + runners/owasp-zap: 'runners/owasp-zap/**' build: if: ${{ needs.changes.outputs.images != '[]' }} diff --git a/runners/owasp-zap/Dockerfile b/runners/owasp-zap/Dockerfile new file mode 100644 index 00000000..b8a25bc1 --- /dev/null +++ b/runners/owasp-zap/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.8-alpine + +WORKDIR /scan + +RUN apk update \ + && apk upgrade \ + && apk add --update curl bash jq + +RUN pip install --upgrade zapcli awscli +COPY entrypoint.sh /entrypoint.sh + +# Launch OWASP scan +ENTRYPOINT ["/entrypoint.sh"] diff --git a/runners/owasp-zap/entrypoint.sh b/runners/owasp-zap/entrypoint.sh new file mode 100644 index 00000000..eb88195a --- /dev/null +++ b/runners/owasp-zap/entrypoint.sh @@ -0,0 +1,58 @@ +#!/bin/sh -l + +#Check if running locally or in ECS +if [[ -z "${ECS_CONTAINER_METADATA_URI}" ]]; then + # ECS environment variable not detected so use local docker networking + HOST_IP="172.17.0.1" +else + # Use ECS host IP + HOST_IP=$(curl -s "$ECS_CONTAINER_METADATA_URI" | jq -r '.Networks[].IPv4Addresses[0]') +fi + +echo "Host ip: $HOST_IP and Port:${ZAP_PORT}" + +# Wait for ZAP proxy to init +CHECKS=0 +while ! eval "$(curl -sSf "$HOST_IP":"${ZAP_PORT}" > /dev/null 2>&1)" +do + echo "Waiting for proxy to start..." + sleep 3 + CHECKS=$((CHECKS+1)) + if [ $CHECKS -gt 100 ] + then + echo "Proxy failed to start within 5 minutes, exiting" + exit 1 + fi +done +sleep 3 + +date=$(date +\"%Y-%m-%dT%H:%M:%S:%N\") +fDate=$(echo "$date" | sed -e 's/[^A-Za-z0-9._-]/_/g') +# Convert URL into a valid filename for the report +FILENAME=$(echo "$SCAN_URL" | sed -e 's/[^A-Za-z0-9._-]/_/g')-$fDate + +zap-cli --port "${ZAP_PORT}" --zap-url "http://$HOST_IP" exclude "^.*/(logout|log-out|signout|sign-out|deconnecter)/?$" +zap-cli --port "${ZAP_PORT}" --zap-url "http://$HOST_IP" exclude "^.*\.(css|gif|jpe?g|tiff|png|webp|bmp|ico|svg|js|jsx|pdf)$" +zap-cli --port "${ZAP_PORT}" --zap-url "http://$HOST_IP" open-url "${SCAN_URL}" +zap-cli --port "${ZAP_PORT}" --zap-url "http://$HOST_IP" spider "${SCAN_URL}" +zap-cli --port "${ZAP_PORT}" --zap-url "http://$HOST_IP" ajax-spider "${SCAN_URL}" + +# Timeout scan after 1 hour to prevent running indefinately if the OWASP ZAP container crashes +timeout 1h zap-cli --port "${ZAP_PORT}" --zap-url "http://$HOST_IP" active-scan --recursive "${SCAN_URL}" + +high_alerts=$( curl "http://$HOST_IP:${ZAP_PORT}/JSON/alert/view/alertsSummary/?baseurl=${SCAN_URL}" | jq -r '.alertsSummary.High') + +echo "high alerts are $high_alerts" + +curl "http://$HOST_IP:${ZAP_PORT}/OTHER/core/other/jsonreport/" | jq . > zap-scan-results.json + +if [[ -z "${PUSH_TO_SECURITYHUB}" ]]; then + IMPORTVULTOSECURITYHUB=false +else + IMPORTVULTOSECURITYHUB=true +fi + +jq "{ \"messageType\": \"ScanReport\", \"reportType\": \"OWASP-Zap\", \"createdAt\": $(date +\"%Y-%m-%dT%H:%M:%S\"),\"importToSecurityhub\": \"$IMPORTVULTOSECURITYHUB\",\"scanUrl\": \"$SCAN_URL\",\"s3Bucket\": \"${S3_BUCKET}\",\"key\": \"Reports/$FILENAME.xml\", \"report\": . }" zap-scan-results.json > payload.json + +aws s3 cp payload.json s3://"${S3_BUCKET}"/Reports/"$FILENAME".json + diff --git a/terragrunt/aws/scanners/owasp-zap/ecr.tf b/terragrunt/aws/scanners/owasp-zap/ecr.tf index b4668748..d4d72509 100644 --- a/terragrunt/aws/scanners/owasp-zap/ecr.tf +++ b/terragrunt/aws/scanners/owasp-zap/ecr.tf @@ -4,6 +4,17 @@ resource "aws_ecr_repository" "scanners-owasp-zap" { name = "${var.product_name}/scanners/owasp-zap" image_tag_mutability = "MUTABLE" + image_scanning_configuration { + scan_on_push = true + } +} + +resource "aws_ecr_repository" "runners-owasp-zap" { + # checkov:skip=CKV_AWS_51:The :latest tag is used in Staging + + name = "${var.product_name}/runners/owasp-zap" + image_tag_mutability = "MUTABLE" + image_scanning_configuration { scan_on_push = true } diff --git a/terragrunt/aws/scanners/owasp-zap/ecs.tf b/terragrunt/aws/scanners/owasp-zap/ecs.tf index af7c19b7..1e28767d 100644 --- a/terragrunt/aws/scanners/owasp-zap/ecs.tf +++ b/terragrunt/aws/scanners/owasp-zap/ecs.tf @@ -8,8 +8,8 @@ resource "aws_ecs_cluster" "scanning_tools" { } -resource "aws_ecs_task_definition" "zap_runner" { - family = "zap_runner" +resource "aws_ecs_task_definition" "runners-owasp-zap" { + family = "runners-owasp-zap" cpu = 2048 memory = 16384 network_mode = "awsvpc" @@ -26,19 +26,19 @@ resource "aws_ecs_task_definition" "zap_runner" { resource "aws_cloudwatch_log_group" "log" { # checkov:skip=CKV_AWS_158:Encryption using default CloudWatch service key is acceptable - name = "/aws/ecs/zap_runner_ecs" + name = "/aws/ecs/runners_owasp_zap_ecs" retention_in_days = 14 } data "template_file" "scanning_tools" { template = file("container-definitions/zap_runner.json") vars = { - image = "${aws_ecr_repository.scanners-owasp-zap.repository_url}:latest" + image = "${aws_ecr_repository.runners-owasp-zap.repository_url}:latest" awslogs-region = "ca-central-1" - awslogs-stream-prefix = "ecs-zap-runner" + awslogs-stream-prefix = "ecs-runners-owasp-zap" s3_name = aws_s3_bucket.owasp-zap-report-data.bucket awslogs-group = aws_cloudwatch_log_group.log.name - name = "zap_runner" + name = "runners-owasp-zap" } } diff --git a/terragrunt/aws/scanners/owasp-zap/iam.tf b/terragrunt/aws/scanners/owasp-zap/iam.tf index bcac4b00..58e3b34c 100644 --- a/terragrunt/aws/scanners/owasp-zap/iam.tf +++ b/terragrunt/aws/scanners/owasp-zap/iam.tf @@ -66,7 +66,7 @@ data "aws_iam_policy_document" "zap_runner_policies" { ] resources = [ - aws_ecs_task_definition.zap_runner.arn + aws_ecs_task_definition.runners-owasp-zap.arn ] } diff --git a/terragrunt/aws/scanners/owasp-zap/lambda.tf b/terragrunt/aws/scanners/owasp-zap/lambda.tf index a15a62cb..e75b7b36 100644 --- a/terragrunt/aws/scanners/owasp-zap/lambda.tf +++ b/terragrunt/aws/scanners/owasp-zap/lambda.tf @@ -15,7 +15,7 @@ resource "aws_lambda_function" "scanners-owasp-zap" { variables = { REPORT_DATA_BUCKET = aws_s3_bucket.owasp-zap-report-data.bucket CLUSTER = aws_ecs_cluster.scanning_tools.arn - TASK_DEF_ARN = aws_ecs_task_definition.zap_runner.arn + TASK_DEF_ARN = aws_ecs_task_definition.runners-owasp-zap.arn PRIVATE_SUBNETS = join(",", var.private_subnet_ids) SECURITY_GROUP = aws_security_group.security_tools_web_scanning.id }