├── .dockerignore ├── .circleci ├── Dockerfile ├── node_critical_fail │ ├── app │ │ ├── .aws │ │ │ └── credentials │ │ └── server.js │ └── Dockerfile ├── node_critical_pass │ ├── Dockerfile │ └── app │ │ └── server.js ├── config.yml └── .anchore │ └── policy_bundle.json ├── .gitignore ├── .github ├── workflows │ ├── thank-you.png │ └── first_issue.yml ├── issue_template.md └── pull_request_template.md ├── examples ├── codeship │ └── inline_scan │ │ ├── env.encrypted │ │ ├── codeship-services.yml │ │ └── codeship-steps.yml ├── aws-codebuild │ └── inline_scan │ │ └── buildspec.yml ├── travisci │ └── inline_scan │ │ └── .travis.yml ├── circleci │ └── inline_scan │ │ └── config.yml ├── jenkins │ └── pipeline │ │ ├── inline_scan │ │ └── Jenkinsfile │ │ └── direct_api │ │ └── Jenkinsfile ├── codefresh │ └── inline_scan │ │ └── codefresh.yml └── gitlab │ └── inline_scan │ └── .gitlab-ci.yml ├── CONTRIBUTING.rst ├── scripts ├── docker-entrypoint.sh ├── image_analysis.sh ├── image_vuln_scan.sh ├── anchore_ci_tools.py ├── build.sh └── inline_scan ├── conf └── stateless_ci_config.yaml ├── Dockerfile ├── README.md └── LICENSE /.dockerignore: -------------------------------------------------------------------------------- 1 | .circleci 2 | .vscode 3 | .gitignore 4 | .git 5 | test/ -------------------------------------------------------------------------------- /.circleci/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker:latest 2 | RUN mkdir /app 3 | WORKDIR /app 4 | COPY . . 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs/ 3 | *.log 4 | *.DS_Store 5 | *.pyc 6 | .python-version 7 | .vscode/ 8 | test/ -------------------------------------------------------------------------------- /.github/workflows/thank-you.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anchore/ci-tools/HEAD/.github/workflows/thank-you.png -------------------------------------------------------------------------------- /examples/codeship/inline_scan/env.encrypted: -------------------------------------------------------------------------------- 1 | codeship:v2 2 | bGPSocnmBH6RJYlkBOk4TFgXaBrMc/wkAseFXfZIoEG6K2CgCQTI8M6NdBP/1fGhI8AipGR1iJZ9mxxy7M4+5fRWzsnIUPPQaEvRpelwYrxVCymz1nhx+KGFq68AUssFeg== -------------------------------------------------------------------------------- /examples/codeship/inline_scan/codeship-services.yml: -------------------------------------------------------------------------------- 1 | anchore: 2 | add_docker: true 3 | image: docker:stable-git 4 | environment: 5 | IMAGE_NAME: anchore/anchore-ci-demo 6 | IMAGE_TAG: codeship 7 | encrypted_env_file: env.encrypted -------------------------------------------------------------------------------- /.circleci/node_critical_fail/app/.aws/credentials: -------------------------------------------------------------------------------- 1 | [default] 2 | aws_access_key_id = 12345678901234567890 3 | aws_secret_access_key = 1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b 4 | [kuber] 5 | aws_access_key_id = 12345678901234567890 6 | aws_secret_access_key = 1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | The problem 2 | --- 3 | 4 | 5 | 6 | Environment 7 | --- 8 | 9 | 10 | 11 | Details 12 | --- 13 | 14 | 15 | 16 | Actual Behaviour 17 | --- 18 | 19 | 20 | 21 | Expected Behaviour 22 | --- 23 | 24 | 25 | 26 | How do you reproduce the error? 27 | --- 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /examples/aws-codebuild/inline_scan/buildspec.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | phases: 4 | build: 5 | commands: 6 | - docker build -t $IMAGE_NAME:$IMAGE_TAG . 7 | 8 | post_build: 9 | commands: 10 | - curl -s https://ci-tools.anchore.io/inline_scan-v0.3.3 | bash -s -- ${IMAGE_NAME}:$IMAGE_TAG 11 | - echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin 12 | - docker push $IMAGE_NAME:$IMAGE_TAG -------------------------------------------------------------------------------- /.circleci/node_critical_pass/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | 3 | RUN mkdir -p /home/node/ && apt-get update && apt-get -y upgrade && apt-get -y install curl 4 | COPY ./app/ /home/node/app/ 5 | 6 | # DEV NOTE: remember to re-enable healthcheck and remove debugging port 22 before final push! 7 | 8 | HEALTHCHECK CMD curl --fail http://localhost:8081/ || exit 1 9 | EXPOSE 8081 10 | 11 | USER node 12 | CMD node /home/node/app/server.js 13 | -------------------------------------------------------------------------------- /.circleci/node_critical_fail/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node@sha256:899febf5a7af3bec94e9a67244087db218a42d55e748d9504b00019705bd3a18 2 | 3 | RUN mkdir -p /home/node/ && apt-get update && apt-get -y install curl 4 | COPY ./app/ /home/node/app/ 5 | 6 | # DEV NOTE: remember to re-enable healthcheck and remove debugging port 22 before final push! 7 | 8 | # HEALTHCHECK CMD curl --fail http://localhost:8081/ || exit 1 9 | EXPOSE 8081 22 10 | 11 | CMD node /home/node/app/server.js 12 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Explain why this change is being made. 2 | --- 3 | 4 | 5 | 6 | Explain how this is accomplished. 7 | --- 8 | 9 | 10 | 11 | Merge/Deploy Checklist 12 | ---- 13 | 14 | - [ ] It has been reviewed for code quality. 15 | - [ ] It has been visually reviewed and accepted. 16 | 17 | How do you manually test this? 18 | ---- 19 | 20 | 21 | 22 | Screenshots 23 | ---- 24 | 25 | | Desktop | Mobile | 26 | |--|--| 27 | | image_placeholder | image_placeholder | 28 | -------------------------------------------------------------------------------- /.circleci/node_critical_fail/app/server.js: -------------------------------------------------------------------------------- 1 | var http = require("http"); 2 | 3 | http.createServer(function (request, response) { 4 | // Send the HTTP header 5 | // HTTP Status: 200 : OK 6 | // Content Type: text/plain 7 | response.writeHead(200, {'Content-Type': 'text/plain'}); 8 | 9 | // Send the response body as "Hello World" 10 | response.end('Hello World\n'); 11 | }).listen(8081); 12 | 13 | // Console will print the message 14 | console.log('Server running at http://127.0.0.1:8081/'); 15 | -------------------------------------------------------------------------------- /.circleci/node_critical_pass/app/server.js: -------------------------------------------------------------------------------- 1 | var http = require("http"); 2 | 3 | http.createServer(function (request, response) { 4 | // Send the HTTP header 5 | // HTTP Status: 200 : OK 6 | // Content Type: text/plain 7 | response.writeHead(200, {'Content-Type': 'text/plain'}); 8 | 9 | // Send the response body as "Hello World" 10 | response.end('Hello World\n'); 11 | }).listen(8081); 12 | 13 | // Console will print the message 14 | console.log('Server running at http://127.0.0.1:8081/'); 15 | -------------------------------------------------------------------------------- /examples/travisci/inline_scan/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | services: 4 | - docker 5 | 6 | env: 7 | - IMAGE_NAME="anchore/anchore-ci-demo" IMAGE_TAG="travisci" 8 | 9 | script: 10 | - docker build -t "${IMAGE_NAME}:ci" . 11 | - curl -s https://ci-tools.anchore.io/inline_scan-v0.3.3 | bash -s -- "${IMAGE_NAME}:ci" 12 | - echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin 13 | - docker tag "${IMAGE_NAME}:ci" "${IMAGE_NAME}:${IMAGE_TAG}" 14 | - docker push "${IMAGE_NAME}:${IMAGE_TAG}" -------------------------------------------------------------------------------- /examples/codeship/inline_scan/codeship-steps.yml: -------------------------------------------------------------------------------- 1 | - name: build-scan 2 | service: anchore 3 | command: sh -c 'apk add bash curl && 4 | mkdir -p /build && 5 | cd /build && 6 | git clone https://github.com/anchore/ci-tools.git . && 7 | docker build -t "${IMAGE_NAME}:ci" . && 8 | curl -s https://ci-tools.anchore.io/inline_scan-v0.3.3 | bash -s -- -f -b ./.anchore_policy.json "${IMAGE_NAME}:ci" && 9 | echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin && 10 | docker tag "${IMAGE_NAME}:ci" "${IMAGE_NAME}:${IMAGE_TAG}" && 11 | docker push "${IMAGE_NAME}:${IMAGE_TAG}"' -------------------------------------------------------------------------------- /.github/workflows/first_issue.yml: -------------------------------------------------------------------------------- 1 | name: Greetings 2 | 3 | on: [issues] 4 | 5 | jobs: 6 | greeting: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/first-interaction@v1 10 | with: 11 | repo-token: ${{ secrets.GITHUB_TOKEN }} 12 | issue-message: |- 13 | Hello ${{ github.actor }}, 14 | 15 | Thank you for being part of the Anchore community! 16 | 17 | [Thank you](https://github.com/anchore/ci-tools/raw/master/.github/workflows/thank-you.png) 18 | 19 | An Anchorite will look at your issue soon. We also have great [documentation](https://docs.anchore.com/current/), including [FAQs](https://docs.anchore.com/current/docs/faq/) and [release notes](https://docs.anchore.com/current/docs/releasenotes/). 20 | 21 | You can also find our [contribution guidelines here](https://github.com/anchore/ci-tools/blob/master/CONTRIBUTING.rst). Thank you again! 22 | -------------------------------------------------------------------------------- /examples/circleci/inline_scan/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | jobs: 3 | build_scan_image: 4 | docker: 5 | - image: docker:stable 6 | environment: 7 | IMAGE_NAME: anchore/anchore-ci-demo 8 | IMAGE_TAG: circleci 9 | steps: 10 | - checkout 11 | - setup_remote_docker 12 | - run: 13 | name: Build image 14 | command: docker build -t "${IMAGE_NAME}:ci" . 15 | - run: 16 | name: Scan image 17 | command: | 18 | apk add curl bash 19 | curl -s https://ci-tools.anchore.io/inline_scan-v0.3.3 | bash -s -- -r "${IMAGE_NAME}:ci" 20 | - run: 21 | name: Push to Dockerhub 22 | command: | 23 | echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin 24 | docker tag "${IMAGE_NAME}:ci" "${IMAGE_NAME}:${IMAGE_TAG}" 25 | docker push "${IMAGE_NAME}:${IMAGE_TAG}" 26 | - store_artifacts: 27 | path: anchore-reports/ 28 | 29 | workflows: 30 | scan_image: 31 | jobs: 32 | - build_scan_image: 33 | context: dockerhub -------------------------------------------------------------------------------- /examples/jenkins/pipeline/inline_scan/Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline{ 2 | agent { 3 | docker { 4 | image 'docker:stable' 5 | } 6 | } 7 | environment { 8 | IMAGE_NAME = 'anchore/anchore-ci-demo' 9 | IMAGE_TAG = 'jenkins' 10 | } 11 | stages { 12 | stage('Build Image') { 13 | steps { 14 | sh 'docker build -t ${IMAGE_NAME}:ci .' 15 | } 16 | } 17 | stage('Scan') { 18 | steps { 19 | sh 'apk add bash curl' 20 | sh 'curl -s https://ci-tools.anchore.io/inline_scan-v0.3.3 | bash -s -- -d ./Dockerfile -b ./.anchore_policy.json ${IMAGE_NAME}:ci' 21 | } 22 | } 23 | stage('Push Image') { 24 | steps { 25 | withDockerRegistry([credentialsId: "dockerhub-creds", url: ""]){ 26 | sh 'docker tag ${IMAGE_NAME}:ci ${IMAGE_NAME}:${IMAGE_TAG}' 27 | sh 'docker push ${IMAGE_NAME}:${IMAGE_TAG}' 28 | } 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /examples/codefresh/inline_scan/codefresh.yml: -------------------------------------------------------------------------------- 1 | version: '1.0' 2 | steps: 3 | build_docker_image: 4 | title: Building Docker Image 5 | type: build 6 | image_name: ${{IMAGE_NAME}} 7 | tag: ci 8 | working_directory: ./ 9 | dockerfile: Dockerfile 10 | 11 | anchore_scan: 12 | type: composition 13 | title: Scanning with Anchore Engine 14 | composition: 15 | version: '2' 16 | services: 17 | anchore-engine: 18 | image: anchore/inline-scan:latest 19 | ports: 20 | - 8228 21 | command: bash -c 'docker-entrypoint.sh start &> /dev/null' 22 | composition_candidates: 23 | scan_image: 24 | image: anchore/inline-scan:latest 25 | links: 26 | - anchore-engine 27 | environment: 28 | - ANCHORE_CLI_URL=http://anchore-engine:8228/v1/ 29 | - IMAGE_NAME=${{IMAGE_NAME}} 30 | - IMAGE_TAG=${{IMAGE_TAG}} 31 | - DOCKER_USER=${{DOCKER_USER}} 32 | - DOCKER_PASS=${{DOCKER_PASS}} 33 | command: bash -xc 'anchore-cli system wait && anchore-cli registry add r.cfcr.io $DOCKER_USER $DOCKER_PASS --skip-validate && anchore_ci_tools.py -a --timeout 500 --image r.cfcr.io/anchore/${IMAGE_NAME}:ci' 34 | composition_variables: 35 | - IMAGE_NAME=${{IMAGE_NAME}} 36 | - IMAGE_TAG=${{IMAGE_TAG}} 37 | - DOCKER_USER=${{DOCKER_USER}} 38 | - DOCKER_PASS=${{DOCKER_PASS}} 39 | 40 | push_to_registry: 41 | title: Pushing to Docker Registry 42 | type: push 43 | candidate: '${{build_docker_image}}' 44 | tag: ${{IMAGE_TAG}} 45 | registry: dockerhub -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | Contributing to Anchore 2 | ======================= 3 | 4 | Signing your contribution 5 | ------------------------- 6 | 7 | We've chosen to use the Developer's Certificate of Origin (DCO) method 8 | that is employed by the Linux Kernel Project, which provides a simple 9 | way to contribute to the Anchore project. 10 | 11 | The process is to certify the below DCO 1.1 text 12 | :: 13 | 14 | Developer's Certificate of Origin 1.1 15 | 16 | By making a contribution to this project, I certify that: 17 | 18 | (a) The contribution was created in whole or in part by me and I 19 | have the right to submit it under the open source license 20 | indicated in the file; or 21 | 22 | (b) The contribution is based upon previous work that, to the best 23 | of my knowledge, is covered under an appropriate open source 24 | license and I have the right under that license to submit that 25 | work with modifications, whether created in whole or in part 26 | by me, under the same open source license (unless I am 27 | permitted to submit under a different license), as indicated 28 | in the file; or 29 | 30 | (c) The contribution was provided directly to me by some other 31 | person who certified (a), (b) or (c) and I have not modified 32 | it. 33 | 34 | (d) I understand and agree that this project and the contribution 35 | are public and that a record of the contribution (including all 36 | personal information I submit with it, including my sign-off) is 37 | maintained indefinitely and may be redistributed consistent with 38 | this project or the open source license(s) involved. 39 | :: 40 | 41 | Then, when it comes time to submit a contribution, include the 42 | following text in your contribution commit message: 43 | 44 | :: 45 | 46 | Signed-off-by: Joan Doe 47 | 48 | :: 49 | 50 | 51 | This message can be entered manually, or if you have configured git 52 | with the correct `user.name` and `user.email`, you can use the `-s` 53 | option to `git commit` to automatically include the signoff message. 54 | -------------------------------------------------------------------------------- /examples/gitlab/inline_scan/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | variables: 2 | IMAGE_NAME: ${CI_REGISTRY_IMAGE}/build:${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHA} 3 | 4 | stages: 5 | - build 6 | - scan 7 | - publish 8 | 9 | container_build: 10 | stage: build 11 | image: docker:stable 12 | services: 13 | - docker:stable-dind 14 | 15 | variables: 16 | DOCKER_DRIVER: overlay2 17 | 18 | script: 19 | - echo "$CI_JOB_TOKEN" | docker login -u gitlab-ci-token --password-stdin "${CI_REGISTRY}" 20 | - docker build -t "$IMAGE_NAME" . 21 | - apk add bash curl 22 | - curl -s https://ci-tools.anchore.io/inline_scan-v0.3.3 | bash -s -- -r -t 500 "$IMAGE_NAME" 23 | - docker push "$IMAGE_NAME" 24 | - | 25 | echo "Parsing anchore reports." 26 | apk add jq 27 | bash <<'EOF' 28 | for f in anchore-reports/*; do 29 | if [[ "$f" =~ "content-os" ]]; then 30 | printf "\n%s\n" "The following OS packages are installed on ${IMAGE_NAME}:" 31 | jq '[.content | sort_by(.package) | .[] | {package: .package, version: .version}]' $f || true 32 | fi 33 | if [[ "$f" =~ "vuln" ]]; then 34 | printf "\n%s\n" "The following vulnerabilities were found on ${IMAGE_NAME}:" 35 | jq '[.vulnerabilities | group_by(.package) | .[] | {package: .[0].package, vuln: [.[].vuln]}]' $f || true 36 | fi 37 | done 38 | EOF 39 | 40 | artifacts: 41 | name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME} 42 | paths: 43 | - anchore-reports/* 44 | 45 | container_scan_service: 46 | stage: scan 47 | variables: 48 | ANCHORE_CLI_URL: "http://anchore-engine:8228/v1" 49 | GIT_STRATEGY: none 50 | image: docker.io/anchore/inline-scan:latest 51 | services: 52 | - name: docker.io/anchore/inline-scan:latest 53 | alias: anchore-engine 54 | command: ["start"] 55 | 56 | script: 57 | - anchore-cli system wait 58 | - anchore-cli registry add "$CI_REGISTRY" gitlab-ci-token "$CI_JOB_TOKEN" --skip-validate 59 | - anchore_ci_tools.py -a -r --timeout 500 --image $IMAGE_NAME 60 | 61 | artifacts: 62 | name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME} 63 | paths: 64 | - anchore-reports/* 65 | 66 | container_publish: 67 | stage: publish 68 | image: docker:stable 69 | services: 70 | - docker:stable-dind 71 | 72 | variables: 73 | DOCKER_DRIVER: overlay2 74 | GIT_STRATEGY: none 75 | 76 | script: 77 | - echo "$CI_JOB_TOKEN" | docker login -u gitlab-ci-token --password-stdin "${CI_REGISTRY}" 78 | - docker pull "$IMAGE_NAME" 79 | - docker tag "$IMAGE_NAME" "${CI_REGISTRY_IMAGE}:latest" 80 | - docker push "${CI_REGISTRY_IMAGE}:latest" 81 | -------------------------------------------------------------------------------- /scripts/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail 4 | 5 | if [[ "${VERBOSE}" ]]; then 6 | set -x 7 | fi 8 | 9 | main() { 10 | # use 'debug' as the first input param for script. This starts all services, then execs all proceeding inputs 11 | if [[ "$#" -lt 1 ]]; then 12 | start_services 'exec' 13 | elif [[ "$1" = 'debug' ]]; then 14 | start_services 15 | exec "${@:2}" 16 | # use 'start' as the first input param for script. This will start all services & execs anchore-manager. 17 | elif [[ "$1" = 'start' ]]; then 18 | start_services 'exec' 19 | elif [[ "$1" == 'scan' ]]; then 20 | start_services 21 | exec image_vuln_scan.sh "${@:2}" 22 | elif [[ "$1" == 'analyze' ]]; then 23 | setup_env 24 | exec image_analysis.sh "${@:2}" 25 | else 26 | exec "$@" 27 | fi 28 | } 29 | 30 | setup_env() { 31 | export POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-mysecretpassword}" 32 | export ANCHORE_DB_PASSWORD="${POSTGRES_PASSWORD}" 33 | export ANCHORE_DB_USER="${POSTGRES_USER}" 34 | export ANCHORE_DB_NAME="${POSTGRES_DB}" 35 | export ANCHORE_DB_HOST="${ANCHORE_ENDPOINT_HOSTNAME}" 36 | export ANCHORE_HOST_ID="${ANCHORE_ENDPOINT_HOSTNAME}" 37 | export ANCHORE_CLI_URL="http://${ANCHORE_ENDPOINT_HOSTNAME}:8228/v1" 38 | export PATH=${PATH}:/usr/pgsql-${PG_MAJOR}/bin/ 39 | export TIMEOUT=${TIMEOUT:=300} 40 | } 41 | 42 | start_services() { 43 | setup_env 44 | local exec_anchore="$1" 45 | 46 | if [ -f "/opt/rh/rh-python36/enable" ]; then 47 | source /opt/rh/rh-python36/enable 48 | fi 49 | 50 | if [[ ! "${exec_anchore}" = "exec" ]] && [[ ! $(pgrep anchore-manager) ]]; then 51 | echo "Starting Anchore Engine" 52 | nohup anchore-manager service start --all &> /var/log/anchore.log & 53 | fi 54 | 55 | if [[ ! $(pg_isready -d postgres --quiet) ]]; then 56 | printf '%s' "Starting Postgresql... " 57 | nohup bash -c 'postgres &> /var/log/postgres.log &' &> /dev/null 58 | sleep 3 && pg_isready -d postgres --quiet && printf '%s\n' "Postgresql started successfully!" 59 | fi 60 | 61 | if [[ ! $(curl --silent "${ANCHORE_ENDPOINT_HOSTNAME}:5000") ]]; then 62 | printf '%s' "Starting Docker registry... " 63 | nohup registry serve /etc/docker/registry/config.yml &> /var/log/registry.log & 64 | sleep 3 && curl --silent --retry 3 "${ANCHORE_ENDPOINT_HOSTNAME}:5000" && printf '%s\n' "Docker registry started successfully!" 65 | fi 66 | 67 | if [[ "${exec_anchore}" = "exec" ]]; then 68 | echo "Starting Anchore Engine" 69 | exec anchore-manager service start --all 70 | fi 71 | 72 | echo "Waiting for Anchore Engine to be available." 73 | # pass python script to background process & wait, required to handle keyboard interrupt when running container non-interactively. 74 | anchore_ci_tools.py --wait --timeout "${TIMEOUT}" & 75 | local wait_proc="$!" 76 | wait "${wait_proc}" 77 | } 78 | 79 | main "$@" -------------------------------------------------------------------------------- /conf/stateless_ci_config.yaml: -------------------------------------------------------------------------------- 1 | # Anchore Service Configuration File 2 | # 3 | 4 | # General system-wide configuration options, these should not need to 5 | # be altered for basic operation 6 | # 7 | 8 | service_dir: ${ANCHORE_SERVICE_DIR} 9 | tmp_dir: /analysis_scratch 10 | log_level: ${ANCHORE_LOG_LEVEL} 11 | cleanup_images: False 12 | host_id: '${ANCHORE_HOST_ID}' 13 | internal_ssl_verify: False 14 | auto_restart_services: False 15 | 16 | feeds: 17 | # If set to False, instruct anchore-engine to skip (all) feed sync operations 18 | sync_enabled: False 19 | selective_sync: 20 | # If enabled only sync specific feeds instead of all. 21 | enabled: True 22 | feeds: 23 | github: True 24 | vulnerabilities: True 25 | # Warning: enabling the packages and nvd sync causes the service to require much 26 | # more memory to do process the significant data volume. We recommend at least 4GB available for the container 27 | packages: False 28 | nvdv2: True 29 | anonymous_user_username: anon@ancho.re 30 | anonymous_user_password: pbiU2RYZ2XrmYQ 31 | url: 'https://ancho.re/v1/service/feeds' 32 | client_url: 'https://ancho.re/v1/account/users' 33 | token_url: 'https://ancho.re/oauth/token' 34 | connection_timeout_seconds: 3 35 | read_timeout_seconds: 180 36 | 37 | 38 | # Can be omitted and will default to 'foobar' on db initialization 39 | default_admin_password: ${ANCHORE_CLI_PASS} 40 | 41 | # Can be ommitted and will default to 'admin@myanchore' 42 | default_admin_email: ${ANCHORE_ADMIN_EMAIL} 43 | 44 | credentials: 45 | database: 46 | db_connect: 'postgresql+pg8000://${ANCHORE_DB_USER}:${ANCHORE_DB_PASSWORD}@${ANCHORE_DB_HOST}:${ANCHORE_DB_PORT}/${ANCHORE_DB_NAME}' 47 | db_connect_args: 48 | timeout: 120 49 | ssl: False 50 | db_pool_size: 30 51 | db_pool_max_overflow: 100 52 | 53 | services: 54 | apiext: 55 | enabled: True 56 | require_auth: True 57 | endpoint_hostname: '${ANCHORE_ENDPOINT_HOSTNAME}' 58 | listen: '0.0.0.0' 59 | port: 8228 60 | authorization_handler: native 61 | catalog: 62 | enabled: True 63 | require_auth: True 64 | endpoint_hostname: '${ANCHORE_ENDPOINT_HOSTNAME}' 65 | listen: '0.0.0.0' 66 | port: 8082 67 | # NOTE: use the below external_* parameters to define the port/tls 68 | # setting that will allow other internal services to access this 69 | # service - if left unset services will use the above, 70 | # e.g. http://: 71 | # external_port: 8082 72 | # external_tls: False 73 | archive: 74 | compression: 75 | enabled: False 76 | min_size_kbytes: 100 77 | storage_driver: 78 | name: db 79 | config: {} 80 | cycle_timer_seconds: 1 81 | cycle_timers: 82 | image_watcher: 3600 83 | policy_eval: 3600 84 | vulnerability_scan: 14400 85 | analyzer_queue: 5 86 | notifications: 30 87 | service_watcher: 15 88 | policy_bundle_sync: 300 89 | repo_watcher: 60 90 | # Uncomment if you would like to receive notifications for events triggered by asynchronous operations in the system. 91 | # In addition, uncomment the webhooks section and supply the configuration for either a 'general' or an 'event_log' webhook 92 | # event_log: 93 | # notification: 94 | # enabled: True 95 | # # (optional) notify events that match these levels. If this section is commented, notifications for all events are sent 96 | # level: 97 | # - error 98 | simplequeue: 99 | enabled: True 100 | require_auth: True 101 | endpoint_hostname: '${ANCHORE_ENDPOINT_HOSTNAME}' 102 | listen: '0.0.0.0' 103 | port: 8083 104 | # external_port: 8083 105 | # external_tls: False 106 | analyzer: 107 | enabled: True 108 | require_auth: True 109 | cycle_timer_seconds: 1 110 | cycle_timers: 111 | image_analyzer: 5 112 | max_threads: 1 113 | analyzer_driver: 'nodocker' 114 | endpoint_hostname: '${ANCHORE_ENDPOINT_HOSTNAME}' 115 | listen: '0.0.0.0' 116 | port: 8084 117 | # external_port: 8084 118 | # external_tls: False 119 | policy_engine: 120 | enabled: True 121 | require_auth: True 122 | endpoint_hostname: '${ANCHORE_ENDPOINT_HOSTNAME}' 123 | listen: '0.0.0.0' 124 | port: 8087 125 | # external_port: 8087 126 | # external_tls: False 127 | cycle_timer_seconds: 1 128 | cycle_timers: 129 | feed_sync: 21600 # 6 hours between feed syncs 130 | feed_sync_checker: 3600 # 1 hour between checks to see if there needs to be a task queued -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG ANCHORE_REPO="anchore/anchore-engine" 2 | ARG ANCHORE_VERSION="latest" 3 | FROM ${ANCHORE_REPO}:${ANCHORE_VERSION} 4 | 5 | USER root:root 6 | 7 | ENV JQ_VERSION=1.6 8 | ENV GOSU_VERSION=1.11 9 | 10 | RUN set -ex; \ 11 | yum -y upgrade; \ 12 | yum install -y ca-certificates; \ 13 | curl -Lo /usr/local/bin/jq "https://github.com/stedolan/jq/releases/download/jq-${JQ_VERSION}/jq-linux64"; \ 14 | curl -o /usr/local/bin/jq.asc "https://raw.githubusercontent.com/stedolan/jq/master/sig/v${JQ_VERSION}/jq-linux64.asc";\ 15 | curl -o /usr/local/bin/jq-public.asc "https://raw.githubusercontent.com/stedolan/jq/master/sig/jq-release.key"; \ 16 | curl -Lo /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/${GOSU_VERSION}/gosu-amd64"; \ 17 | curl -Lo /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/${GOSU_VERSION}/gosu-amd64.asc"; \ 18 | export GNUPGHOME="$(mktemp -d)"; \ 19 | gpg --batch --keyserver keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \ 20 | gpg --batch --import /usr/local/bin/jq-public.asc; \ 21 | gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \ 22 | gpg --batch --verify /usr/local/bin/jq.asc /usr/local/bin/jq; \ 23 | command -v gpgconf && gpgconf --kill all || :; \ 24 | chmod +x /usr/local/bin/jq; \ 25 | chmod +x /usr/local/bin/gosu; \ 26 | rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc /usr/local/bin/jq.asc; \ 27 | rm -rf /anchore-engine/* /root/.cache /config/config.yaml 28 | 29 | ENV PG_MAJOR="9.6" 30 | ENV PGDATA="/var/lib/postgresql/data" 31 | 32 | RUN set -eux; \ 33 | yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm && \ 34 | yum install -y postgresql96 postgresql96-server 35 | 36 | RUN set -eux; \ 37 | mkdir -p /var/lib/postgresql; \ 38 | chown -R anchore:anchore /var/lib/postgresql; \ 39 | mkdir /docker-entrypoint-initdb.d; \ 40 | touch /var/log/postgres.log; \ 41 | chown anchore:anchore /var/log/postgres.log; \ 42 | mkdir -p /var/run/postgresql; \ 43 | chown -R anchore:anchore /var/run/postgresql; \ 44 | chmod 2775 /var/run/postgresql; \ 45 | mkdir -p "$PGDATA"; \ 46 | chown -R anchore:anchore "$PGDATA"; \ 47 | chmod 700 "$PGDATA" 48 | 49 | COPY anchore-bootstrap.sql.gz /docker-entrypoint-initdb.d/ 50 | 51 | ENV POSTGRES_USER="postgres" \ 52 | POSTGRES_DB="postgres" \ 53 | POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-mysecretpassword}" 54 | 55 | RUN set -eux; \ 56 | export PATH=${PATH}:/usr/pgsql-${PG_MAJOR}/bin/; \ 57 | gosu anchore bash -c 'initdb --username=${POSTGRES_USER} --pwfile=<(echo "$POSTGRES_PASSWORD")'; \ 58 | PGUSER="${PGUSER:-$POSTGRES_USER}" \ 59 | gosu anchore bash -c 'pg_ctl -D "$PGDATA" -o "-c listen_addresses='*'" -w start'; \ 60 | printf '\n%s' 'host all all 0.0.0.0/0 md5' >> ${PGDATA}/pg_hba.conf; \ 61 | export PGPASSWORD="${PGPASSWORD:-$POSTGRES_PASSWORD}"; \ 62 | gosu anchore bash -c '\ 63 | export PGPASSWORD="${PGPASSWORD:-$POSTGRES_PASSWORD}"; \ 64 | export psql=( psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --no-password --dbname "$POSTGRES_DB" ); \ 65 | for f in /docker-entrypoint-initdb.d/*; do \ 66 | echo running "$f"; gunzip -c "$f" | "${psql[@]}"; echo ; \ 67 | done'; \ 68 | PGUSER="${PGUSER:-$POSTGRES_USER}" \ 69 | gosu anchore bash -c 'pg_ctl -D "$PGDATA" -m fast -w stop'; \ 70 | unset PGPASSWORD; \ 71 | rm -f /docker-entrypoint-initdb.d/anchore-bootstrap.sql.gz 72 | 73 | ENV REGISTRY_VERSION 2.7 74 | 75 | RUN set -eux; \ 76 | mkdir -p /etc/docker/registry; \ 77 | mkdir /var/lib/registry; \ 78 | chown anchore:anchore /var/lib/registry; \ 79 | curl -LH 'Accept: application/octet-stream' -o /usr/local/bin/registry https://github.com/docker/distribution-library-image/raw/release/${REGISTRY_VERSION}/amd64/registry; \ 80 | chmod +x /usr/local/bin/registry; \ 81 | curl -Lo /etc/docker/registry/config.yml https://raw.githubusercontent.com/docker/distribution-library-image/release/${REGISTRY_VERSION}/amd64/config-example.yml; \ 82 | touch /var/log/registry.log; \ 83 | chown anchore:anchore /var/log/registry.log 84 | 85 | ENV ANCHORE_ENDPOINT_HOSTNAME="localhost" 86 | RUN set -eux; \ 87 | echo "127.0.0.1 $ANCHORE_ENDPOINT_HOSTNAME" >> /etc/hosts; \ 88 | touch /var/log/anchore.log; \ 89 | chown anchore:anchore /var/log/anchore.log; \ 90 | chown anchore:anchore /anchore-engine 91 | 92 | COPY conf/stateless_ci_config.yaml /config/config.yaml 93 | COPY scripts/anchore_ci_tools.py \ 94 | scripts/docker-entrypoint.sh \ 95 | scripts/image_analysis.sh \ 96 | scripts/image_vuln_scan.sh /usr/local/bin/ 97 | 98 | USER anchore:anchore 99 | WORKDIR /anchore-engine 100 | ENV PATH ${PATH}:/anchore-cli/bin 101 | 102 | EXPOSE 5432 5000 103 | ENTRYPOINT ["docker-entrypoint.sh"] 104 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Define YAML anchors 2 | filter_semver_tags: &filter_semver_tags 3 | branches: 4 | ignore: /.*/ 5 | tags: 6 | only: /^v[0-9]+(\.[0-9]+)*$/ 7 | 8 | # Start of CircleCI Configuration 9 | version: 2.1 10 | jobs: 11 | build_test_push_inline_image: 12 | parameters: 13 | anchore_version: 14 | default: "" 15 | type: string 16 | docker: 17 | - image: circleci/python:3.6 18 | steps: 19 | - setup_remote_docker 20 | - checkout 21 | - run: 22 | name: Build and Save Images 23 | command: scripts/build.sh build_and_save_images << parameters.anchore_version >> 24 | no_output_timeout: 1h 25 | - run: 26 | name: Test Inline Script 27 | command: scripts/build.sh test_built_images << parameters.anchore_version >> 28 | no_output_timeout: 1h 29 | - run: 30 | name: Load & Push Image to DockerHub 31 | command: scripts/build.sh load_image_and_push_dockerhub << parameters.anchore_version >> 32 | no_output_timeout: 1h 33 | 34 | build_test_push_inline_slim: 35 | parameters: 36 | anchore_version: 37 | default: "" 38 | type: string 39 | docker: 40 | - image: circleci/python:3.6 41 | steps: 42 | - setup_remote_docker 43 | - checkout 44 | - run: 45 | name: Build and Save Images 46 | command: IMAGE_REPO=docker.io/anchore/inline-scan-slim scripts/build.sh -s build_and_save_images << parameters.anchore_version >> 47 | no_output_timeout: 1h 48 | - run: 49 | name: Test Inline Script 50 | command: IMAGE_REPO=docker.io/anchore/inline-scan-slim scripts/build.sh -s test_built_images << parameters.anchore_version >> 51 | no_output_timeout: 1h 52 | - run: 53 | name: Load & Push Image to DockerHub 54 | command: IMAGE_REPO=docker.io/anchore/inline-scan-slim scripts/build.sh -s load_image_and_push_dockerhub << parameters.anchore_version >> 55 | no_output_timeout: 1h 56 | 57 | push_inline_scan_script: 58 | docker: 59 | - image: circleci/python:3.6 60 | steps: 61 | - checkout 62 | - run: 63 | name: Install awscli 64 | command: | 65 | pip install --upgrade awscli 66 | - run: 67 | name: Copy inline_scan script to s3 68 | command: | 69 | aws s3 cp ./scripts/inline_scan s3://ci-tools.anchore.io/inline_scan-"${CIRCLE_TAG}" --acl public-read 70 | aws s3 cp ./scripts/inline_scan s3://ci-tools.anchore.io/inline_scan-latest --acl public-read 71 | 72 | workflows: 73 | default_workflow: 74 | jobs: 75 | - build_test_push_inline_image: 76 | name: build_test_push_inline_image_dev 77 | anchore_version: dev 78 | context: dockerhub 79 | 80 | build_prod_images_on_tag: 81 | jobs: 82 | - build_test_push_inline_image: 83 | name: build_test_push_inline_image_all 84 | anchore_version: all 85 | context: dockerhub 86 | filters: *filter_semver_tags 87 | - build_test_push_inline_slim: 88 | name: build_test_push_inline_slim_all 89 | anchore_version: all 90 | context: dockerhub 91 | filters: *filter_semver_tags 92 | - push_inline_scan_script: 93 | context: aws-prod 94 | filters: *filter_semver_tags 95 | requires: 96 | - build_test_push_inline_image_all 97 | - build_test_push_inline_slim_all 98 | 99 | nightly_build: 100 | triggers: 101 | - schedule: 102 | cron: "0 12 * * *" 103 | filters: 104 | branches: 105 | only: 106 | - master 107 | jobs: 108 | - build_test_push_inline_slim: 109 | name: build_test_push_inline_slim_v094 110 | anchore_version: v0.9.4 111 | context: dockerhub 112 | - build_test_push_inline_slim: 113 | name: build_test_push_inline_slim_v0100 114 | anchore_version: v0.10.0 115 | context: dockerhub 116 | - build_test_push_inline_slim: 117 | name: build_test_push_inline_slim_v0101 118 | anchore_version: v0.10.1 119 | context: dockerhub 120 | - build_test_push_inline_slim: 121 | name: build_test_push_inline_slim_v0102 122 | anchore_version: v0.10.2 123 | context: dockerhub 124 | - build_test_push_inline_image: 125 | name: build_test_push_inline_image_v094 126 | anchore_version: v0.9.4 127 | context: dockerhub 128 | - build_test_push_inline_image: 129 | name: build_test_push_inline_image_v0100 130 | anchore_version: v0.10.0 131 | context: dockerhub 132 | - build_test_push_inline_image: 133 | name: build_test_push_inline_image_v0101 134 | anchore_version: v0.10.1 135 | context: dockerhub 136 | - build_test_push_inline_image: 137 | name: build_test_push_inline_image_v0102 138 | anchore_version: v0.10.2 139 | context: dockerhub 140 | -------------------------------------------------------------------------------- /.circleci/.anchore/policy_bundle.json: -------------------------------------------------------------------------------- 1 | { 2 | "blacklisted_images": [], 3 | "comment": "Default bundle", 4 | "id": "custom_policy_bundle_orb_testing_123456abcdefg", 5 | "last_updated": 1543266146, 6 | "mappings": [ 7 | { 8 | "id": "c4f9bf74-dc38-4ddf-b5cf-00e9c0074611", 9 | "image": { 10 | "type": "tag", 11 | "value": "*" 12 | }, 13 | "name": "default", 14 | "policy_id": "48e6f7d6-1765-11e8-b5f9-8b6f228548b6", 15 | "registry": "*", 16 | "repository": "*", 17 | "whitelist_ids": [ 18 | "37fd763e-1765-11e8-add4-3b16c029ac5c" 19 | ] 20 | } 21 | ], 22 | "name": "Critical Security Policy", 23 | "policies": [ 24 | { 25 | "comment": "Critical vulnerability, secrets, and best practice violations", 26 | "id": "48e6f7d6-1765-11e8-b5f9-8b6f228548b6", 27 | "name": "default", 28 | "rules": [ 29 | { 30 | "action": "WARN", 31 | "gate": "vulnerabilities", 32 | "id": "92b1c635-5ba4-422d-951b-0e4172a18d1a", 33 | "params": [ 34 | { 35 | "name": "package_type", 36 | "value": "all" 37 | }, 38 | { 39 | "name": "severity_comparison", 40 | "value": ">=" 41 | }, 42 | { 43 | "name": "severity", 44 | "value": "medium" 45 | }, 46 | { 47 | "name": "fix_available", 48 | "value": "true" 49 | } 50 | ], 51 | "trigger": "package" 52 | }, 53 | { 54 | "action": "STOP", 55 | "gate": "dockerfile", 56 | "id": "20a32af4-5c16-487f-8c67-212ff2bc2d11", 57 | "params": [ 58 | { 59 | "name": "users", 60 | "value": "root" 61 | }, 62 | { 63 | "name": "type", 64 | "value": "blacklist" 65 | } 66 | ], 67 | "trigger": "effective_user" 68 | }, 69 | { 70 | "action": "STOP", 71 | "gate": "secret_scans", 72 | "id": "509d5438-f0e3-41df-bb1a-33013f23e31c", 73 | "params": [], 74 | "trigger": "content_regex_checks" 75 | }, 76 | { 77 | "action": "STOP", 78 | "gate": "dockerfile", 79 | "id": "ce7b8000-829b-4c27-8122-69cd59018400", 80 | "params": [ 81 | { 82 | "name": "ports", 83 | "value": "22,80,443" 84 | }, 85 | { 86 | "name": "type", 87 | "value": "blacklist" 88 | } 89 | ], 90 | "trigger": "exposed_ports" 91 | }, 92 | { 93 | "action": "WARN", 94 | "gate": "dockerfile", 95 | "id": "312d9e41-1c05-4e2f-ad89-b7d34b0855bb", 96 | "params": [ 97 | { 98 | "name": "instruction", 99 | "value": "HEALTHCHECK" 100 | }, 101 | { 102 | "name": "check", 103 | "value": "not_exists" 104 | } 105 | ], 106 | "trigger": "instruction" 107 | }, 108 | { 109 | "action": "STOP", 110 | "gate": "vulnerabilities", 111 | "id": "6b5c14e7-a6f7-48cc-99d2-959273a2c6fa", 112 | "params": [ 113 | { 114 | "name": "max_days_since_sync", 115 | "value": "2" 116 | } 117 | ], 118 | "trigger": "stale_feed_data" 119 | }, 120 | { 121 | "action": "STOP", 122 | "gate": "vulnerabilities", 123 | "id": "3e79ea94-18c4-4d26-9e29-3b9172a62c2e", 124 | "params": [], 125 | "trigger": "vulnerability_data_unavailable" 126 | } 127 | ], 128 | "version": "1_0" 129 | } 130 | ], 131 | "version": "1_0", 132 | "whitelisted_images": [], 133 | "whitelists": [ 134 | { 135 | "comment": "Default global whitelist", 136 | "id": "37fd763e-1765-11e8-add4-3b16c029ac5c", 137 | "items": [], 138 | "name": "Global Whitelist", 139 | "version": "1_0" 140 | } 141 | ] 142 | } 143 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | _**Warning**: The Anchore Inline Scan script is deprecated and will reach **EOL on Jan 10, 2022**. Please update your integrations to use [Grype](https://github.com/anchore/grype) for CI-based vulnerability scanning or [Syft](https://github.com/anchore/syft). We will be updating our integrations that use inline_scan during that time to use Grype directly. Until that time all integrations will continue to function and get updated vulnerability data._ 2 | 3 | _**Until Jan 10, 2022**: we will continue building inline_scan images based on v0.10.x of Anchore Engine and they will be updated daily for latest feed data. 4 | On Jan 10, 2022, we will stop building new versions of the images with updated vulnerability data and the data will be stale._ 5 | 6 | _**After Jan 10, 2022**: users should be transitioned to [Grype](https://github.com/anchore/grype) or Grype-based integrations._ 7 | 8 | # Anchore CI tools [![CircleCI](https://circleci.com/gh/anchore/ci-tools.svg?style=svg)](https://circleci.com/gh/anchore/ci-tools) 9 | 10 | An assortment of scripts & tools for integrating Anchore Engine into your CI/CD pipeline. 11 | 12 | ### `scripts/inline_scan` 13 | * Allows scanning and analysis of local docker images. 14 | * Invokes the anchore/inline-scan container to perform a vulnerability scan or image analysis. 15 | 16 | ### `scripts/anchore-ci-tools.py` 17 | * Currently only supports docker based CI/CD tools. 18 | * Script is intended to run directly on Anchore Engine containers. 19 | * Used by scripts/inline_scan & should only be used when inline_scan is not an option. 20 | 21 | ### `scripts/build.sh` 22 | * Used to build any version of the inline_scan container. 23 | * Also can be used to run CI tests locally. 24 | 25 | # CircleCI Orbs 26 | 27 | Source code for CircleCi orbs has been moved to a new [GitHub Repository](https://github.com/anchore/circleci-orbs/tree/master/anchore-engine). 28 | 29 | # Anchore inline-scan container 30 | 31 | Image is built using the official Anchore Engine image base. It contains a Postgresql database pre-loaded with Anchore vulnerability data from https://github.com/anchore/engine-db-preload daily. Also contains a Docker registry which is used for passing images to Anchore Engine for vulnerability scanning. 32 | 33 | Container is built using the `scripts/build.sh` script. The version of anchore-engine to build with can be specified with the environment variable `ANCHORE_VERSION=`. 34 | 35 | After building the inline_scan container locally, the `scripts/inline_scan` script can be called using this container by setting the environment variable `ANCHORE_CI_IMAGE=stateless_anchore:ci`. 36 | 37 | ## Inline vulnerability scanner 38 | Wrapper script for inline_scan container, requires Docker & BASH to be installed on system running the script. 39 | * Call script directly from github with: 40 | 41 | ```curl -s https://ci-tools.anchore.io/inline_scan-v0.10.0 | bash -s -- [OPTIONS] ``` 42 | 43 | #### Pull multiple images from dockerhub, scan and generate reports. 44 | ```bash 45 | inline_scan -p -r alpine:latest ubuntu:latest centos:latest 46 | ``` 47 | 48 | #### Pass Dockerfile to image scan, after docker build. 49 | ```bash 50 | docker build -t example-image:latest -f ./Dockerfile . 51 | inline_scan -d ./Dockerfile example-image:latest 52 | ``` 53 | 54 | #### Scan image using custom policy bundle, fail script upon failed policy evaluation. 55 | ```bash 56 | inline_scan -f -p ./policy-bundle.json example-image:latest 57 | ``` 58 | 59 | ## Inline image analysis 60 | 61 | For use cases where it is desirable to perform image analysis for a locally build container image, and import the image analysis to an existing Anchore Engine installation, we support a methodology using the inline_scan tool. With this technique, you can 'add' an image to your anchore engine service by analyzing any image that is available locally (say, on the docker host on which the image was built). You can then import the analysis data into anchore engine, rather than the regular method where images are pulled from a registry when added to anchore engine. 62 | 63 | The only requirements to run the inline_scan script with the 'analyze' operation is the ability to execute Docker commands, network connectivity to an anchore engine API endpoint & bash. We host a versioned copy of this script that can be downloaded directly with curl and executed in a bash pipeline. 64 | 65 | * *Note - For the rest of this document, `USER`, `PASS`, and `URL` refer to an anchore engine user, password, and engine endpoint URL (http://:/v1) respectively.* 66 | 67 | To run the script on your workstation, use the following command syntax. 68 | 69 | `curl -s https://ci-tools.anchore.io/inline_scan-v0.10.0 | bash -s -- analyze -u -p -r [ OPTIONS ] ` 70 | 71 | ### Image Identity Selection 72 | In order to perform local analysis and import the image correctly into your existing anchore engine deployment, special attention should be paid to the image identifiers (image id, digest, manifest, full tag name) when performing local analysis. Since image digests are generated from an image 'manifest' which is generated by a container registry, this technique requires that you either specify a digest, ask for one to be randomly 'generated', or supply a valid manifest alongside the image when it is being imported. An image ID can also be supplied if one is available that you would prefer to use. Best practice is to supply these identifiers in whichever way is most appropriate for your use case, resulting in the information being associated with the imported image correctly such that you can refer to it later using these identifiers. 73 | 74 | See [docs.anchore.com](https://docs.anchore.com/current/docs/engine/usage/integration/ci_cd/inline_analysis/) for more usage examples. 75 | -------------------------------------------------------------------------------- /scripts/image_analysis.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail 4 | 5 | if [[ "${VERBOSE}" ]]; then 6 | set -x 7 | fi 8 | 9 | ######################## 10 | ### GLOBAL VARIABLES ### 11 | ######################## 12 | 13 | export TIMEOUT=${TIMEOUT:=300} 14 | # defaults for variables set by script options 15 | ANALYZE_CMD=() 16 | ANCHORE_ANNOTATIONS="" 17 | IMAGE_DIGEST_SHA="" 18 | ANCHORE_IMAGE_ID="" 19 | IMAGE_TAG="" 20 | DOCKERFILE="/anchore-engine/Dockerfile" 21 | MANIFEST_FILE="/anchore-engine/manifest.json" 22 | 23 | 24 | display_usage() { 25 | cat << EOF 26 | 27 | Anchore Engine Inline Analyzer -- 28 | 29 | Script for performing analysis on local docker images, utilizing Anchore Engine analyzer subsystem. 30 | After image is analyzed, the resulting Anchore image archive is sent to a remote Anchore Engine installation 31 | using the -r option. This allows inline_analysis data to be persisted & utilized for reporting. 32 | 33 | Images should be built & tagged locally. 34 | 35 | Usage: ${0##*/} [ OPTIONS ] 36 | 37 | -u [required] Anchore user account name associated with image analysis (ex: -u 'admin') 38 | -a [optional] Add annotations (ex: -a 'key=value,key=value') 39 | -d [optional] Specify image digest (ex: -d 'sha256:<64 hex characters>') 40 | -f [optional] Path to Dockerfile (ex: -f ./Dockerfile) 41 | -i [optional] Specify image ID used within Anchore Engine (ex: -i '<64 hex characters>') 42 | -m [optional] Path to Docker image manifest (ex: -m ./manifest.json) 43 | -t [optional] Specify timeout for image analysis in seconds. Defaults to 300s. (ex: -t 500) 44 | -g [optional] Generate an image digest from docker save tarball 45 | 46 | EOF 47 | } 48 | 49 | main() { 50 | trap 'error' SIGINT 51 | get_and_validate_options "$@" 52 | 53 | local base_image_name=${IMAGE_TAG##*/} 54 | local image_file_path="/anchore-engine/${base_image_name}.tar" 55 | 56 | if [[ ! -f "${image_file_path}" ]]; then 57 | printf '\n\t%s\n\n' "ERROR - Could not find file: ${image_file_path}" >&2 58 | display_usage >&2 59 | exit 1 60 | fi 61 | 62 | # if filename has a : in it, replace it with _ to avoid skopeo errors 63 | if [[ "${base_image_name}" =~ [:] ]]; then 64 | local new_file_path="/anchore-engine/${base_image_name//:/_}.tar" 65 | mv "${image_file_path}" "${new_file_path}" 66 | image_file_path="${new_file_path}" 67 | fi 68 | 69 | # analyze image with anchore-engine 70 | ANALYZE_CMD=('anchore-manager analyzers exec') 71 | ANALYZE_CMD+=('--tag "${IMAGE_TAG}"') 72 | ANALYZE_CMD+=('--account-id "${ANCHORE_ACCOUNT}"') 73 | 74 | if [[ "${g_flag}" ]]; then 75 | IMAGE_DIGEST_SHA=$(skopeo inspect --raw "docker-archive:///${image_file_path}" | jq -r .config.digest) 76 | fi 77 | if [[ "${d_flag}" ]] || [[ "${g_flag}" ]]; then 78 | ANALYZE_CMD+=('--digest "${IMAGE_DIGEST_SHA}"') 79 | fi 80 | if [[ "${m_flag}" ]]; then 81 | ANALYZE_CMD+=('--manifest "${MANIFEST_FILE}"') 82 | fi 83 | if [[ "${f_flag}" ]]; then 84 | ANALYZE_CMD+=('--dockerfile "${DOCKERFILE}"') 85 | fi 86 | if [[ "${i_flag}" ]]; then 87 | ANALYZE_CMD+=('--image-id "${ANCHORE_IMAGE_ID}"') 88 | fi 89 | if [[ "${a_flag}" ]]; then 90 | # transform all commas to spaces & cast to an array 91 | local annotation_array=(${ANCHORE_ANNOTATIONS//,/ }) 92 | for i in "${annotation_array[@]}"; do 93 | ANALYZE_CMD+=("--annotation $i") 94 | done 95 | fi 96 | 97 | ANALYZE_CMD+=('"$image_file_path" /anchore-engine/image-analysis-archive.tgz > /dev/null') 98 | printf '\n%s\n' "Analyzing ${IMAGE_TAG}..." 99 | eval "${ANALYZE_CMD[*]}" 100 | } 101 | 102 | get_and_validate_options() { 103 | # parse options 104 | while getopts ':u:a:d:f:i:m:t:gh' option; do 105 | case "${option}" in 106 | a ) a_flag=true; ANCHORE_ANNOTATIONS="${OPTARG}";; 107 | d ) d_flag=true; IMAGE_DIGEST_SHA="${OPTARG}";; 108 | f ) f_flag=true; DOCKERFILE="/anchore-engine/$(basename ${OPTARG})";; 109 | i ) i_flag=true; ANCHORE_IMAGE_ID="${OPTARG}";; 110 | m ) m_flag=true; MANIFEST_FILE="/anchore-engine/$(basename ${OPTARG})";; 111 | t ) t_flag=true; TIMEOUT="${OPTARG}";; 112 | u ) u_flag=true; ANCHORE_ACCOUNT="${OPTARG}";; 113 | g ) g_flag=true;; 114 | h ) display_usage; exit;; 115 | \? ) printf "\n\t%s\n\n" " Invalid option: -${OPTARG}" >&2; display_usage >&2; exit 1;; 116 | : ) printf "\n\t%s\n\n%s\n\n" " Option -${OPTARG} requires an argument." >&2; display_usage >&2; exit 1;; 117 | esac 118 | done 119 | shift "$((OPTIND - 1))" 120 | 121 | # Ensure only a single image tag is passed after options 122 | if [[ "${#@}" -gt 1 ]]; then 123 | printf '\n\t%s\n\n' "ERROR - only 1 image tag can be analyzed at a time" >&2 124 | display_usage >&2 125 | exit 1 126 | else 127 | IMAGE_TAG="$1" 128 | fi 129 | 130 | if [[ ! "${u_flag}" ]]; then 131 | printf '\n\t%s\n\n' "ERROR - must specify an anchore engine account name with -u" >&2 132 | display_usage >&2 133 | exit 1 134 | elif [[ "${f_flag}" ]] && [[ ! -f "${DOCKERFILE}" ]]; then 135 | printf '\n\t%s\n\n' "ERROR - invalid path to dockerfile provided - ${DOCKERFILE}" >&2 136 | display_usage >&2 137 | exit 1 138 | elif [[ "${m_flag}" ]] && [[ ! -f "${MANIFEST_FILE}" ]]; then 139 | printf '\n\t%s\n\n' "ERROR - invalid path to image manifest file provided - ${MANIFEST_FILE}" >&2 140 | display_usage >&2 141 | exit 1 142 | elif [[ "${g_flag}" ]] && ([[ "${m_flag}" ]] || [[ "${d_flag}" ]]); then 143 | printf '\n\t%s\n\n' "ERROR - cannot specify manifest file or digest when using the -g option" >&2 144 | display_usage >&2 145 | exit 1 146 | fi 147 | } 148 | 149 | error() { 150 | set +e 151 | printf '\n\n\t%s\n\n' "ERROR - $0 received SIGTERM or SIGINT" >&2 152 | # kill python process in wait loop 153 | (pkill -f python3 &> /dev/null) 154 | # kill skopeo process in wait loop 155 | (pkill -f skopeo &> /dev/null) 156 | exit 130 157 | } 158 | 159 | main "$@" 160 | -------------------------------------------------------------------------------- /scripts/image_vuln_scan.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail 4 | 5 | if [[ "${VERBOSE}" ]]; then 6 | set -x 7 | fi 8 | 9 | ######################## 10 | ### GLOBAL VARIABLES ### 11 | ######################## 12 | 13 | export TIMEOUT=${TIMEOUT:=300} 14 | SCAN_FILES=() 15 | FINISHED_IMAGES=() 16 | # defaults for variables set by script options 17 | FILE_NAME="" 18 | IMAGE_NAME="" 19 | POLICY_BUNDLE="/anchore-engine/policy_bundle.json" 20 | DOCKERFILE="/anchore-engine/Dockerfile" 21 | 22 | 23 | display_usage() { 24 | cat << EOF 25 | 26 | Anchore Engine Inline Scan -- 27 | 28 | Docker entrypoint for performing vulnerability analysis on local docker images. 29 | 30 | Starts Anchore Engine, Postgresql 9.6, and Docker Registry. 31 | Finds docker image archives copied or mounted to /anchore-engine in the form of image+tag.tar. 32 | Also supports taking stdin from the docker save command (use -i option to specify image name). 33 | 34 | 35 | Usage: ${0##*/} [ -f ] [ -r ] [ -d Dockerfile ] [ -b policy.json ] [ -i IMAGE_ONE ] 36 | 37 | -d [optional] Dockerfile name - must be mounted/copied to /anchore-engine. 38 | -i [optional] Image name or file name location (use image name if piping in docker save stdout). 39 | -b [optional] Anchore policy bundle name - must be mounted/copied to /anchore-engine. 40 | -f [optional] Exit script upon failed Anchore policy evaluation. 41 | -r [optional] Generate analysis reports. 42 | 43 | EOF 44 | } 45 | 46 | main() { 47 | trap 'error' SIGINT 48 | get_and_validate_options "$@" 49 | 50 | if [[ "${FILE_NAME}" ]]; then 51 | if [[ $(skopeo inspect "docker-archive:${FILE_NAME}") ]]; then 52 | SCAN_FILES+=("$FILE_NAME") 53 | else 54 | printf '\n\t%s\n\n' "ERROR - Invalid Docker image archive: ${FILE_NAME}" >&2 55 | display_usage >&2 56 | exit 1 57 | fi 58 | else 59 | printf '%s\n\n' "Searching for Docker archive files in /anchore-engine." 60 | for i in $(find /anchore-engine -type f); do 61 | local file_name="$i" 62 | if [[ "${file_name}" =~ [:] ]]; then 63 | local new_file_name="/tmp/$(basename ${file_name//:/_})" 64 | cp "${file_name}" "${new_file_name}" 65 | file_name="${new_file_name}" 66 | fi 67 | if [[ $(skopeo inspect "docker-archive:${file_name}") ]] && [[ ! "${SCAN_FILES[@]}" =~ "${file_name}" ]]; then 68 | SCAN_FILES+=("$file_name") 69 | printf '\t%s\n' "Found docker image archive: ${file_name}" 70 | else 71 | printf '\t%s\n' "Ignoring invalid docker archive: ${file_name}" >&2 72 | fi 73 | done 74 | fi 75 | echo 76 | 77 | if [[ "${#SCAN_FILES[@]}" -gt 0 ]]; then 78 | for file in "${SCAN_FILES[@]}"; do 79 | prepare_image "${file}" 80 | done 81 | else 82 | printf '\n\t%s\n\n' "ERROR - No valid docker archives provided." >&2 83 | display_usage >&2 84 | exit 1 85 | fi 86 | 87 | if [[ "${b_flag}" ]]; then 88 | (anchore-cli --json policy add "${POLICY_BUNDLE}" | jq '.policyId' | xargs anchore-cli policy activate) || \ 89 | printf "\n%s\n" "Unable to activate policy bundle - ${POLICY_BUNDLE} -- using default policy bundle." >&2 90 | fi 91 | 92 | if [[ "${#FINISHED_IMAGES[@]}" -ge 1 ]]; then 93 | if [[ "${r_flag}" ]]; then 94 | for image in "${FINISHED_IMAGES[@]}"; do 95 | anchore_ci_tools.py -r --image "${image}" 96 | done 97 | fi 98 | echo 99 | for image in "${FINISHED_IMAGES[@]}"; do 100 | printf '\t%s\n' "Policy Evaluation - ${image##*/}" 101 | printf '%s\n\n' "-----------------------------------------------------------" 102 | (set +o pipefail; anchore-cli evaluate check "${image}" --detail | tee /dev/null; set -o pipefail) 103 | done 104 | 105 | if [[ "${f_flag}" ]]; then 106 | for image in "${FINISHED_IMAGES[@]}"; do 107 | anchore-cli evaluate check "${image}" 108 | done 109 | fi 110 | fi 111 | } 112 | 113 | get_and_validate_options() { 114 | # Parse options 115 | while getopts ':d:b:i:fhr' option; do 116 | case "${option}" in 117 | d ) d_flag=true; DOCKERFILE="/anchore-engine/$(basename $OPTARG)";; 118 | b ) b_flag=true; POLICY_BUNDLE="/anchore-engine/$(basename $OPTARG)";; 119 | i ) i_flag=true; IMAGE_NAME="${OPTARG}";; 120 | f ) f_flag=true;; 121 | r ) r_flag=true;; 122 | h ) display_usage; exit;; 123 | \? ) printf "\n\t%s\n\n" " Invalid option: -${OPTARG}" >&2; display_usage >&2; exit 1;; 124 | : ) printf "\n\t%s\n\n%s\n\n" " Option -${OPTARG} requires an argument." >&2; display_usage >&2; exit 1;; 125 | esac 126 | done 127 | 128 | shift "$((OPTIND - 1))" 129 | 130 | # Test options to ensure they're all valid. Error & display usage if not. 131 | if [[ "${d_flag}" ]] && [[ ! "${i_flag}" ]]; then 132 | printf '\n\t%s\n\n' "ERROR - must specify an image when passing a Dockerfile." >&2 133 | display_usage >&2 134 | exit 1 135 | elif [[ "${d_flag}" ]] && [[ ! -f "${DOCKERFILE}" ]]; then 136 | printf '\n\t%s\n\n' "ERROR - Can not find dockerfile at: ${DOCKERFILE}" >&2 137 | display_usage >&2 138 | exit 1 139 | elif [[ "${b_flag}" ]] && [[ ! -f "${POLICY_BUNDLE}" ]]; then 140 | printf '\n\t%s\n\n' "ERROR - Can not find policy bundle file at: ${POLICY_BUNDLE}" >&2 141 | display_usage >&2 142 | exit 1 143 | fi 144 | 145 | if [[ "${i_flag}" ]]; then 146 | if [[ -f "/anchore-engine/${IMAGE_NAME##*/}" ]] && [[ "${IMAGE_NAME}" =~ [.]tar? ]]; then 147 | FILE_NAME="/anchore-engine/${IMAGE_NAME##*/}" 148 | elif [[ "${IMAGE_NAME}" =~ (.*/|)([a-zA-Z0-9_.-]+)[:]([a-zA-Z0-9_.-]*) ]]; then 149 | FILE_NAME="/anchore-engine/${IMAGE_NAME##*/}.tar" 150 | if [[ ! -f "${FILE_NAME}" ]]; then 151 | cat <&0 > "${FILE_NAME}" 152 | fi 153 | # Transform file name for skopeo functionality, replace : with _ 154 | if [[ "${FILE_NAME}" =~ [:] ]]; then 155 | local new_file_name="/tmp/$(basename ${FILE_NAME//:/_})" 156 | mv "${FILE_NAME}" "${new_file_name}" 157 | FILE_NAME="${new_file_name}" 158 | fi 159 | else 160 | printf '\n\t%s\n\n' "ERROR - Could not find image file ${IMAGE_NAME}" >&2 161 | display_usage >&2 162 | exit 1 163 | fi 164 | fi 165 | } 166 | 167 | prepare_image() { 168 | local file="$1" 169 | 170 | if [[ -z "${IMAGE_NAME##*/}" ]]; then 171 | if [[ "${file}" =~ ([a-zA-Z0-9_.-]+)([:]|[_])([a-zA-Z0-9_.-]*)[.]tar$ ]]; then 172 | local image_repo="${BASH_REMATCH[1]}" 173 | local image_tag="${BASH_REMATCH[3]}" 174 | else 175 | local image_repo=$(basename "${file%%.*}") 176 | local image_tag="latest" 177 | fi 178 | elif [[ "${IMAGE_NAME##*/}" =~ (.*/|)([a-zA-Z0-9_.-]+)[:]([a-zA-Z0-9_.-]*) ]]; then 179 | local image_repo="${BASH_REMATCH[2]}" 180 | local image_tag="${BASH_REMATCH[3]:-latest}" 181 | elif [[ "${IMAGE_NAME##*/}" =~ ([a-zA-Z0-9_.-]*) ]]; then 182 | local image_repo="${BASH_REMATCH[1]}" 183 | local image_tag='latest' 184 | else 185 | printf '\n\t%s\n\n' "ERROR - Could not parse image file name ${IMAGE_NAME}" >&2 186 | display_usage >&2 187 | exit 1 188 | fi 189 | 190 | local anchore_image_name="${ANCHORE_ENDPOINT_HOSTNAME}:5000/${image_repo}:${image_tag}" 191 | printf '%s\n\n' "Preparing ${IMAGE_NAME} for analysis" 192 | # pass to background process & wait, required to handle keyboard interrupt when running container non-interactively. 193 | skopeo copy --dest-tls-verify=false "docker-archive:${file}" "docker://${anchore_image_name}" & 194 | wait_proc="$!" 195 | wait "${wait_proc}" 196 | printf '\n%s\n' "Image archive loaded into Anchore Engine using tag -- ${anchore_image_name#*/}" 197 | 198 | start_scan "${file}" "${anchore_image_name}" 199 | } 200 | 201 | start_scan() { 202 | local file="$1" 203 | local anchore_image_name="$2" 204 | local wait_proc="" 205 | 206 | if [[ "${d_flag}" ]] && [[ -f "${DOCKERFILE}" ]]; then 207 | anchore-cli image add "${anchore_image_name}" --dockerfile "${DOCKERFILE}" > /dev/null 208 | else 209 | anchore-cli image add "${anchore_image_name}" > /dev/null 210 | fi 211 | 212 | # pass to background process & wait, required to handle keyboard interrupt when running container non-interactively. 213 | anchore_ci_tools.py --wait --timeout "${TIMEOUT}" --image "${anchore_image_name}" & 214 | wait_proc="$!" 215 | wait "${wait_proc}" 216 | 217 | FINISHED_IMAGES+=("${anchore_image_name}") 218 | } 219 | 220 | error() { 221 | set +e 222 | printf '\n\n\t%s\n\n' "ERROR - $0 received SIGTERM or SIGINT" >&2 223 | # kill python process in wait loop 224 | (pkill -f python3 &> /dev/null) 225 | # kill skopeo process in wait loop 226 | (pkill -f skopeo &> /dev/null) 227 | exit 130 228 | } 229 | 230 | main "$@" -------------------------------------------------------------------------------- /examples/jenkins/pipeline/direct_api/Jenkinsfile: -------------------------------------------------------------------------------- 1 | // Jenkinsfile example for Anchore Engine compliance and security analysis of a container image in a docker v2 registry. 2 | // This example expects Anchore Engine installed and available over the network. It uses curl to interact with Anchore Engine API directly 3 | // 4 | // Configure the environment section accordingly before using this snippet in your Jenkins Pipeline. 5 | // Successful run produces the following artifacts in the Jenkins workspace. Use Jenkins archive step (not included) to persist these artifacts. 6 | // Ensure proper cleanup of these artifacts before running the snippet or use some combination of Jenkins job name and build ID in file path of the 7 | // generated artifacts to avoid conflicting with other build results 8 | // 9 | // - anchore_vulnerabilities_report.json 10 | // - anchore_policy_evaluation_report.json 11 | // - anchore_content_report_os.json 12 | // - anchore_content_report_files.json 13 | // - anchore_content_report_npm.json 14 | // - anchore_content_report_gem.json 15 | // - anchore_content_report_python.json 16 | // - anchore_content_report_java.json 17 | 18 | 19 | import groovy.json.JsonOutput 20 | import groovy.json.JsonSlurper 21 | 22 | pipeline { 23 | agent any 24 | 25 | stages { 26 | stage('Anchore Scan') { 27 | environment { 28 | ANCHORE_ENGINE_URL = "http://192.168.1.146:8228/v1" 29 | ANCHORE_ENGINE_CREDENTIALS = credentials('anchore-engine-credentials') // Jenkins credentials reference 30 | ANCHORE_ENGINE_POLICY_POLICY_ID = "" // if left blank, anchore engine defaults to active bundle for policy evaluation 31 | ANCHORE_ENGINE_ANALYSIS_TIMEOUT_MINUTES = 5 // time in minutes to wait for analysis to complete 32 | ANCHORE_ENGINE_SLEEP_SECONDS = 5 // wait time in seconds between retries 33 | ANCHORE_ENGINE_VULNERABILITIES = "anchore_vulnerabilities_report.json" 34 | ANCHORE_ENGINE_POLICY_EVALUATION = "anchore_policy_evaluation_report.json" 35 | ANCHORE_ENGINE_CONTENT_PREFIX = "anchore_content_report_" 36 | 37 | TAG_TO_BE_ANALYZED = "docker.io/library/alpine:latest" 38 | } 39 | steps { 40 | script { 41 | node { 42 | echo "Starting Anchore Scan Stage" 43 | 44 | def output = "anchore_curl_output" 45 | def curler = "curl -ks -w '%{http_code}' -u ${ANCHORE_ENGINE_CREDENTIALS}" 46 | def error_msg = "" 47 | 48 | // Add image for analysis. parse digest and analysis state from response 49 | echo "Adding ${TAG_TO_BE_ANALYZED} to Anchore Engine for analysis" 50 | def request_body = JsonOutput.toJson([tag: "${TAG_TO_BE_ANALYZED}"]) 51 | def cmd = "${curler} -o ${output} -X POST -H 'Content-Type: application/json' -d '${request_body}' '${ANCHORE_ENGINE_URL}/images?autosubscribe=false'" 52 | // echo "cli: ${cmd}" 53 | def http_status = sh script: cmd, returnStdout: true 54 | http_status = http_status.trim().toInteger() 55 | if (http_status != 200 && http_status != 202) { 56 | error_msg = readFile(file: output) 57 | error "Failed to add ${TAG_TO_BE_ANALYZED} to Anchore Engine for analysis. HTTP status: ${http_status}, message: ${error_msg}" 58 | } 59 | def analysis_response_json = readFile(file: output) 60 | def analysis_response_map = new JsonSlurper().parseText(analysis_response_json).get(0) 61 | def image_digest = analysis_response_map.imageDigest 62 | def analysis_status = analysis_response_map.analysis_status 63 | analysis_response_map = null // unset to avoid serialization error 64 | echo "Received image digest: ${image_digest}, analysis status: ${analysis_status}" 65 | 66 | // Get image and check status 67 | cmd = "${curler} -o ${output} -X GET '${ANCHORE_ENGINE_URL}/images/${image_digest}'" 68 | echo "Checking analysis status for image digest ${image_digest}" 69 | timeout (time: "${ANCHORE_ENGINE_ANALYSIS_TIMEOUT_MINUTES}", unit: 'MINUTES') { 70 | while (true) { 71 | http_status = sh script: cmd, returnStdout: true 72 | http_status = http_status.trim().toInteger() 73 | if (http_status != 200 && http_status != 202) { 74 | error_msg = readFile(file: output) 75 | error "Failed to get image info from Anchore Engine. HTTP status: ${http_status}, message: ${error_msg}" 76 | } 77 | def image_response_json = readFile(file: output) 78 | def image_response_map = new JsonSlurper().parseText(image_response_json).get(0) 79 | def image_status = image_response_map.analysis_status 80 | image_response_map = null // unset to avoid serialization error 81 | if (image_status == 'analyzed') { 82 | echo "Image analyzed" 83 | break 84 | } else if (image_status == 'analyzing' || image_status == 'not_analyzed') { 85 | echo "Image status is ${image_status}, will retry after ${ANCHORE_ENGINE_SLEEP_SECONDS} seconds" 86 | sleep time: "${ANCHORE_ENGINE_SLEEP_SECONDS}", unit: 'SECONDS' 87 | } else { 88 | error "Failed due to image status ${image_status}" 89 | } 90 | } 91 | } 92 | 93 | // Fetching all reports 94 | echo "Fetching all reports for image digest ${image_digest}" 95 | def report_gen_error = false 96 | 97 | // Get vuln report 98 | echo "Fetching vulnerability listing" 99 | output = "${ANCHORE_ENGINE_VULNERABILITIES}" 100 | cmd = "${curler} -o ${output} -X GET '${ANCHORE_ENGINE_URL}/images/${image_digest}/vuln/all'" 101 | http_status = sh script: cmd, returnStdout: true 102 | http_status = http_status.trim().toInteger() 103 | if (http_status != 200 && http_status != 202) { 104 | report_gen_error = true 105 | error_msg = readFile(file: output) 106 | echo "ERROR: Failed to get vulnerabilities listing from Anchore Engine. HTTP status: ${http_status}, message: ${error_msg}" 107 | } 108 | 109 | // Get policy evaluation report 110 | echo "Fetching policy evaluation" 111 | output = "${ANCHORE_ENGINE_POLICY_EVALUATION}" 112 | try { // check for environment variable. this is hacky but there does not seem to be a better way for checking environment variables 113 | cmd = "${curler} -o ${output} -X GET '${ANCHORE_ENGINE_URL}/images/${image_digest}/check?policyId=${ANCHORE_ENGINE_POLICY_POLICY_ID}&tag=${TAG_TO_BE_ANALYZED}&detail=true'" 114 | } catch (e) { 115 | cmd = "${curler} -o ${output} -X GET '${ANCHORE_ENGINE_URL}/images/${image_digest}/check?tag=${TAG_TO_BE_ANALYZED}&detail=true'" 116 | } 117 | http_status = sh script: cmd, returnStdout: true 118 | http_status = http_status.trim().toInteger() 119 | if (http_status != 200 && http_status != 202) { 120 | report_gen_error = true 121 | error_msg = readFile(file: output) 122 | echo "ERROR: Failed to get policy evaluation from Anchore Engine. HTTP status: ${http_status}, message: ${error_msg}" 123 | } 124 | 125 | // Get os image content 126 | echo "Fetching os content" 127 | output = "${ANCHORE_ENGINE_CONTENT_PREFIX}os.json" 128 | cmd = "${curler} -o ${output} -X GET '${ANCHORE_ENGINE_URL}/images/${image_digest}/content/os'" 129 | http_status = sh script: cmd, returnStdout: true 130 | http_status = http_status.trim().toInteger() 131 | if (http_status != 200 && http_status != 202) { 132 | report_gen_error = true 133 | error_msg = readFile(file: output) 134 | echo "ERROR: Failed to get os content from Anchore Engine. HTTP status: ${http_status}, message: ${error_msg}" 135 | } 136 | 137 | // Get files image content 138 | echo "Fetching files content" 139 | output = "${ANCHORE_ENGINE_CONTENT_PREFIX}files.json" 140 | cmd = "${curler} -o ${output} -X GET '${ANCHORE_ENGINE_URL}/images/${image_digest}/content/files'" 141 | http_status = sh script: cmd, returnStdout: true 142 | http_status = http_status.trim().toInteger() 143 | if (http_status != 200 && http_status != 202) { 144 | report_gen_error = true 145 | error_msg = readFile(file: output) 146 | echo "ERROR: Failed to get files content from Anchore Engine. HTTP status: ${http_status}, message: ${error_msg}" 147 | } 148 | 149 | // Get npm image content 150 | echo "Fetching npm content" 151 | output = "${ANCHORE_ENGINE_CONTENT_PREFIX}npm.json" 152 | cmd = "${curler} -o ${output} -X GET '${ANCHORE_ENGINE_URL}/images/${image_digest}/content/npm'" 153 | http_status = sh script: cmd, returnStdout: true 154 | http_status = http_status.trim().toInteger() 155 | if (http_status != 200 && http_status != 202) { 156 | report_gen_error = true 157 | error_msg = readFile(file: output) 158 | echo "ERROR: Failed to get npm content from Anchore Engine. HTTP status: ${http_status}, message: ${error_msg}" 159 | } 160 | 161 | // Get gem image content 162 | echo "Fetching gem content" 163 | output = "${ANCHORE_ENGINE_CONTENT_PREFIX}gem.json" 164 | cmd = "${curler} -o ${output} -X GET '${ANCHORE_ENGINE_URL}/images/${image_digest}/content/gem'" 165 | http_status = sh script: cmd, returnStdout: true 166 | http_status = http_status.trim().toInteger() 167 | if (http_status != 200 && http_status != 202) { 168 | report_gen_error = true 169 | error_msg = readFile(file: output) 170 | echo "ERROR: Failed to get gem content from Anchore Engine. HTTP status: ${http_status}, message: ${error_msg}" 171 | } 172 | 173 | // Get python image content 174 | echo "Fetching python content" 175 | output = "${ANCHORE_ENGINE_CONTENT_PREFIX}python.json" 176 | cmd = "${curler} -o ${output} -X GET '${ANCHORE_ENGINE_URL}/images/${image_digest}/content/python'" 177 | http_status = sh script: cmd, returnStdout: true 178 | http_status = http_status.trim().toInteger() 179 | if (http_status != 200 && http_status != 202) { 180 | report_gen_error = true 181 | error_msg = readFile(file: output) 182 | echo "ERROR: Failed to get python content from Anchore Engine. HTTP status: ${http_status}, message: ${error_msg}" 183 | } 184 | 185 | // Get java image content 186 | echo "Fetching java content" 187 | output = "${ANCHORE_ENGINE_CONTENT_PREFIX}java.json" 188 | cmd = "${curler} -o ${output} -X GET '${ANCHORE_ENGINE_URL}/images/${image_digest}/content/java'" 189 | http_status = sh script: cmd, returnStdout: true 190 | http_status = http_status.trim().toInteger() 191 | if (http_status != 200 && http_status != 202) { 192 | report_gen_error = true 193 | error_msg = readFile(file: output) 194 | echo "ERROR: Failed to get java content from Anchore Engine. HTTP status: ${http_status}, message: ${error_msg}" 195 | } 196 | 197 | if (report_gen_error) { 198 | error "Failed to get one or more reports from Anchore Engine, check above logs for errors" 199 | } 200 | 201 | echo "Completed Anchore Scan Stage" 202 | } // node 203 | } // script 204 | } // steps 205 | } // stage 206 | } // stages 207 | } // pipeline 208 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /scripts/anchore_ci_tools.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import json 5 | import os 6 | import requests 7 | import subprocess 8 | import sys 9 | import time 10 | import re 11 | 12 | global ALL_CONTENT_TYPES 13 | global ALL_REPORT_COMMANDS 14 | global ALL_VULN_TYPES 15 | 16 | ALL_CONTENT_TYPES = ['os', 'python', 'java', 'gem', 'npm', 'files'] 17 | ALL_REPORT_COMMANDS = { 18 | 'content': 'anchore-cli --json image content', 19 | 'vuln': 'anchore-cli --json image vuln', 20 | 'details': 'anchore-cli --json image get', 21 | 'policy': 'anchore-cli --json evaluate check' 22 | } 23 | ALL_VULN_TYPES = ['all', 'non-os', 'os'] 24 | 25 | 26 | def setup_parser(): 27 | content_type_choices = [type for type in ALL_CONTENT_TYPES] 28 | content_type_choices.append('all') 29 | report_type_choices = [type for type in ALL_REPORT_COMMANDS.keys()] 30 | report_type_choices.append('all') 31 | vuln_type_choices = ALL_VULN_TYPES 32 | 33 | parser = argparse.ArgumentParser(description="A tool that automates various anchore engine functions for CI pipelines. Intended to be run directly on the anchore/anchore-engine container.") 34 | parser.add_argument('-a', '--analyze', action='store_true', help="Specify if you want image to be analyzed by anchore engine.") 35 | parser.add_argument('-r', '--report', action='store_true', help="Generate reports on analyzed image.") 36 | parser.add_argument('-s', '--setup', action='store_true', help="Sets up & starts anchore engine on running container.") 37 | parser.add_argument('-w','--wait', action='store_true', help="Wait for anchore engine to start up.") 38 | parser.add_argument('--image', help="Specify the image name. REQUIRED for analyze and report options.") 39 | parser.add_argument('--timeout', default=300, type=int, help="Set custom timeout (in seconds) for image analysis and/or engine setup.") 40 | parser.add_argument('--content', nargs='+', choices=content_type_choices, default='all', help="Specify what content reports to generate. Can pass multiple options. Ignored if --type content not specified. Available options are: [{}]".format(', '.join(content_type_choices)), metavar='') 41 | parser.add_argument('--type', nargs='+', choices=report_type_choices, default='all', help="Specify what report types to generate. Can pass multiple options. Available options are: [{}]".format(', '.join(report_type_choices)), metavar='') 42 | parser.add_argument('--vuln', choices=vuln_type_choices, default='all', help="Specify what vulnerability reports to generate. Available options are: [{}] ".format(', '.join(vuln_type_choices)), metavar='') 43 | 44 | return parser 45 | 46 | 47 | def add_image(image_name): 48 | image_basename = re.match(r'(?:.+\/)?([^:+].+)', image_name).group(1) 49 | print ("\nImage submitted to Anchore Engine: {}".format(image_basename), flush=True) 50 | cmd = 'anchore-cli --json image add {}'.format(image_name).split() 51 | 52 | try: 53 | output = subprocess.check_output(cmd, stderr=subprocess.STDOUT) 54 | except subprocess.CalledProcessError as error: 55 | output = error.output 56 | raise Exception ("Failed to add image to anchore engine. Error: {}".format(output.decode('utf-8'))) 57 | 58 | img_details = json.loads(output.decode('utf-8')) 59 | img_digest = img_details[0]['imageDigest'] 60 | 61 | return img_digest 62 | 63 | 64 | def generate_reports(image_name, content_type=['all'], report_type=['all'], vuln_type='all', report_directory="anchore-reports"): 65 | image_basename = re.match(r'(?:.+\/)?([^:+].+)', image_name).group(1) 66 | if 'all' in content_type: 67 | content_type = ALL_CONTENT_TYPES 68 | 69 | if 'all' in report_type: 70 | report_type = ALL_REPORT_COMMANDS.keys() 71 | 72 | for type in report_type: 73 | if type not in ALL_REPORT_COMMANDS.keys(): 74 | raise Exception ("{} is not a valid report type.".format(type)) 75 | 76 | for type in content_type: 77 | if type not in ALL_CONTENT_TYPES: 78 | raise Exception ("{} is not a valid content report type.".format(type)) 79 | 80 | if vuln_type not in ALL_VULN_TYPES: 81 | raise Exception ("{} is not a valid vulnerability report type.".format(type)) 82 | 83 | report_dir = report_directory 84 | if not os.path.exists(report_dir): 85 | os.makedirs(report_dir) 86 | 87 | # Copy ALL_REPORT_COMMANDS dictionary but filter on report_type arg. 88 | active_report_cmds = {k:ALL_REPORT_COMMANDS[k] for k in report_type} 89 | 90 | # loop through active_report_cmds dict and run specified commands for all report_types 91 | # generate log files from output of run commands 92 | for report in active_report_cmds.keys(): 93 | if report == 'content': 94 | for type in content_type: 95 | file_name = '{}/{}-{}-{}.json'.format(report_dir, image_basename.replace(':', '_'), report, type) 96 | cmd = '{} {} {}'.format(ALL_REPORT_COMMANDS[report], image_name, type).split() 97 | write_log_from_output(cmd, file_name) 98 | 99 | elif report == 'policy': 100 | file_name = '{}/{}-{}.json'.format(report_dir, image_basename.replace(':', '_'), report) 101 | cmd = '{} {} --detail'.format(ALL_REPORT_COMMANDS[report], image_name).split() 102 | write_log_from_output(cmd, file_name, ignore_exit_code=True) 103 | 104 | elif report == 'vuln': 105 | file_name = '{}/{}-{}.json'.format(report_dir, image_basename.replace(':', '_'), report) 106 | cmd = '{} {} {}'.format(ALL_REPORT_COMMANDS[report], image_name, vuln_type).split() 107 | write_log_from_output(cmd, file_name) 108 | 109 | else: 110 | file_name = '{}/{}-{}.json'.format(report_dir, image_basename.replace(':', '_'), report) 111 | cmd = '{} {}'.format(ALL_REPORT_COMMANDS[report], image_name).split() 112 | write_log_from_output(cmd, file_name) 113 | 114 | return True 115 | 116 | 117 | def get_config(config_path='/config/config.yaml', config_url='https://raw.githubusercontent.com/anchore/ci-tools/master/conf/stateless_ci_config.yaml'): 118 | conf_dir = os.path.dirname(config_path) 119 | if not os.path.exists(conf_dir): 120 | os.makedirs(conf_dir) 121 | 122 | with requests.get(config_url, stream=True) as r: 123 | if r.status_code == 200: 124 | with open(config_path, 'wb') as file: 125 | file.write(r.content) 126 | else: 127 | raise Exception ("Failed to download config file {} - response httpcode={} data={}".format(config_url, r.status_code, r.text)) 128 | 129 | return True 130 | 131 | 132 | def get_image_digest(img_name, user='admin', pw='foobar', engine_url='http://localhost:8228/v1/images'): 133 | cmd = 'anchore-cli --json image get {}'.format(img_name).split() 134 | 135 | try: 136 | output = subprocess.check_output(cmd, stderr=subprocess.STDOUT) 137 | except subprocess.CalledProcessError as error: 138 | output = error.output 139 | raise Exception ("Failed to get image digest. Error: {}".format(output.decode('utf-8'))) 140 | 141 | img_details = json.loads(output.decode('utf-8')) 142 | img_digest = img_details[0]['imageDigest'] 143 | 144 | return img_digest 145 | 146 | 147 | def get_image_info(img_digest, user='admin', pw='foobar', engine_url='http://localhost:8228/v1/'): 148 | engine_url=re.sub('[/]$', '', engine_url) 149 | url = '{}/images/{}'.format(engine_url, img_digest) 150 | r = requests.get(url, auth=(user, pw), verify=False, timeout=20) 151 | 152 | if r.status_code == 200: 153 | img_info = json.loads(r.text)[0] 154 | return img_info 155 | else: 156 | raise Exception ("Bad response from Anchore Engine - httpcode={} data={}".format(r.status_code, r.text)) 157 | 158 | 159 | def is_engine_running(): 160 | cmd = 'ps aux'.split() 161 | output = subprocess.check_output(cmd) 162 | output = output.decode('utf-8') 163 | 164 | if 'anchore-manager' in output or 'twistd' in output: 165 | return True 166 | else: 167 | return False 168 | 169 | 170 | def is_image_analyzed(image_digest, user='admin', pw='foobar', engine_url='http://localhost:8228/v1/'): 171 | image_info = get_image_info(image_digest, user=user, pw=pw, engine_url=engine_url) 172 | img_status = image_info['analysis_status'] 173 | 174 | if img_status == 'analyzed': 175 | return True, img_status 176 | if img_status == 'analysis_failed': 177 | parsed_image_info = json.dumps(image_info, indent=2) 178 | raise Exception("Image analysis failed. Image details:\n{}".format(parsed_image_info)) 179 | else: 180 | return False, img_status 181 | 182 | 183 | def is_service_available(url, user='admin', pw='foobar'): 184 | try: 185 | r = requests.get(url, auth=(user, pw), verify=False, timeout=10) 186 | if r.status_code == 200: 187 | status = "ready" 188 | return True, status 189 | else: 190 | status = "not_ready" 191 | return False, status 192 | except Exception: 193 | status = "not_ready" 194 | return False, status 195 | 196 | 197 | def print_status_message(last_status, status): 198 | if not status == last_status: 199 | print ("\n\tStatus: {}".format(status), end='', flush=True) 200 | else: 201 | print (".", end='', flush=True) 202 | 203 | return True 204 | 205 | 206 | def start_anchore_engine(): 207 | if not is_engine_running(): 208 | cmd = 'anchore-manager service start --all'.split() 209 | print ("Starting anchore engine...", flush=True) 210 | try: 211 | with open('anchore-engine.log', 'w') as out: 212 | subprocess.Popen(cmd, stdout=out, stderr=subprocess.STDOUT) 213 | except Exception as error: 214 | raise Exception ("Unable to start anchore engine. Exception: {}".format(error)) 215 | 216 | return True 217 | else: 218 | raise Exception ("Anchore engine is already running.") 219 | 220 | 221 | def wait_engine_available(health_check_urls=[], timeout=300, user='admin', pw='foobar'): 222 | start_ts = time.time() 223 | last_status = str() 224 | 225 | for url in health_check_urls: 226 | is_available = False 227 | while not is_available: 228 | if time.time() - start_ts >= timeout: 229 | raise Exception("Timed out after {} seconds.".format(timeout)) 230 | is_available, status = is_service_available(url=url, user=user, pw=pw) 231 | if is_available: 232 | break 233 | print_status_message(last_status, status) 234 | last_status = status 235 | time.sleep(5) 236 | 237 | print ("\n\nAnchore Engine is available!\n", flush=True) 238 | 239 | return True 240 | 241 | 242 | def wait_image_analyzed(image_digest, timeout=300, user='admin', pw='foobar', engine_url='http://localhost:8228/v1/'): 243 | print ("Waiting for analysis to complete...", flush=True) 244 | last_img_status = str() 245 | is_analyzed = False 246 | start_ts = time.time() 247 | 248 | while not is_analyzed: 249 | if time.time() - start_ts >= timeout: 250 | raise Exception("Timed out after {} seconds.".format(timeout)) 251 | is_analyzed, img_status = is_image_analyzed(image_digest, user=user, pw=pw, engine_url=engine_url) 252 | print_status_message(last_img_status, img_status) 253 | if is_analyzed: 254 | break 255 | last_img_status = img_status 256 | time.sleep(5) 257 | 258 | print ("\n\nAnalysis completed!\n", flush=True) 259 | 260 | return True 261 | 262 | 263 | def write_log_from_output(command, file_name, ignore_exit_code=False): 264 | skip_empty_values = ['vulnerabilities', 'content'] 265 | try: 266 | output = subprocess.check_output(command) 267 | output_json = json.loads(output) 268 | if not type(output_json) is dict or not bool(set(output_json.keys()).intersection(skip_empty_values)) or [x for x in output_json.keys() if x in skip_empty_values and not output_json[x] == []]: 269 | with open(file_name, 'w') as file: 270 | file.write(output.decode('utf-8')) 271 | else: 272 | return False 273 | 274 | except subprocess.CalledProcessError as error: 275 | output = error.output.decode('utf-8') 276 | output_json = json.loads(output) 277 | if ignore_exit_code: 278 | if not type(output_json) is dict or not bool(set(output_json.keys()).intersection(skip_empty_values)) or [x for x in output_json.keys() if x in skip_empty_values and not output_json[x] == []]: 279 | with open(file_name, 'w') as file: 280 | file.write(output) 281 | else: 282 | return False 283 | else: 284 | print ("Failed to generate {}. Exception: {} \n {}".format(file_name, error, output), flush=True) 285 | return False 286 | 287 | print ("Successfully generated {}.".format(file_name), flush=True) 288 | 289 | return True 290 | 291 | 292 | ### MAIN PROGRAM STARTS HERE ### 293 | def main(arg_parser): 294 | parser = arg_parser 295 | 296 | # setup vars for arguments passed to script 297 | args = parser.parse_args() 298 | analyze_image = args.analyze 299 | content_type = args.content 300 | generate_report = args.report 301 | image_name = args.image 302 | report_type = args.type 303 | setup_engine = args.setup 304 | timeout = args.timeout 305 | vuln_type = args.vuln 306 | wait_engine = args.wait 307 | 308 | if len(sys.argv) <= 1 : 309 | parser.print_help() 310 | raise Exception ("\n\nERROR - Must specify at least one option.") 311 | 312 | if wait_engine and (setup_engine or generate_report or analyze_image): 313 | parser.print_help() 314 | raise Exception ("\n\nERROR - The --wait option can only be used with the --image option or standalone.") 315 | 316 | if setup_engine and (image_name or generate_report or analyze_image): 317 | parser.print_help() 318 | raise Exception ("\n\nERROR - Cannot analyze image or generate reports until engine is setup.") 319 | 320 | if (generate_report or analyze_image) and not image_name: 321 | parser.print_help() 322 | raise Exception ("\n\nERROR - Cannot analyze image or generate a report without specifying an image name.") 323 | 324 | if image_name and not (generate_report or analyze_image or wait_engine): 325 | parser.print_help() 326 | raise Exception ("\n\nERROR - Must specify an action to perform on image. Please include --report or --analyze") 327 | 328 | anchore_env_vars = { 329 | 'ANCHORE_HOST_ID' : 'localhost', 330 | 'ANCHORE_ENDPOINT_HOSTNAME' : 'localhost', 331 | 'ANCHORE_CLI_URL' : 'http://localhost:8228/v1/', 332 | 'ANCHORE_CLI_USER' : 'admin', 333 | 'ANCHORE_CLI_PASS' : 'foobar', 334 | 'ANCHORE_CLI_SSL_VERIFY' : 'n' 335 | } 336 | 337 | # set default anchore cli environment variables if they aren't already set 338 | for var in anchore_env_vars.keys(): 339 | if var not in os.environ: 340 | os.environ[var] = anchore_env_vars[var] 341 | 342 | anchore_user = os.environ['ANCHORE_CLI_USER'] 343 | anchore_pw = os.environ['ANCHORE_CLI_PASS'] 344 | anchore_cli_url = os.environ['ANCHORE_CLI_URL'] 345 | feeds_url='{}/system/feeds'.format(re.sub('[/]$', '', anchore_cli_url)) 346 | health_url='{}/health'.format(re.sub('/v1[/]*$', '', anchore_cli_url)) 347 | 348 | if wait_engine: 349 | if image_name: 350 | img_digest = get_image_digest(image_name, user=anchore_user, pw=anchore_pw) 351 | wait_image_analyzed(img_digest, timeout=timeout, user=anchore_user, pw=anchore_pw, engine_url=anchore_cli_url) 352 | else: 353 | wait_engine_available(health_check_urls=[health_url, feeds_url], timeout=timeout, user=anchore_user, pw=anchore_pw) 354 | 355 | elif setup_engine: 356 | get_config() 357 | start_anchore_engine() 358 | wait_engine_available(health_check_urls=[health_url, feeds_url], timeout=timeout, user=anchore_user, pw=anchore_pw) 359 | 360 | elif image_name: 361 | if analyze_image: 362 | img_digest = add_image(image_name) 363 | wait_image_analyzed(img_digest, timeout=timeout, user=anchore_user, pw=anchore_pw, engine_url=anchore_cli_url) 364 | if generate_report: 365 | generate_reports(image_name, content_type, report_type, vuln_type) 366 | 367 | else: 368 | parser.print_help() 369 | raise Exception ("\n\nError processing command arguments for {}.".format(sys.argv[0])) 370 | 371 | 372 | if __name__ == '__main__': 373 | try: 374 | arg_parser = setup_parser() 375 | main(arg_parser) 376 | except KeyboardInterrupt: 377 | print ("\n\nReceived interupt signal. Exiting...") 378 | sys.exit(130) 379 | except Exception as error: 380 | print ("\n\nERROR executing script - Exception: {}".format(error)) 381 | sys.exit(1) 382 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Fail on any errors, including in pipelines 4 | # Don't allow unset variables. Trace all functions with DEBUG trap 5 | set -eo pipefail -o functrace 6 | 7 | display_usage() { 8 | echo "${color_yellow}" 9 | cat << EOF 10 | Anchore Build Pipeline --- 11 | 12 | CI pipeline script for Anchore container images. 13 | Allows building container images & mocking CI pipelines. 14 | 15 | The following overide environment variables are available: 16 | 17 | SKIP_CLEANUP = [ true | false ] - skips cleanup job that runs on exit (kills containers & removes workspace) 18 | IMAGE_REPO = docker.io/example/test - specify a custom image repo to build/test 19 | WORKING_DIRECTORY = /home/test/workdir - used as a temporary workspace for build/test 20 | WORKSPACE = /home/test/workspace - used to store temporary artifacts 21 | 22 | Usage: ${0##*/} [ -s ] [ build | test | ci | function_name ] < build_version > 23 | 24 | s - Build slim preloaded image without nvd2 feed data 25 | build - Build image tagged IMAGE_REPO:dev using specified version of anchore-engine & engine-db-preload 26 | test - Run test pipeline on specified image version locally on your workstation 27 | ci - Run mocked CircleCI pipeline using Docker-in-Docker 28 | function_name - Invoke a function directly using build environment 29 | EOF 30 | echo "${color_normal}" 31 | } 32 | 33 | ############################################## 34 | ### PROJECT SPECIFIC ENVIRONMENT SETUP ### 35 | ############################################## 36 | 37 | # Specify what versions to build & what version should get 'latest' tag 38 | export BUILD_VERSIONS=('v0.10.2' 'v0.10.1' 'v0.10.0' 'v0.9.4') 39 | export LATEST_VERSION='v0.10.2' 40 | 41 | # PROJECT_VARS are custom vars that are modified between projects 42 | # Expand all required ENV vars or set to default values with := variable substitution 43 | # Use eval on $CIRCLE_WORKING_DIRECTORY to ensure default value (~/project) gets expanded to the absolute path 44 | PROJECT_VARS=( \ 45 | "IMAGE_REPO=${IMAGE_REPO:=anchore/inline-scan}" \ 46 | "PROJECT_REPONAME=${CIRCLE_PROJECT_REPONAME:=ci-tools}" \ 47 | "WORKING_DIRECTORY=${WORKING_DIRECTORY:=$(eval echo ${CIRCLE_WORKING_DIRECTORY:="${HOME}/tempci_${IMAGE_REPO##*/}_${RANDOM}/project"})}" \ 48 | "WORKSPACE=${WORKSPACE:=$(dirname "$WORKING_DIRECTORY")/workspace}" \ 49 | ) 50 | # These vars are static & defaults should not need to be changed 51 | PROJECT_VARS+=( \ 52 | "CI=${CI:=false}" \ 53 | "GIT_BRANCH=${CIRCLE_BRANCH:=$(git rev-parse --abbrev-ref HEAD || echo 'dev')}" \ 54 | "SKIP_FINAL_CLEANUP=${SKIP_FINAL_CLEANUP:=false}" \ 55 | ) 56 | 57 | 58 | ######################################## 59 | ### MAIN PROGRAM BOOTSTRAP LOGIC ### 60 | ######################################## 61 | 62 | main() { 63 | if [[ "$#" -eq 0 ]]; then 64 | echo "ERROR - $0 requires at least 1 input" >&2 65 | display_usage >&2 66 | exit 1 67 | fi 68 | 69 | while getopts ':sh' option; do 70 | case "${option}" in 71 | s ) s_flag=true;; 72 | h ) display_usage; exit;; 73 | \? ) printf "\n\t%s\n\n" "Invalid option: -${OPTARG}" >&2; display_usage >&2; exit 1;; 74 | : ) printf "\n\t%s\n\n" "Option -${OPTARG} requires an argument." >&2; display_usage >&2; exit 1;; 75 | esac 76 | done 77 | shift "$((OPTIND - 1))" 78 | 79 | PROJECT_VARS+=( \ 80 | "SLIM_BUILD=${s_flag:=false}" \ 81 | ) 82 | 83 | # Save current working directory for cleanup on exit 84 | pushd . &> /dev/null 85 | 86 | # Trap all signals that cause script to exit & run cleanup function before exiting 87 | trap 'cleanup' SIGINT SIGTERM ERR EXIT 88 | trap 'printf "\n%s+ PIPELINE ERROR - exit code %s - cleaning up %s\n" "${color_red}" "$?" "${color_normal}"' SIGINT SIGTERM ERR 89 | 90 | # Get ci_utils.sh from anchore test-infra repo - used for common functions 91 | # If running on test-infra container ci_utils.sh is installed to /usr/local/bin/ 92 | # if [[ -f /usr/local/bin/ci_utils.sh ]]; then 93 | # source ci_utils.sh 94 | # elif [[ -f "${WORKSPACE}/test-infra/scripts/ci_utils.sh" ]]; then 95 | # source "${WORKSPACE}/test-infra/scripts/ci_utils.sh" 96 | # else 97 | # git clone https://github.com/anchore/test-infra "${WORKSPACE}/test-infra" 98 | # source "${WORKSPACE}/test-infra/scripts/ci_utils.sh" 99 | # fi 100 | 101 | # Setup terminal colors for printing 102 | export TERM=xterm 103 | color_red=$(tput setaf 1) 104 | color_cyan=$(tput setaf 6) 105 | color_yellow=$(tput setaf 3) 106 | color_normal=$(tput setaf 9) 107 | echo 108 | 109 | setup_and_print_env_vars 110 | 111 | # Trap all bash commands & print to screen. Like using set -v but allows printing in color 112 | trap 'printf "%s+ %s%s\n" "${color_cyan}" "$BASH_COMMAND" "${color_normal}" >&2' DEBUG 113 | 114 | # Use the 'build' param to build the latest image 115 | if [[ "$1" == 'build' ]]; then 116 | setup_build_environment 117 | build_image "${2:-latest}" 118 | # Use the 'test' param to execute the full pipeline locally on latest versions 119 | elif [[ "$1" == 'test' ]]; then 120 | build_and_save_images "${2:-dev}" 121 | test_built_images "${2:-dev}" 122 | load_image_and_push_dockerhub "${2:-dev}" 123 | # Use the 'ci' param to execute a fully mocked CircleCI pipeline, utilizing docker in docker 124 | elif [[ "$1" == 'ci' ]]; then 125 | setup_build_environment 126 | ci_test_job 'docker.io/anchore/test-infra:latest' 'build_and_save_images' 127 | ci_test_job 'docker.io/anchore/test-infra:latest' 'test_built_images' 128 | ci_test_job 'docker.io/anchore/test-infra:latest' 'load_image_and_push_dockerhub' 129 | else 130 | # If first param is a valid function name, execute the function & pass all following params to function 131 | export SKIP_FINAL_CLEANUP=true 132 | if declare -f "$1" > /dev/null; then 133 | "$@" 134 | else 135 | display_usage >&2 136 | printf "%sERROR - %s is not a valid function name %s\n" "${color_red}" "$1" "${color_normal}" >&2 137 | exit 1 138 | fi 139 | fi 140 | } 141 | 142 | # The cleanup() function that runs whenever the script exits 143 | cleanup() { 144 | ret="$?" 145 | set +euo pipefail 146 | if [[ "${ret}" -eq 0 ]]; then 147 | set +o functrace 148 | fi 149 | if [[ "${SKIP_FINAL_CLEANUP}" == false ]]; then 150 | deactivate 2> /dev/null 151 | docker-compose down --volumes 2> /dev/null 152 | if [[ "${#DOCKER_RUN_IDS[@]}" -ne 0 ]]; then 153 | for i in "${DOCKER_RUN_IDS[@]}"; do 154 | docker kill $i 2> /dev/null 155 | docker rm $i 2> /dev/null 156 | done 157 | fi 158 | popd &> /dev/null 159 | if [[ "${WORKING_DIRECTORY}" =~ 'tempci' ]]; then 160 | rm -rf $(dirname "${WORKING_DIRECTORY}") 161 | fi 162 | else 163 | echo "Workspace Dir: ${WORKSPACE}" 164 | echo "Working Dir: ${WORKING_DIRECTORY}" 165 | fi 166 | popd &> /dev/null 167 | exit "${ret}" 168 | } 169 | 170 | ################################################################# 171 | ### FUNCTIONS CALLED DIRECTLY BY CIRCLECI - RUNTIME ORDER ### 172 | ################################################################# 173 | 174 | build_and_save_images() { 175 | local build_version="$1" 176 | echo "build_version=${build_version}" 177 | setup_build_environment 178 | # build images for every specified version 179 | if [[ "${build_version}" == 'all' ]]; then 180 | for version in "${BUILD_VERSIONS[@]}"; do 181 | echo "Building ${IMAGE_REPO}:dev-${version}" 182 | git reset --hard 183 | # exit script if tag does not exist 184 | git checkout "tags/${version}" || { if [[ "${CI}" == 'false' ]]; then true && local no_tag=true; else exit 1; fi; }; 185 | build_image "${version}" 186 | test_inline_image "${version}" 187 | save_image "${version}" 188 | # Move back to previously checked out branch 189 | if ! "${no_tag:=false}"; then 190 | git reset --hard 191 | git checkout @{-1} 192 | fi 193 | unset no_tag 194 | done 195 | else 196 | echo "Buiding ${IMAGE_REPO}:${build_version}" 197 | if [[ "${build_version}" == 'latest' ]]; then 198 | git reset --hard 199 | git checkout "tags/${LATEST_VERSION}" 200 | elif [[ ! "${build_version}" == 'dev' ]]; then 201 | git reset --hard 202 | git checkout "tags/${build_version}" 203 | fi 204 | build_image "${build_version}" 205 | test_inline_image "${build_version}" 206 | save_image "${build_version}" 207 | if [[ ! "${build_version}" == 'dev' ]]; then 208 | git reset --hard 209 | git checkout @{-1} 210 | fi 211 | fi 212 | } 213 | 214 | test_built_images() { 215 | local build_version="$1" 216 | echo "build_version=${build_version}" 217 | setup_build_environment 218 | if [[ "${build_version}" == 'all' ]]; then 219 | for version in "${BUILD_VERSIONS[@]}"; do 220 | unset ANCHORE_CI_IMAGE 221 | load_image "${version}" 222 | export ANCHORE_CI_IMAGE="${IMAGE_REPO}:dev-${version}" 223 | git reset --hard 224 | git checkout "tags/${version}" || { if [[ "$CI" == 'false' ]]; then true && local no_tag=true; else exit 1; fi; }; 225 | if [[ "${CI}" == 'true' ]]; then 226 | test_inline_script "https://raw.githubusercontent.com/anchore/ci-tools/${version}/scripts/inline_scan" 227 | else 228 | test_inline_script "${WORKING_DIRECTORY}/scripts/inline_scan" 229 | fi 230 | # Move back to previously checked out branch 231 | if ! "${no_tag:=false}"; then 232 | git reset --hard 233 | git checkout @{-1} 234 | fi 235 | unset no_tag 236 | done 237 | else 238 | load_image "${build_version}" 239 | export ANCHORE_CI_IMAGE="${IMAGE_REPO}:dev" 240 | if [[ "${build_version}" == 'latest' || "${build_version}" == 'dev' ]] && [[ "${CI}" == 'true' ]] ; then 241 | test_inline_script "https://raw.githubusercontent.com/anchore/ci-tools/${GIT_BRANCH}/scripts/inline_scan" 242 | elif [[ "${build_version}" == 'latest' || "${build_version}" == 'dev' ]] && [[ "${CI}" == 'false' ]] ; then 243 | test_inline_script "${WORKING_DIRECTORY}/scripts/inline_scan" 244 | else 245 | test_inline_script "https://raw.githubusercontent.com/anchore/ci-tools/${build_version}/scripts/inline_scan" 246 | fi 247 | fi 248 | } 249 | 250 | load_image_and_push_dockerhub() { 251 | local build_version="$1" 252 | echo "build_version=${build_version}" 253 | setup_build_environment 254 | if [[ "${build_version}" == 'all' ]]; then 255 | for version in "${BUILD_VERSIONS[@]}"; do 256 | load_image "${version}" 257 | push_dockerhub "${version}" 258 | done 259 | else 260 | load_image "${build_version}" 261 | push_dockerhub "${build_version}" 262 | fi 263 | } 264 | 265 | 266 | ########################################################### 267 | ### PROJECT SPECIFIC FUNCTIONS - ALPHABETICAL ORDER ### 268 | ########################################################### 269 | 270 | build_image() { 271 | local anchore_version="$1" 272 | echo "anchore_version=${anchore_version}" 273 | # if the DB_PRELOAD_IMAGE variable is set, use it as the engine-db-preload image 274 | if [[ -z "${DB_PRELOAD_IMAGE}" ]]; then 275 | if [[ "${SLIM_BUILD}" == 'true' ]]; then 276 | local db_image="anchore/engine-db-preload-slim:${anchore_version}" 277 | else 278 | local db_image="anchore/engine-db-preload:${anchore_version}" 279 | fi 280 | else 281 | local db_image="${DB_PRELOAD_IMAGE}" 282 | fi 283 | if ! docker pull "${db_image}" &>/dev/null && ! docker inspect "${db_image}" &>/dev/null; then 284 | db_image="anchore/engine-db-preload:latest" 285 | docker pull "${db_image}" 286 | fi 287 | 288 | echo "Copying anchore-bootstrap.sql.gz from ${db_image} image..." 289 | db_preload_id=$(docker run -d --entrypoint tail "${db_image}" /dev/null | tail -n1) 290 | docker cp "${db_preload_id}:/docker-entrypoint-initdb.d/anchore-bootstrap.sql.gz" "${WORKING_DIRECTORY}/anchore-bootstrap.sql.gz" 291 | DOCKER_RUN_IDS+=("${db_preload_id:0:6}") 292 | 293 | if [[ "${anchore_version}" == "dev" ]]; then 294 | docker build --build-arg "ANCHORE_REPO=anchore/anchore-engine-dev" --build-arg "ANCHORE_VERSION=latest" -t "${IMAGE_REPO}:dev" . 295 | elif [[ "${anchore_version}" == "issue-712" ]]; then 296 | docker build --build-arg "ANCHORE_REPO=anchore/anchore-engine-dev" --build-arg "ANCHORE_VERSION=issue-712" -t "${IMAGE_REPO}:dev" . 297 | else 298 | docker build --build-arg "ANCHORE_VERSION=${anchore_version}" -t "${IMAGE_REPO}:dev" . 299 | fi 300 | local docker_name 301 | docker_name="${RANDOM:-temp}-db-preload" 302 | docker run -it --name "${docker_name}" "${IMAGE_REPO}:dev" debug /bin/bash -c "anchore-cli system wait --feedsready 'vulnerabilities' && anchore-cli system status && anchore-cli system feeds list" 303 | local docker_id 304 | docker_id=$(docker inspect ${docker_name} | jq '.[].Id') 305 | docker kill "${docker_id}" && docker rm "${docker_id}" 306 | DOCKER_RUN_IDS+=("${docker_id:0:6}") 307 | rm -f "${WORKING_DIRECTORY}/anchore-bootstrap.sql.gz" 308 | 309 | docker tag "${IMAGE_REPO}:dev" "${IMAGE_REPO}:dev-${anchore_version}" 310 | } 311 | 312 | install_dependencies() { 313 | # No dependencies to install for this project 314 | true 315 | } 316 | 317 | test_inline_image() { 318 | local anchore_version="$1" 319 | echo "anchore_version=${anchore_version}" 320 | export ANCHORE_CI_IMAGE="${IMAGE_REPO}:dev" 321 | cat "${WORKING_DIRECTORY}/scripts/inline_scan" | bash -s -- -p alpine:latest ubuntu:latest 322 | } 323 | 324 | test_inline_script() { 325 | local INLINE_URL="$1" 326 | if [[ "${INLINE_URL}" =~ 'http' ]]; then 327 | local exec_cmd=curl 328 | else 329 | local exec_cmd=cat 330 | fi 331 | ${exec_cmd} -s "${INLINE_URL}" | bash -s -- -p -t 900 centos:latest 332 | # test script with dockerfile 333 | docker pull docker:stable-git 334 | ${exec_cmd} -s "${INLINE_URL}" | bash -s -- -t 900 -d ".circleci/Dockerfile" docker:stable-git 335 | # test script with policy bundle 336 | ${exec_cmd} -s "${INLINE_URL}" | bash -s -- -p -t 1500 -b ".circleci/.anchore/policy_bundle.json" "anchore/anchore-engine:latest" 337 | # test script with policy bundle & dockerfile 338 | pushd "${WORKING_DIRECTORY}/.circleci/node_critical_pass/" 339 | docker build -t example.com:5000/ci-test_1/node_critical-pass:latest . 340 | popd 341 | ${exec_cmd} -s "${INLINE_URL}" | bash -s -- -t 900 -d ".circleci/node_critical_pass/Dockerfile" -b ".circleci/.anchore/policy_bundle.json" example.com:5000/ci-test_1/node_critical-pass 342 | } 343 | 344 | 345 | ######################################################## 346 | ### COMMON HELPER FUNCTIONS - ALPHABETICAL ORDER ### 347 | ######################################################## 348 | 349 | ci_test_job() { 350 | local ci_image=$1 351 | local ci_function=$2 352 | local docker_name="${RANDOM:-TEMP}-ci-test" 353 | docker run --net host -it --name "${docker_name}" -v "$(dirname "${WORKING_DIRECTORY}"):$(dirname "${WORKING_DIRECTORY}"):delegated" -v /var/run/docker.sock:/var/run/docker.sock "${ci_image}" /bin/sh -c "\ 354 | cd $(dirname "${WORKING_DIRECTORY}") && \ 355 | cp ${WORKING_DIRECTORY}/scripts/build.sh $(dirname "${WORKING_DIRECTORY}")/build.sh && \ 356 | export WORKING_DIRECTORY=${WORKING_DIRECTORY} && \ 357 | sudo -E bash $(dirname "${WORKING_DIRECTORY}")/build.sh ${ci_function} \ 358 | " 359 | local docker_id 360 | docker_id=$(docker inspect ${docker_name} | jq '.[].Id') 361 | docker kill "${docker_id}" && docker rm "${docker_id}" 362 | DOCKER_RUN_IDS+=("${docker_id:0:6}") 363 | } 364 | 365 | load_image() { 366 | local anchore_version="$1" 367 | docker load -i "${WORKSPACE}/caches/${PROJECT_REPONAME}-${anchore_version}-dev.tar" 368 | docker tag ${IMAGE_REPO}:dev ${IMAGE_REPO}:dev-${anchore_version} 369 | } 370 | 371 | push_dockerhub() { 372 | local anchore_version="$1" 373 | if [[ "${CI}" == true ]]; then 374 | echo "${DOCKER_PASS}" | docker login -u "${DOCKER_USER}" --password-stdin 375 | fi 376 | # Push :latest or :[semver] tags to Dockerhub if on 'master' branch or a vX.X semver tag, and running in CircleCI 377 | if [[ "${GIT_BRANCH}" == 'master' || "${CIRCLE_TAG:=false}" =~ ^v[0-9]+(\.[0-9]+)*$ ]] && [[ "${CI}" == true ]] && [[ ! "${anchore_version}" == 'latest' ]]; then 378 | docker tag "${IMAGE_REPO}:dev-${anchore_version}" "${IMAGE_REPO}:${anchore_version}" 379 | echo "Pushing to DockerHub - ${IMAGE_REPO}:${anchore_version}" 380 | docker push "${IMAGE_REPO}:${anchore_version}" 381 | if [ "${anchore_version}" == "${LATEST_VERSION}" ]; then 382 | docker tag "${IMAGE_REPO}:dev-${anchore_version}" "${IMAGE_REPO}:latest" 383 | echo "Pushing to DockerHub - ${IMAGE_REPO}:latest" 384 | docker push "${IMAGE_REPO}:latest" 385 | fi 386 | else 387 | docker tag "${IMAGE_REPO}:dev-${anchore_version}" "anchore/private_testing:${IMAGE_REPO##*/}-${anchore_version}" 388 | echo "Pushing to DockerHub - anchore/private_testing:${IMAGE_REPO##*/}-${anchore_version}" 389 | if [[ "${CI}" == false ]]; then 390 | sleep 10 391 | fi 392 | docker push "anchore/private_testing:${IMAGE_REPO##*/}-${anchore_version}" 393 | fi 394 | } 395 | 396 | save_image() { 397 | local anchore_version="$1" 398 | mkdir -p "${WORKSPACE}/caches" 399 | docker save -o "${WORKSPACE}/caches/${PROJECT_REPONAME}-${anchore_version}-dev.tar" "${IMAGE_REPO}:dev" 400 | } 401 | 402 | setup_and_print_env_vars() { 403 | # Export & print all project env vars to the screen 404 | echo "${color_yellow}" 405 | printf "%s\n\n" "- ENVIRONMENT VARIABLES SET -" 406 | echo "BUILD_VERSIONS=${BUILD_VERSIONS[*]}" 407 | printf "%s\n" "LATEST_VERSION=${LATEST_VERSION}" 408 | for var in "${PROJECT_VARS[@]}"; do 409 | export "${var?}" 410 | printf "%s" "${color_yellow}" 411 | printf "%s\n" "${var}" 412 | done 413 | echo "${color_normal}" 414 | # If running tests manually, sleep for a few seconds to give time to visually double check that ENV is setup correctly 415 | if [[ "$CI" == false ]]; then 416 | sleep 5 417 | fi 418 | # Setup a variable for docker image cleanup at end of script 419 | declare -a DOCKER_RUN_IDS 420 | export DOCKER_RUN_IDS 421 | } 422 | 423 | setup_build_environment() { 424 | # Copy source code to $WORKING_DIRECTORY for mounting to docker volume as working dir 425 | if [[ ! -d "${WORKING_DIRECTORY}" ]]; then 426 | mkdir -p "${WORKING_DIRECTORY}" 427 | cp -a . "${WORKING_DIRECTORY}" 428 | fi 429 | mkdir -p "${WORKSPACE}/caches" 430 | pushd "${WORKING_DIRECTORY}" 431 | install_dependencies || true 432 | } 433 | 434 | main "$@" 435 | -------------------------------------------------------------------------------- /scripts/inline_scan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail 4 | 5 | ######################## 6 | ### GLOBAL VARIABLES ### 7 | ######################## 8 | 9 | # If using a locally built stateless CI container, export ANCHORE_CI_IMAGE=. 10 | # This will override the image name from Dockerhub. 11 | INLINE_SCAN_IMAGE="${ANCHORE_CI_IMAGE:-docker.io/anchore/inline-scan:v0.10.2}" 12 | DOCKER_NAME="${RANDOM:-temp}-inline-anchore-engine" 13 | DOCKER_ID="" 14 | ANALYZE=false 15 | VULN_SCAN=false 16 | CREATE_CMD=() 17 | RUN_CMD=() 18 | COPY_CMDS=() 19 | IMAGE_NAMES=() 20 | IMAGE_FILES=() 21 | SCAN_IMAGES=() 22 | FAILED_IMAGES=() 23 | VALIDATED_OPTIONS="" 24 | # Vuln scan option variable defaults 25 | DOCKERFILE="./Dockerfile" 26 | POLICY_BUNDLE="./policy_bundle.json" 27 | TIMEOUT=600 28 | VOLUME_PATH="/tmp/" 29 | # Analyzer option variable defaults 30 | ANCHORE_URL="http://localhost:8228" 31 | ANCHORE_USER="admin" 32 | ANCHORE_PASS="foobar" 33 | ANCHORE_ANNOTATIONS="foo=bar" 34 | IMAGE_DIGEST_SHA="sha256:123456890abcdefg" 35 | ANCHORE_IMAGE_ID="123456890abcdefg" 36 | MANIFEST_FILE="./manifest.json" 37 | 38 | deprecation_notice() { 39 | cat << EOF 40 | 41 | **WARNING** 42 | 43 | The Anchore Inline Scan script is deprecated and will reach EOL on Jan 10, 2022. 44 | The vulnerability database will no longer be kept up to date and new images will no longer be published. 45 | 46 | Please update your integrations to use our new CLI vulnerability scanning tool, grype. 47 | 48 | https://github.com/anchore/grype 49 | 50 | **WARNING** 51 | 52 | EOF 53 | sleep 10 54 | } 55 | 56 | display_usage() { 57 | cat << EOF 58 | 59 | Anchore Engine Inline Scanner/Analyzer -- 60 | 61 | Wrapper script for performing vulnerability scan or image analysis on local docker images, utilizing the Anchore Engine inline_scan container. 62 | For more detailed usage instructions use the -h option after specifying scan or analyze. 63 | 64 | Usage: ${0##*/} [ OPTIONS ] 65 | 66 | EOF 67 | } 68 | 69 | display_usage_scanner() { 70 | cat << EOF 71 | 72 | Anchore Engine Inline Scan -- 73 | 74 | Script for performing vulnerability scan on local docker images, utilizing the Anchore Engine inline_scan container. 75 | Multiple images can be passed for scanning if a Dockerfile is not specified. 76 | 77 | Images should be built & tagged locally, or remote images can be pulled with the -p option. 78 | 79 | Usage: ${0##*/} [ OPTIONS ] <....> 80 | 81 | -b [optional] Path to local Anchore policy bundle (ex: -b ./policy_bundle.json) 82 | -d [optional] Path to local Dockerfile (ex: -d ./dockerfile) 83 | -t [optional] Specify timeout for image scanning in seconds. Defaults to 300s. (ex: -t 500) 84 | -f [optional] Exit script upon failed Anchore policy evaluation 85 | -p [optional] Pull remote docker images 86 | -r [optional] Generate analysis reports in your current working directory 87 | -V [optional] Increase verbosity 88 | 89 | EOF 90 | } 91 | 92 | display_usage_analyzer() { 93 | cat << EOF 94 | 95 | Anchore Engine Inline Analyzer -- 96 | 97 | Script for performing analysis on local docker images, utilizing the Anchore Engine analyzer subsystem. 98 | After image is analyzed, the resulting Anchore image archive is sent to a remote Anchore Engine installation 99 | using the -r option. This allows inline_analysis data to be persisted & utilized for reporting. 100 | 101 | Images should be built & tagged locally. 102 | 103 | Usage: ${0##*/} analyze -r -u -p [ OPTIONS ] 104 | 105 | -r [required] URL to remote Anchore Engine API endpoint (ex: -r 'https://anchore.example.com:8228/v1') 106 | -u [required] Username for remote Anchore Engine auth (ex: -u 'admin') 107 | -p [required] Password for remote Anchore Engine auth (ex: -p 'foobar') 108 | 109 | -a [optional] Add annotations (ex: -a 'key=value,key=value') 110 | -d [optional] Specify image digest (ex: -d 'sha256:<64 hex characters>') 111 | -f [optional] Path to Dockerfile (ex: -f ./Dockerfile) 112 | -i [optional] Specify image ID used within Anchore Engine (ex: -i '<64 hex characters>') 113 | -m [optional] Path to Docker image manifest (ex: -m ./manifest.json) 114 | -t [optional] Specify timeout for image analysis in seconds. Defaults to 300s. (ex: -t 500) 115 | -g [optional] Generate an image digest from docker save tarball 116 | -P [optional] Pull docker image from registry 117 | -V [optional] Increase verbosity 118 | 119 | EOF 120 | } 121 | 122 | main() { 123 | trap 'cleanup' EXIT ERR SIGTERM 124 | trap 'interupt' SIGINT 125 | 126 | if [[ "$#" -lt 1 ]]; then 127 | deprecation_notice >&2 128 | display_usage >&2 129 | printf '\n\t%s\n\n' "ERROR - must specify operation ('scan' or 'analyze')" >&2 130 | exit 1 131 | fi 132 | if [[ "$1" == 'help' ]]; then 133 | deprecation_notice >&2 134 | display_usage >&2 135 | exit 1 136 | elif [[ "$1" == 'analyze' ]]; then 137 | shift "$((OPTIND))" 138 | deprecation_notice >&2 139 | ANALYZE=true 140 | get_and_validate_analyzer_options "$@" 141 | get_and_validate_images "${VALIDATED_OPTIONS}" 142 | prepare_inline_container 143 | CREATE_CMD+=('analyze') 144 | RUN_CMD+=('analyze') 145 | start_analysis 146 | deprecation_notice >&2 147 | else 148 | if [[ "$1" == 'scan' ]]; then 149 | shift "$((OPTIND))" 150 | fi 151 | deprecation_notice >&2 152 | VULN_SCAN=true 153 | get_and_validate_scanner_options "$@" 154 | get_and_validate_images "${VALIDATED_OPTIONS}" 155 | prepare_inline_container 156 | CREATE_CMD+=('scan') 157 | RUN_CMD+=('scan') 158 | start_vuln_scan 159 | deprecation_notice >&2 160 | fi 161 | } 162 | 163 | get_and_validate_analyzer_options() { 164 | #Parse options 165 | while getopts ':r:u:p:a:d:f:i:m:t:PgVh' option; do 166 | case "${option}" in 167 | r ) r_flag=true; ANCHORE_URL="${OPTARG%%/v1}";; 168 | u ) u_flag=true; ANCHORE_USER="${OPTARG}";; 169 | p ) p_flag=true; ANCHORE_PASS="${OPTARG}";; 170 | a ) a_flag=true; ANCHORE_ANNOTATIONS="${OPTARG}";; 171 | d ) d_flag=true; IMAGE_DIGEST_SHA="${OPTARG}";; 172 | f ) f_flag=true; DOCKERFILE="${OPTARG}";; 173 | i ) i_flag=true; ANCHORE_IMAGE_ID="${OPTARG}";; 174 | m ) m_flag=true; MANIFEST_FILE="${OPTARG}";; 175 | t ) t_flag=true; TIMEOUT="${OPTARG}";; 176 | P ) P_flag=true;; 177 | g ) g_flag=true;; 178 | V ) V_flag=true;; 179 | h ) display_usage_analyzer; exit;; 180 | \? ) printf "\n\t%s\n\n" "Invalid option: -${OPTARG}" >&2; display_usage_analyzer >&2; exit 1;; 181 | : ) printf "\n\t%s\n\n%s\n\n" "Option -${OPTARG} requires an argument." >&2; display_usage_analyzer >&2; exit 1;; 182 | esac 183 | done 184 | shift "$((OPTIND - 1))" 185 | 186 | # Check for invalid options 187 | if [[ ! $(which docker) ]]; then 188 | printf '\n\t%s\n\n' 'ERROR - Docker is not installed or cannot be found in $PATH' >&2 189 | display_usage_analyzer >&2 190 | exit 1 191 | elif [[ "${#@}" -gt 1 ]]; then 192 | printf '\n\t%s\n\n' "ERROR - only 1 image can be analyzed at a time" >&2 193 | display_usage_analyzer >&2 194 | exit 1 195 | elif [[ "${#@}" -lt 1 ]]; then 196 | printf '\n\t%s\n\n' "ERROR - must specify an image to analyze" >&2 197 | display_usage_analyzer >&2 198 | exit 1 199 | # validate URL is functional anchore-engine api endpoint 200 | elif [[ ! "${r_flag}" ]]; then 201 | printf '\n\t%s\n\n' "ERROR - must provide an anchore-engine endpoint" >&2 202 | display_usage_analyzer >&2 203 | exit 1 204 | elif ! curl -s --fail "${ANCHORE_URL%%/}/v1" > /dev/null; then 205 | printf '\n\t%s\n\n' "ERROR - invalid anchore-engine endpoint provided - ${ANCHORE_URL}" >&2 206 | display_usage_analyzer >&2 207 | exit 1 208 | # validate user & password are provided & correct 209 | elif [[ ! "${u_flag}" ]] || [[ ! "${p_flag}" ]]; then 210 | printf '\n\t%s\n\n' "ERROR - must provide anchore-engine username & password" >&2 211 | display_usage_analyzer >&2 212 | exit 1 213 | elif ! curl -s --fail -u "${ANCHORE_USER}:${ANCHORE_PASS}" "${ANCHORE_URL%%/}/v1/status" > /dev/null; then 214 | printf '\n\t%s\n\n' "ERROR - invalid anchore-engine username/password provided" >&2 215 | display_usage_analyzer >&2 216 | exit 1 217 | elif [[ "${a_flag}" ]]; then 218 | # transform all commas to spaces & cast to an array 219 | local annotation_array=(${ANCHORE_ANNOTATIONS//,/ }) 220 | # get count of = in annotation string 221 | local number_keys=${ANCHORE_ANNOTATIONS//[^=]} 222 | # compare number of elements in array with number of = in annotation string 223 | if [[ "${#number_keys}" -ne "${#annotation_array[@]}" ]]; then 224 | printf '\n\t%s\n\n' "ERROR - ${ANCHORE_ANNOTATIONS} is not a valid input for -a option" >&2 225 | display_usage_analyzer >&2 226 | exit 1 227 | fi 228 | elif [[ ! "${g_flag}" ]] && [[ ! "${d_flag}" ]] && [[ ! "${m_flag}" ]]; then 229 | printf '\n\t%s\n\n' "ERROR - must provide an image digest, manifest, or specify -g to generate a digest" >&2 230 | display_usage_analyzer >&2 231 | exit 1 232 | elif [[ "${g_flag}" ]] && ([[ "${d_flag}" ]] || [[ "${m_flag}" ]]); then 233 | printf '\n\t%s\n\n' "ERROR - cannot generate digest when a manifest or digest is provided" >&2 234 | display_usage_analyzer >&2 235 | exit 1 236 | elif [[ "${f_flag}" ]] && [[ ! -f "${DOCKERFILE}" ]]; then 237 | printf '\n\t%s\n\n' "ERROR - Dockerfile: ${DOCKERFILE} does not exist" >&2 238 | display_usage_analyzer >&2 239 | exit 1 240 | elif [[ "${m_flag}" ]] && [[ ! -f "${MANIFEST_FILE}" ]];then 241 | printf '\n\t%s\n\n' "ERROR - Manifest: ${MANIFEST_FILE} does not exist" >&2 242 | display_usage_analyzer >&2 243 | exit 1 244 | elif [[ "${t_flag}" ]] && [[ ! "${TIMEOUT}" =~ ^[0-9]+$ ]]; then 245 | printf '\n\t%s\n\n' "ERROR - timeout must be set to a valid integer" >&2 246 | display_usage_analyzer >&2 247 | exit 1 248 | fi 249 | 250 | if [[ "$V_flag" ]]; then 251 | set -x 252 | fi 253 | 254 | VALIDATED_OPTIONS="$@" 255 | } 256 | 257 | get_and_validate_scanner_options() { 258 | # Parse options 259 | while getopts ':d:b:t:fhrVp' option; do 260 | case "${option}" in 261 | d ) d_flag=true; DOCKERFILE="${OPTARG}";; 262 | f ) f_flag=true;; 263 | r ) r_flag=true;; 264 | b ) b_flag=true; POLICY_BUNDLE="${OPTARG}";; 265 | p ) p_flag=true;; 266 | t ) t_flag=true; TIMEOUT="${OPTARG}";; 267 | V ) V_flag=true;; 268 | h ) display_usage_scanner; exit;; 269 | \? ) printf "\n\t%s\n\n" " Invalid option: -${OPTARG}" >&2; display_usage_scanner >&2; exit 1;; 270 | : ) printf "\n\t%s\n\n%s\n\n" " Option -${OPTARG} requires an argument" >&2; display_usage_scanner >&2; exit 1;; 271 | esac 272 | done 273 | shift "$((OPTIND - 1))" 274 | 275 | # Check for invalid options 276 | if [[ "${#@}" -lt 1 ]]; then 277 | printf '\n\t%s\n\n' "ERROR - must specify an image to scan" >&2 278 | display_usage_scanner >&2 279 | exit 1 280 | # validate URL is functional anchore-engine api endpoint 281 | elif [[ ! $(which docker) ]]; then 282 | printf '\n\t%s\n\n' 'ERROR - Docker is not installed or cannot be found in $PATH' >&2 283 | display_usage_scanner >&2 284 | exit 1 285 | elif [[ "${d_flag}" ]] && [[ "${#@}" -gt 1 ]]; then 286 | printf '\n\t%s\n\n' "ERROR - If specifying a Dockerfile, only 1 image can be scanned at a time" >&2 287 | display_usage_scanner >&2 288 | exit 1 289 | elif [[ "${r_flag}" ]] && ! (mkdir -p ./anchore-reports); then 290 | printf '\n\t%s\n\n' "ERROR - ${PWD}/anchore-reports is not writable" >&2 291 | display_usage_scanner >&2 292 | exit 1 293 | elif [[ "${b_flag}" ]] && [[ ! -f "${POLICY_BUNDLE}" ]]; then 294 | printf '\n\t%s\n\n' "ERROR - Policy Bundle: ${POLICY_BUNDLE} does not exist" >&2 295 | display_usage_scanner >&2 296 | exit 1 297 | elif [[ "${d_flag}" ]] && [[ ! -f "${DOCKERFILE}" ]]; then 298 | printf '\n\t%s\n\n' "ERROR - Dockerfile: ${DOCKERFILE} does not exist" >&2 299 | display_usage_scanner >&2 300 | exit 1 301 | elif [[ "${#@}" -eq 0 ]]; then 302 | printf '\n\t%s\n\n' "ERROR - ${0##*/} requires at least 1 image name as input" >&2 303 | display_usage_scanner >&2 304 | exit 1 305 | elif [[ "${t_flag}" ]] && [[ ! "${TIMEOUT}" =~ ^[0-9]+$ ]]; then 306 | printf '\n\t%s\n\n' "ERROR - timeout must be set to a valid integer" >&2 307 | display_usage_scanner >&2 308 | exit 1 309 | fi 310 | 311 | if [[ "$V_flag" ]]; then 312 | set -x 313 | fi 314 | 315 | VALIDATED_OPTIONS="$@" 316 | } 317 | 318 | get_and_validate_images() { 319 | # Add all unique positional input params to IMAGE_NAMES array 320 | for i in $@; do 321 | if [[ ! "${IMAGE_NAMES[@]}" =~ "$i" ]]; then 322 | IMAGE_NAMES+=("$i") 323 | fi 324 | done 325 | 326 | # Make sure all images are available locally, add to FAILED_IMAGES array if not 327 | for i in "${IMAGE_NAMES[@]}"; do 328 | if ([[ "${p_flag}" == true ]] && [[ "${VULN_SCAN}" == true ]]) || [[ "${P_flag}" == true ]]; then 329 | echo "Pulling image -- $i" 330 | docker pull $i || true 331 | fi 332 | 333 | docker inspect "$i" &> /dev/null || FAILED_IMAGES+=("$i") 334 | 335 | if [[ ! "${FAILED_IMAGES[@]}" =~ "$i" ]]; then 336 | SCAN_IMAGES+=("$i") 337 | fi 338 | done 339 | 340 | # Give error message on any invalid image names 341 | if [[ "${#FAILED_IMAGES[@]}" -gt 0 ]]; then 342 | printf '\n%s\n\n' "WARNING - Please pull remote image, or build/tag all local images before attempting analysis again" >&2 343 | 344 | if [[ "${#FAILED_IMAGES[@]}" -ge "${#IMAGE_NAMES[@]}" ]]; then 345 | printf '\n\t%s\n\n' "ERROR - no local docker images specified in script input: ${0##*/} ${IMAGE_NAMES[*]}" >&2 346 | display_usage >&2 347 | exit 1 348 | fi 349 | 350 | for i in "${FAILED_IMAGES[@]}"; do 351 | printf '\t%s\n' "Could not find image locally -- $i" >&2 352 | done 353 | fi 354 | } 355 | 356 | prepare_inline_container() { 357 | # Check if env var is overriding which inline-scan image to utilize. 358 | if [[ -z "${ANCHORE_CI_IMAGE}" ]]; then 359 | printf '\n%s\n' "Pulling ${INLINE_SCAN_IMAGE}" 360 | docker pull "${INLINE_SCAN_IMAGE}" 361 | else 362 | printf '\n%s\n' "Using local image for scanning -- ${INLINE_SCAN_IMAGE}" 363 | fi 364 | 365 | # setup command arrays to eval & run after adding all required options 366 | CREATE_CMD=('docker create --name "${DOCKER_NAME}"') 367 | RUN_CMD=('docker run -i --name "${DOCKER_NAME}"') 368 | 369 | if [[ "${t_flag}" ]]; then 370 | CREATE_CMD+=('-e TIMEOUT="${TIMEOUT}"') 371 | RUN_CMD+=('-e TIMEOUT="${TIMEOUT}"') 372 | fi 373 | if [[ "${V_flag}" ]]; then 374 | CREATE_CMD+=('-e VERBOSE=true') 375 | RUN_CMD+=('-e VERBOSE=true') 376 | fi 377 | 378 | CREATE_CMD+=('"${INLINE_SCAN_IMAGE}"') 379 | RUN_CMD+=('"${INLINE_SCAN_IMAGE}"') 380 | } 381 | 382 | start_vuln_scan() { 383 | if [[ "${f_flag}" ]]; then 384 | CREATE_CMD+=('-f') 385 | RUN_CMD+=('-f') 386 | fi 387 | if [[ "${r_flag}" ]]; then 388 | CREATE_CMD+=('-r') 389 | RUN_CMD+=('-r') 390 | fi 391 | 392 | # If no files need to be copied to container, pipe docker save output to stdin of docker run command. 393 | if [[ ! "${d_flag}" ]] && [[ ! "${b_flag}" ]] && [[ "${#SCAN_IMAGES[@]}" -eq 1 ]]; then 394 | RUN_CMD+=('-i "${SCAN_IMAGES[*]}"') 395 | 396 | # If image is passed without a tag, append :latest to docker save to prevent skopeo manifest error 397 | if [[ ! "${SCAN_IMAGES[*]}" =~ [:]+ ]]; then 398 | docker save "${SCAN_IMAGES[*]}:latest" | eval "${RUN_CMD[*]}" 399 | else 400 | docker save "${SCAN_IMAGES[*]}" | eval "${RUN_CMD[*]}" 401 | fi 402 | else 403 | # Prepare commands for container creation & copying all files to container. 404 | if [[ "${b_flag}" ]]; then 405 | CREATE_CMD+=('-b "${POLICY_BUNDLE}"') 406 | COPY_CMDS+=('docker cp "${POLICY_BUNDLE}" "${DOCKER_NAME}:/anchore-engine/$(basename ${POLICY_BUNDLE})";') 407 | fi 408 | if [[ "${#SCAN_IMAGES[@]}" -eq 1 ]]; then 409 | if [[ "${d_flag}" ]]; then 410 | CREATE_CMD+=('-d "${DOCKERFILE}" -i "${SCAN_IMAGES[*]}"') 411 | COPY_CMDS+=('docker cp "${DOCKERFILE}" "${DOCKER_NAME}:/anchore-engine/$(basename ${DOCKERFILE})";') 412 | else 413 | CREATE_CMD+=('-i "${SCAN_IMAGES[*]}"') 414 | fi 415 | fi 416 | 417 | DOCKER_ID=$(eval "${CREATE_CMD[*]}") 418 | eval "${COPY_CMDS[*]}" 419 | save_and_copy_images 420 | echo 421 | docker start -ia "${DOCKER_NAME}" 422 | fi 423 | } 424 | 425 | start_analysis() { 426 | # Prepare commands for container creation & copying all files to container. 427 | if [[ "${d_flag}" ]]; then 428 | CREATE_CMD+=('-d "${IMAGE_DIGEST_SHA}"') 429 | fi 430 | if [[ "${i_flag}" ]]; then 431 | CREATE_CMD+=('-i "${ANCHORE_IMAGE_ID}"') 432 | fi 433 | if [[ "${a_flag}" ]]; then 434 | CREATE_CMD+=('-a "${ANCHORE_ANNOTATIONS}"') 435 | fi 436 | if [[ "${g_flag}" ]]; then 437 | CREATE_CMD+=('-g') 438 | fi 439 | if [[ "${m_flag}" ]]; then 440 | CREATE_CMD+=('-m "${MANIFEST_FILE}"') 441 | COPY_CMDS+=('docker cp "${MANIFEST_FILE}" "${DOCKER_NAME}:/anchore-engine/$(basename ${MANIFEST_FILE})";') 442 | fi 443 | if [[ "$f_flag" ]]; then 444 | CREATE_CMD+=('-f "${DOCKERFILE}"') 445 | COPY_CMDS+=('docker cp "${DOCKERFILE}" "${DOCKER_NAME}:/anchore-engine/$(basename ${DOCKERFILE})";') 446 | fi 447 | 448 | # finally, get the account from anchore engine for the input username 449 | mkdir -p /tmp/anchore 450 | HCODE=$(curl -sS --output /tmp/anchore/anchore_output.log --write-out "%{http_code}" -u "${ANCHORE_USER}:${ANCHORE_PASS}" "${ANCHORE_URL%%/}/v1/account") 451 | if [[ "${HCODE}" == 200 ]] && [[ -f "/tmp/anchore/anchore_output.log" ]]; then 452 | ANCHORE_ACCOUNT=$(cat /tmp/anchore/anchore_output.log | grep '"name"' | awk -F'"' '{print $4}') 453 | CREATE_CMD+=('-u "${ANCHORE_ACCOUNT}"') 454 | else 455 | printf '\n\t%s\n\n' "ERROR - unable to fetch account information from anchore-engine for specified user" 456 | if [ -f /tmp/anchore/anchore_output.log ]; then 457 | printf '%s\n\n' "***SERVICE RESPONSE****">&2 458 | cat /tmp/anchore/anchore_output.log >&2 459 | printf '\n%s\n' "***END SERVICE RESPONSE****" >&2 460 | fi 461 | exit 1 462 | fi 463 | 464 | CREATE_CMD+=("${SCAN_IMAGES[*]}") 465 | DOCKER_ID=$(eval "${CREATE_CMD[*]}") 466 | eval "${COPY_CMDS[*]}" 467 | save_and_copy_images 468 | echo 469 | docker start -ia "${DOCKER_NAME}" 470 | 471 | local analysis_archive_name="${IMAGE_FILES[*]%.tar}-archive.tgz" 472 | # copy image analysis archive from inline_scan containter to host & curl to remote anchore-engine endpoint 473 | docker cp "${DOCKER_NAME}:/anchore-engine/image-analysis-archive.tgz" "/tmp/anchore/${analysis_archive_name}" 474 | 475 | if [[ -f "/tmp/anchore/${analysis_archive_name}" ]]; then 476 | printf '%s\n' " Analysis complete!" 477 | printf '\n%s\n' "Sending analysis archive to ${ANCHORE_URL%%/}" 478 | HCODE=$(curl -sS --output /tmp/anchore/anchore_output.log --write-out "%{http_code}" -u "${ANCHORE_USER}:${ANCHORE_PASS}" -F "archive_file=@/tmp/anchore/${analysis_archive_name}" "${ANCHORE_URL%%/}/v1/import/images") 479 | if [[ "${HCODE}" != 200 ]]; then 480 | printf '\n\t%s\n\n' "ERROR - unable to POST ${analysis_archive_name} to ${ANCHORE_URL%%/}/v1/import/images" >&2 481 | if [ -f /tmp/anchore/anchore_output.log ]; then 482 | printf '%s\n\n' "***SERVICE RESPONSE****">&2 483 | cat /tmp/anchore/anchore_output.log >&2 484 | printf '\n%s\n' "***END SERVICE RESPONSE****" >&2 485 | fi 486 | exit 1 487 | else 488 | if [ -f /tmp/anchore/anchore_output.log ]; then 489 | cat /tmp/anchore/anchore_output.log 490 | fi 491 | fi 492 | else 493 | printf '\n\t%s\n\n' "ERROR - analysis file invalid: /tmp/anchore/${analysis_archive_name}. An error occured during analysis." >&2 494 | display_usage_analyzer >&2 495 | exit 1 496 | fi 497 | } 498 | 499 | save_and_copy_images() { 500 | # Save all image files to /tmp and copy to created container 501 | for image in "${SCAN_IMAGES[@]}"; do 502 | local base_image_name="${image##*/}" 503 | echo "Saving ${image} for local analysis" 504 | local save_file_name="${base_image_name}.tar" 505 | # IMAGE_FILES is used for storing temp image paths for cleanup at end of script 506 | IMAGE_FILES+=("$save_file_name") 507 | 508 | mkdir -p /tmp/anchore 509 | local save_file_path="/tmp/anchore/${save_file_name}" 510 | 511 | # If image is passed without a tag, append :latest to docker save to prevent skopeo manifest error 512 | if [[ ! "${image}" =~ [:]+ ]]; then 513 | docker save "${image}:latest" -o "${save_file_path}" 514 | else 515 | docker save "${image}" -o "${save_file_path}" 516 | fi 517 | 518 | if [[ -f "${save_file_path}" ]]; then 519 | chmod +r "${save_file_path}" 520 | printf '%s' "Successfully prepared image archive -- ${save_file_path}" 521 | else 522 | printf '\n\t%s\n\n' "ERROR - unable to save docker image to ${save_file_path}." >&2 523 | display_usage >&2 524 | exit 1 525 | fi 526 | 527 | docker cp "${save_file_path}" "${DOCKER_NAME}:/anchore-engine/${save_file_name}" 528 | rm -f "${save_file_path}" 529 | done 530 | } 531 | 532 | interupt() { 533 | cleanup 130 534 | } 535 | 536 | cleanup() { 537 | local ret="$?" 538 | if [[ "${#@}" -ge 1 ]]; then 539 | local ret="$1" 540 | fi 541 | set +e 542 | 543 | if [[ -z "${DOCKER_ID}" ]]; then 544 | DOCKER_ID="${DOCKER_NAME:-$(docker ps -a | grep 'inline-anchore-engine' | awk '{print $1}')}" 545 | fi 546 | 547 | for id in ${DOCKER_ID}; do 548 | local -i timeout=0 549 | while (docker ps -a | grep "${id:0:10}") > /dev/null && [[ "${timeout}" -lt 12 ]]; do 550 | if [[ "${r_flag}" ]]; then 551 | echo "Copying scan reports from ${DOCKER_NAME} to ${PWD}/anchore-reports/" 552 | docker cp "${DOCKER_NAME}:/anchore-engine/anchore-reports/" ./ 553 | fi 554 | docker kill "${id}" &> /dev/null 555 | docker rm "${id}" &> /dev/null 556 | printf '\n%s\n' "Cleaning up docker container: ${id}" 557 | ((timeout=timeout+1)) 558 | sleep 5 559 | done 560 | 561 | if [[ "${timeout}" -ge 12 ]]; then 562 | exit 1 563 | fi 564 | unset DOCKER_ID 565 | done 566 | 567 | if [[ "${#IMAGE_FILES[@]}" -ge 1 ]] || [[ -f /tmp/anchore/anchore_output.log ]]; then 568 | if [[ -d "/tmp/anchore" ]]; then 569 | rm -rf "/tmp/anchore" 570 | fi 571 | fi 572 | 573 | exit "${ret}" 574 | } 575 | 576 | main "$@" 577 | --------------------------------------------------------------------------------