From ce5c59e491a2b0e77b1156ab939ffa36eb5f7e17 Mon Sep 17 00:00:00 2001 From: Mohamed Nur Date: Mon, 16 Aug 2021 16:45:55 -0400 Subject: [PATCH 1/7] feat: ecs container that orchestrates zap scans --- .github/workflows/build_and_push.yml | 5 +- task-runners/owasp-zap/Dockerfile | 13 ++++++ task-runners/owasp-zap/entrypoint.sh | 51 +++++++++++++++++++++ terragrunt/aws/scanners/owasp-zap/ecr.tf | 11 +++++ terragrunt/aws/scanners/owasp-zap/ecs.tf | 12 ++--- terragrunt/aws/scanners/owasp-zap/iam.tf | 2 +- terragrunt/aws/scanners/owasp-zap/lambda.tf | 2 +- 7 files changed, 87 insertions(+), 9 deletions(-) create mode 100644 task-runners/owasp-zap/Dockerfile create mode 100644 task-runners/owasp-zap/entrypoint.sh diff --git a/.github/workflows/build_and_push.yml b/.github/workflows/build_and_push.yml index adab4067..00d813ab 100644 --- a/.github/workflows/build_and_push.yml +++ b/.github/workflows/build_and_push.yml @@ -14,6 +14,7 @@ jobs: runs-on: ubuntu-latest outputs: images: ${{ steps.filter.outputs.changes }} + ecs-images: ${{ steps.filter-ecs.outputs.changes }} steps: - name: Checkout uses: actions/checkout@v2 @@ -25,6 +26,7 @@ jobs: api: 'api/**' scanners/axe-core: 'scanners/axe-core/**' scanners/owasp-zap: 'scanners/owasp-zap/**' + runners-owasp-zap: 'task-runners/owasp-zap/**' build-push-and-deploy: if: ${{ needs.changes.outputs.images != '[]' }} @@ -68,6 +70,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 +81,4 @@ jobs: if: ${{ matrix.image == 'api' }} run: | source .github/workflows/scripts/migrate.sh - migrate + migrate \ No newline at end of file diff --git a/task-runners/owasp-zap/Dockerfile b/task-runners/owasp-zap/Dockerfile new file mode 100644 index 00000000..b8a25bc1 --- /dev/null +++ b/task-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/task-runners/owasp-zap/entrypoint.sh b/task-runners/owasp-zap/entrypoint.sh new file mode 100644 index 00000000..c9ae6b54 --- /dev/null +++ b/task-runners/owasp-zap/entrypoint.sh @@ -0,0 +1,51 @@ +#!/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 +while ! eval "$(curl -sSf "$HOST_IP":"${ZAP_PORT}" > /dev/null 2>&1)" +do + echo "Waiting for proxy to start..." + sleep 3 +done +sleep 3 + +date=$(date +\"%Y-%m-%dT%H:%M:%S\") +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).*$" +zap-cli --port "${ZAP_PORT}" --zap-url "http://$HOST_IP" exclude "^.*\.(css|gif|jpe?g|tiff|png|webp|bmp|ico|svg|js|jsx)$" +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\"),\"import_to_securityhub\": \"$IMPORTVULTOSECURITYHUB\",\"scan_url\": \"$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 } From 57d828cdecea6e30541f7ba1313b2a72b571efc7 Mon Sep 17 00:00:00 2001 From: Mohamed Nur Date: Mon, 16 Aug 2021 16:52:07 -0400 Subject: [PATCH 2/7] chore: cleanup zap shell script --- .github/workflows/build_and_push.yml | 2 +- .github/workflows/ci_build_continers.yml | 1 + {task-runners => runners}/owasp-zap/Dockerfile | 0 {task-runners => runners}/owasp-zap/entrypoint.sh | 2 +- 4 files changed, 3 insertions(+), 2 deletions(-) rename {task-runners => runners}/owasp-zap/Dockerfile (100%) rename {task-runners => runners}/owasp-zap/entrypoint.sh (88%) diff --git a/.github/workflows/build_and_push.yml b/.github/workflows/build_and_push.yml index 00d813ab..24e71546 100644 --- a/.github/workflows/build_and_push.yml +++ b/.github/workflows/build_and_push.yml @@ -26,7 +26,7 @@ jobs: api: 'api/**' scanners/axe-core: 'scanners/axe-core/**' scanners/owasp-zap: 'scanners/owasp-zap/**' - runners-owasp-zap: 'task-runners/owasp-zap/**' + runners/owasp-zap: 'runners/owasp-zap/**' build-push-and-deploy: if: ${{ needs.changes.outputs.images != '[]' }} 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/task-runners/owasp-zap/Dockerfile b/runners/owasp-zap/Dockerfile similarity index 100% rename from task-runners/owasp-zap/Dockerfile rename to runners/owasp-zap/Dockerfile diff --git a/task-runners/owasp-zap/entrypoint.sh b/runners/owasp-zap/entrypoint.sh similarity index 88% rename from task-runners/owasp-zap/entrypoint.sh rename to runners/owasp-zap/entrypoint.sh index c9ae6b54..98a1f8c9 100644 --- a/task-runners/owasp-zap/entrypoint.sh +++ b/runners/owasp-zap/entrypoint.sh @@ -45,7 +45,7 @@ else IMPORTVULTOSECURITYHUB=true fi -jq "{ \"messageType\": \"ScanReport\", \"reportType\": \"OWASP-Zap\", \"createdAt\": $(date +\"%Y-%m-%dT%H:%M:%S\"),\"import_to_securityhub\": \"$IMPORTVULTOSECURITYHUB\",\"scan_url\": \"$SCAN_URL\",\"s3bucket\": \"${S3_BUCKET}\",\"key\": \"Reports/$FILENAME.xml\", \"report\": . }" zap-scan-results.json > payload.json +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 From 43860fbc9093eae9b835d3a33d946dc169a67a31 Mon Sep 17 00:00:00 2001 From: Mohamed Nur Date: Tue, 17 Aug 2021 10:22:10 -0400 Subject: [PATCH 3/7] feat: exit with error if zap proxy isn't ready within 5 minutes --- runners/owasp-zap/entrypoint.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/runners/owasp-zap/entrypoint.sh b/runners/owasp-zap/entrypoint.sh index 98a1f8c9..95464d10 100644 --- a/runners/owasp-zap/entrypoint.sh +++ b/runners/owasp-zap/entrypoint.sh @@ -12,10 +12,17 @@ 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 From 257470ec7204cd93685dcc65be7bb3c9621bbd08 Mon Sep 17 00:00:00 2001 From: Mohamed Nur Date: Tue, 17 Aug 2021 10:30:33 -0400 Subject: [PATCH 4/7] chore: exclude pdf when running zap scan and updated filename generation to include nanoseconds --- runners/owasp-zap/entrypoint.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runners/owasp-zap/entrypoint.sh b/runners/owasp-zap/entrypoint.sh index 95464d10..28e990d6 100644 --- a/runners/owasp-zap/entrypoint.sh +++ b/runners/owasp-zap/entrypoint.sh @@ -26,13 +26,13 @@ do done sleep 3 -date=$(date +\"%Y-%m-%dT%H:%M:%S\") +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).*$" -zap-cli --port "${ZAP_PORT}" --zap-url "http://$HOST_IP" exclude "^.*\.(css|gif|jpe?g|tiff|png|webp|bmp|ico|svg|js|jsx)$" +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}" From 6abf32ebc57f42ce72e1cd492f75a86d603bec03 Mon Sep 17 00:00:00 2001 From: Mohamed Nur Date: Tue, 17 Aug 2021 10:32:06 -0400 Subject: [PATCH 5/7] chore: added french logout text to scan exclusion list --- runners/owasp-zap/entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runners/owasp-zap/entrypoint.sh b/runners/owasp-zap/entrypoint.sh index 28e990d6..f81c78b0 100644 --- a/runners/owasp-zap/entrypoint.sh +++ b/runners/owasp-zap/entrypoint.sh @@ -31,7 +31,7 @@ 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).*$" +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}" From fd0e9e3a27076f54fa622ec5acce8b3fb4ba0022 Mon Sep 17 00:00:00 2001 From: Mohamed Nur Date: Tue, 17 Aug 2021 11:31:22 -0400 Subject: [PATCH 6/7] chore: tighter logout exclusion regex --- runners/owasp-zap/entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runners/owasp-zap/entrypoint.sh b/runners/owasp-zap/entrypoint.sh index f81c78b0..eb88195a 100644 --- a/runners/owasp-zap/entrypoint.sh +++ b/runners/owasp-zap/entrypoint.sh @@ -31,7 +31,7 @@ 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 "^.*/(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}" From f63ed24d2a14c86185d15209c0f65b7788b6a83e Mon Sep 17 00:00:00 2001 From: Mohamed Nur Date: Tue, 17 Aug 2021 13:11:10 -0400 Subject: [PATCH 7/7] chore: removed unused github action output --- .github/workflows/build_and_push.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build_and_push.yml b/.github/workflows/build_and_push.yml index 24e71546..de1b965f 100644 --- a/.github/workflows/build_and_push.yml +++ b/.github/workflows/build_and_push.yml @@ -14,7 +14,6 @@ jobs: runs-on: ubuntu-latest outputs: images: ${{ steps.filter.outputs.changes }} - ecs-images: ${{ steps.filter-ecs.outputs.changes }} steps: - name: Checkout uses: actions/checkout@v2