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 は環境変数の設定時の挿入をサポートしませんが、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
-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/sh
や set +e
はダメでした
リモートDockerのボリュームをマウントできない
ジョブ空間からリモート 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