tezu memo blog

日々行った作業をメモしていきます

OWASP ZAP DockerをCircleCIで実行

はじめに

CircleCIで初めてリモートDocker(setup_remote_docker)を使った。それなりにハマったので整理

IPがプライマリとリモートで異なる

テスト対象はdevelop環境なので、AWS WAFでIP制限をしている

既にCypressでE2Eテストを実行しているので、実施前後でWAFにIP追加(削除)の実績有りだったが、IPが異なるのでハマった
WAFのIP追加はcommandsで共通化しているので、parametersにリモート有無を追加

  • リモート $DOCKER_HOST
  • プライマリ コンテナ curl -s ifconfig.me
commands:
  waf-add-address:
    parameters:
      is-remote:
        type: boolean
        default: false
    steps:
      - run:
          name: waf-add-address
          command: |
            if << parameters.is-remote >> ; then
                IP=`echo $DOCKER_HOST | grep -Eo '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}'`
            else
                IP=`curl -s ifconfig.me`
            fi
            IPSETID=`aws waf list-ip-sets | jq -r '.IPSets[] | select(.Name == "allow-ip-address-circleci") | .IPSetId'`
            TOKEN=`aws waf get-change-token | jq -r .ChangeToken`
            aws waf update-ip-set --ip-set-id ${IPSETID} --change-token ${TOKEN} --updates "Action="INSERT",IPSetDescriptor={Type="IPV4",Value="${IP}/32"}"
jobs:
  e2e-test:
    steps:
      - waf-add-address
      # Cypress実行
      - waf-delete-address
  penetration-test:
    steps:
      - waf-add-address:
          is-remote: true
      # OWASP ZAP実行
      - waf-delete-address:
          is-remote: true

環境変数が取得出来ない

circleci.com

CircleCI は環境変数の設定時の挿入をサポートしませんが、BASH_ENV を使用して、現在のシェルに変数を設定することは可能です。 これは、PATH を変更するときや、他の変数を参照する環境変数を設定するときに便利です。

PHPUnitカバレッジ計測した結果をSlack通知する際に網羅率を含めて、リポートを見るトリガーにしている(100%だったらリポートを見る必要が無いので)
XMLから取得して環境変数に設定後に、Slack通知するcommandの引数に指定している

jobs:
  unit-test-and-report:
      - run:
          name: Run PHPUnit
          command: |
            mkdir -p reports
            vendor/bin/phpunit --dump-xdebug-filter xdebug-filter.php
            vendor/bin/phpunit --configuration phpunit.xml --prepend xdebug-filter.php --log-junit reports/junit/results.xml --coverage-html coverage --coverage-xml coverage -d memory_limit=-1
            echo "export COVERAGE_PERCENT=`xmllint --xpath "string(/*[local-name()='phpunit']/*[local-name()='project']/*[local-name()='directory']/*[local-name()='totals']/*[local-name()='lines']/@percent)" coverage/index.xml`" >> $BASH_ENV
      - notify-slack-for-coverage:
          url: https://$CIRCLE_BUILD_NUM-XXXXXXXXX-gh.circle-artifacts.com/0/coverage/index.html
          percent: $COVERAGE_PERCENT

同じ要領でリモートDockerのIPを取得して、環境変数に設定したがcommands側で取得出来ない。CIDR表記不正でエラー

上述の通り、$DOCKER_HOSTからIPを取得で解決

/32' at 'updates.1.member.iPSetDescriptor.value' failed to satisfy constraint: 
Member must satisfy regular expression pattern: .*\S.*
commands:
  waf-add-address:
    parameters:
      ip:
        type: string
    steps:
      - run:
          name: waf-add-address
          command: |
            IPSETID=`aws waf list-ip-sets | jq -r '.IPSets[] | select(.Name == "allow-ip-address-circleci") | .IPSetId'`
            TOKEN=`aws waf get-change-token | jq -r .ChangeToken`
            aws waf update-ip-set --ip-set-id ${IPSETID} --change-token ${TOKEN} --updates "Action="INSERT",IPSetDescriptor={Type="IPV4",Value="<< parameters.ip >>/32"}"

jobs:
  penetration-test:
    steps:
      - run:
          name: Set IP Address
          command: |
            echo "export IP_ADDR=`docker run -t owasp/zap2docker-stable curl inet-ip.info`" >> $BASH_ENV
      - waf-add-address:
          ip: $IP_ADDR

OWASP ZAPアラート有りでCircleCIのJobがFail

OWASP ZAPでアラートがある場合はstatusが0以外となりJobがFailする
後続Jobを実行することは when: always 指定で回避出来るが、CircleCI管理コンソールで実行結果を見た際に真っ赤になるので、exit 0としたい

FAIL-NEW: 0  FAIL-INPROG: 0  WARN-NEW: 1 WARN-INPROG: 0  INFO: 0 IGNORE: 0   PASS: 37

Exited with code exit status 2
CircleCI received exit code 2

www.zaproxy.org

-I do not return failure on warning

-Iを指定することで回避

コードを見て理解できました
https://github.com/zaproxy/zaproxy/blob/main/docker/zap-baseline.py#L443 https://github.com/zaproxy/zaproxy/blob/main/docker/zap-baseline.py#L646

shell: /bin/shset +e はダメでした

リモートDockerのボリュームをマウントできない

circleci.com

ジョブ空間からリモート Docker 内のコンテナにボリュームをマウントすること (およびその逆) はできません。

SSHで直接実行するとNGのケースでも動作したので少しハマった

NG

jobs:
  penetration-test:
      - run:
          name: Run OWASP ZIP
          working_directory: ./test-penetration
          command: |
            docker run -u root -v $(pwd):/zap/wrk/:rw -t owasp/zap2docker-stable zap-baseline.py -t "テスト対象のURL" -I -r owasp-report.html -J owasp-report.json
      - run:
          name: copy report remote to local
          working_directory: ./test-penetration
          command: |
             docker run -u root -v $(pwd):/zap/wrk/:rw -t owasp/zap2docker-stable cat /zap/wrk/project/test-penetration/owasp-report.html > owasp-report.html
             docker run -u root -v $(pwd):/zap/wrk/:rw -t owasp/zap2docker-stable cat /zap/wrk/project/test-penetration/owasp-report.json > owasp-report.json

OK

jobs:
  penetration-test:
      - run:
          name: Run OWASP ZIP
          working_directory: ./test-penetration
          command: |
            docker create -v /zap/wrk --name remote-owasp alpine:3.4 /bin/true
            docker run -u root --volumes-from remote-owasp owasp/zap2docker-stable zap-baseline.py -t "テスト対象のURL" -I -r owasp-report.html -J owasp-report.json
      - run:
          name: copy report remote to local
          working_directory: ./test-penetration
          command: |
            docker cp remote-owasp:/zap/wrk/owasp-report.html owasp-report.html
            docker cp remote-owasp:/zap/wrk/owasp-report.json owasp-report.json