stages: - validate - build - platform - test - normalize - report default: interruptible: true retry: max: 1 when: - runner_system_failure - stuck_or_timeout_failure variables: PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" ARTIFACT_ROOT: "$CI_PROJECT_DIR/artifacts" KUBE_NAMESPACE: "test-automation" ANSIBLE_IMAGE: "$CI_REGISTRY_IMAGE/ansible:$CI_COMMIT_SHA" DEMO_TARGET_IMAGE: "$CI_REGISTRY_IMAGE/demo-target:$CI_COMMIT_SHA" TARGET_ENVIRONMENT: value: "test" description: "GitLab environment scope used to select credentials" .python_job: image: python:3.13-alpine cache: key: python-ci-v1 paths: - .cache/pip/ before_script: - python3 -m pip install --disable-pip-version-check -r requirements-ci.txt validate:assets: extends: .python_job stage: validate script: - python3 scripts/parse-assets.py assets.yml --expected-environment "$TARGET_ENVIRONMENT" --dotenv artifacts/metadata.env --matrix artifacts/asset-matrix.json artifacts: expire_in: 30 days reports: dotenv: artifacts/metadata.env paths: - artifacts/asset-matrix.json validate:python: extends: .python_job stage: validate script: - python3 -m compileall -q scripts methodologies/ansible/scripts methodologies/zap/scripts - python3 methodologies/ansible/scripts/normalize.py methodologies/ansible/fixtures/sample-output.json artifacts/normalized/sample-ansible.json artifacts: expire_in: 7 days paths: - artifacts/normalized/sample-ansible.json .kaniko_build: stage: build image: name: gcr.io/kaniko-project/executor:v1.23.2-debug entrypoint: [""] before_script: - mkdir -p /kaniko/.docker - printf '{"auths":{"%s":{"username":"%s","password":"%s"}}}' "$CI_REGISTRY" "$CI_REGISTRY_USER" "$CI_REGISTRY_PASSWORD" > /kaniko/.docker/config.json build:ansible: extends: .kaniko_build script: - /kaniko/executor --context "$CI_PROJECT_DIR" --dockerfile "$CI_PROJECT_DIR/methodologies/ansible/Dockerfile" --destination "$ANSIBLE_IMAGE" rules: - if: '$RUN_DEMO == "true"' - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' changes: - methodologies/ansible/**/* build:demo-target: extends: .kaniko_build script: - /kaniko/executor --context "$CI_PROJECT_DIR" --dockerfile "$CI_PROJECT_DIR/targets/ubuntu-weak/Dockerfile" --destination "$DEMO_TARGET_IMAGE" rules: - if: '$RUN_DEMO == "true"' platform:verify: stage: platform image: alpine:3.20 needs: - validate:assets script: - test -d /cache/tools - df -h /cache/tools resource_group: test-automation-platform rules: - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' when: manual - when: never # Tool jobs are introduced behind explicit opt-in variables. Their Kubernetes # Job templates and adapters are added as each tool contract is implemented. test:ansible: stage: test image: name: "$CI_REGISTRY_IMAGE/ansible:latest" entrypoint: [""] needs: - validate:assets environment: name: "$TARGET_ENVIRONMENT" action: verify script: - | SECRETS_DIR="$CI_PROJECT_DIR/.run/secrets" python3 methodologies/ansible/scripts/build-secrets.py --out-dir "$SECRETS_DIR" export ANSIBLE_VARS_FILE="$SECRETS_DIR/ansible-vars.yml" if [ -f "$SECRETS_DIR/ansible-private-key" ]; then export ANSIBLE_PRIVATE_KEY_FILE="$SECRETS_DIR/ansible-private-key" fi bash methodologies/ansible/run.sh --limit "${ANSIBLE_LIMIT:-all}" artifacts: when: always expire_in: 90 days paths: - artifacts/raw/ansible/ - artifacts/normalized/ - artifacts/rendered/ rules: - if: '$RUN_ANSIBLE == "true"' - when: never test:zap: stage: test image: name: zaproxy/zap-stable:latest entrypoint: [""] needs: - validate:assets environment: name: "$TARGET_ENVIRONMENT" action: verify script: - test -n "${ZAP_TARGET_URL:-}" || { echo "ZAP_TARGET_URL is required" >&2; exit 1; } - NORMALIZE_ZAP=false bash methodologies/zap/run.sh "$ZAP_TARGET_URL" artifacts: when: always expire_in: 90 days paths: - artifacts/raw/zaproxy/ rules: - if: '$RUN_ZAP == "true"' - when: never demo:ansible: stage: test image: name: "$ANSIBLE_IMAGE" entrypoint: [""] services: - name: "$DEMO_TARGET_IMAGE" alias: demo-target needs: - build:ansible - build:demo-target variables: DEMO_SSH_PASSWORD: "DemoPassword1!" INVENTORY: "$CI_PROJECT_DIR/targets/ubuntu-weak/assets.yml" PLAYBOOK: "$CI_PROJECT_DIR/methodologies/ansible/playbooks/demo_target.yml" LIMIT: "linux_vms" TEST_PROJECT_ID: "demo-ubuntu-weak" TEST_PROJECT_NAME: "Ubuntu Weak Target Demonstration" TEST_ENVIRONMENT: "test" TEST_CUSTOMER: "Internal" TEST_LOCATION: "testserv" script: - | python3 <<'PY' import socket import time for _ in range(60): try: with socket.create_connection(("demo-target", 22), 2): break except OSError: time.sleep(2) else: raise SystemExit("SSH target did not become ready") PY - bash methodologies/ansible/run.sh artifacts: when: always expire_in: 30 days paths: - artifacts/raw/ansible/ - artifacts/normalized/ rules: - if: '$RUN_DEMO == "true"' demo:zap: stage: test image: name: zaproxy/zap-stable:latest entrypoint: [""] services: - name: "$DEMO_TARGET_IMAGE" alias: demo-target needs: - build:demo-target variables: NORMALIZE_ZAP: "false" script: - | python3 <<'PY' import socket import time for _ in range(60): try: with socket.create_connection(("demo-target", 443), 2): break except OSError: time.sleep(2) else: raise SystemExit("HTTPS target did not become ready") PY - bash methodologies/zap/run.sh https://demo-target artifacts: when: always expire_in: 30 days paths: - artifacts/raw/zaproxy/ rules: - if: '$RUN_DEMO == "true"' normalize:demo-zap: extends: .python_job stage: normalize needs: - job: demo:zap artifacts: true variables: TEST_PROJECT_ID: "demo-ubuntu-weak" TEST_PROJECT_NAME: "Ubuntu Weak Target Demonstration" TEST_ENVIRONMENT: "test" TEST_CUSTOMER: "Internal" TEST_LOCATION: "testserv" script: - python3 methodologies/zap/scripts/normalize.py artifacts/raw/zaproxy/zap.json artifacts/raw/zaproxy/tls.json artifacts/normalized/zaproxy-demo-web.json --target https://demo-target artifacts: expire_in: 30 days paths: - artifacts/normalized/zaproxy-demo-web.json rules: - if: '$RUN_DEMO == "true"' report:demo: extends: .python_job stage: report needs: - job: demo:ansible artifacts: true - job: normalize:demo-zap artifacts: true script: - ANSIBLE_REPORT="$(find artifacts/normalized -name 'ansible-*.json' -print -quit)" - test -n "$ANSIBLE_REPORT" - python3 scripts/aggregate-reports.py "$ANSIBLE_REPORT" artifacts/normalized/zaproxy-demo-web.json --output artifacts/normalized/combined-demo.json - python3 scripts/render-normalized.py artifacts/normalized/combined-demo.json --output-dir artifacts/rendered artifacts: when: always expire_in: 90 days paths: - artifacts/normalized/combined-demo.json - artifacts/rendered/combined-project-scope.md - artifacts/rendered/combined-project-scope.html - artifacts/rendered/combined-project-scope.pdf rules: - if: '$RUN_DEMO == "true"' report:sample: extends: .python_job stage: report needs: - validate:assets - validate:python script: - python3 scripts/render-normalized.py artifacts/normalized/sample-ansible.json --output-dir artifacts/rendered artifacts: when: always expire_in: 90 days paths: - artifacts/normalized/ - artifacts/rendered/