├── .github └── dependabot.yaml ├── .gitignore ├── LICENSE ├── README.md ├── action.yml └── scan.sh /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | - package-ecosystem: github-actions 5 | directory: "/" 6 | schedule: 7 | interval: monthly 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # editor and IDE paraphernalia 2 | .idea 3 | *.swp 4 | *.swo 5 | *~ 6 | *.DS_Store 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # container-image-scan action 2 | 3 | ## Usage 4 | 5 | ### Pre-requisites 6 | 7 | 1. Have a CrowdStrike Container Workload Protection (CWP) subscription 8 | 1. Create an OAUTH2 secret at [https://falcon.crowdstrike.com/support/api-clients-and-keys](https://falcon.crowdstrike.com/support/api-clients-and-keys) 9 | 1. Add your OAUTH2 secret called `FALCON_CLIENT_SECRET` to a GitHub secret at `https://github.com///settings/secrets/actions` 10 | 1. Create a workflow `.yml` file in your `.github/workflows` directory. An [example workflow](#example-workflow) is available below. 11 | For more information, reference the GitHub Help Documentation for [Creating a workflow file](https://help.github.com/en/articles/configuring-a-workflow#creating-a-workflow-file) 12 | 13 | ### Inputs 14 | 15 | - `falcon_client_id`: Your CrowdStrike OAUTH2 Client ID 16 | - `container_repository`: The container image to scan (e.g. `my_image` or `myregistry.io/my_container`) 17 | - `container_tag`: The container tag to scan against (default: `latest`) 18 | - `crowdstrike_region`: The CrowdStrike Cloud region to submit for scanning (default: `us-1`) 19 | - `crowdstrike_score`: The score threshold used to allow for step success (optional, default: `500`) 20 | - `retry_count`: How many attempts will be made to download the scan report before giving up (optional, default: `10`) 21 | - `json_report`: Path to output the json report (optional, default: `None`) 22 | - `log_level`: Set the logging level (optional, default: `INFO`) 23 | 24 | NOTE: Scoring is based on the CrowdStrike vulnerability severity table scoring shown below. 25 | 26 | | Severity | Score | 27 | |--------------------|:-----------| 28 | | Critical | 2000 | 29 | | High | 500 | 30 | | Medium | 100 | 31 | | Low | 20 | 32 | 33 | ### Example Workflow 34 | 35 | Create a workflow (eg: `.github/workflows/scan.yml`): 36 | 37 | ```yaml 38 | name: Scan Container Images 39 | 40 | on: 41 | push: 42 | branches: 43 | - master 44 | 45 | jobs: 46 | scan: 47 | runs-on: ubuntu-latest 48 | steps: 49 | - name: Checkout 50 | uses: actions/checkout@v2 51 | 52 | - name: CrowdStrike Container Image Scan 53 | uses: crowdstrike/container-image-scan-action@v1.1.0 54 | with: 55 | falcon_client_id: 56 | container_repository: docker.io/library/busybox 57 | env: 58 | FALCON_CLIENT_SECRET: "${{ secrets.FALCON_CLIENT_SECRET }}" 59 | ``` 60 | 61 | Alternatively if you want to run all the configurations as secrets, set any the following as environment variables under `env` instead of `uses`: 62 | 63 | ```yaml 64 | name: Scan Container Images 65 | 66 | on: 67 | push: 68 | branches: 69 | - master 70 | 71 | jobs: 72 | scan: 73 | runs-on: ubuntu-latest 74 | steps: 75 | - name: Checkout 76 | uses: actions/checkout@v2 77 | 78 | - name: CrowdStrike Container Image Scan 79 | uses: crowdstrike/container-image-scan-action@v1.1.0 80 | env: 81 | FALCON_CLIENT_ID: "${{ secrets.FALCON_CLIENT_ID }}" 82 | FALCON_CLIENT_SECRET: "${{ secrets.FALCON_CLIENT_SECRET }}" 83 | FALCON_CLOUD_REGION: "{{ secrets.FALCON_CLOUD_REGION }}" 84 | CONTAINER_REPO: "{{ secrets.CONTAINER_REPO }}" 85 | CONTAINER_TAG: "{{ secrets.CONTAINER_TAG }}" 86 | ``` 87 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: "CrowdStrike Container Image Scan" 2 | description: "Scan your container image for vulnerabilities and malware" 3 | author: "The CrowdStrike Community" 4 | branding: 5 | color: red 6 | icon: shield 7 | inputs: 8 | falcon_client_id: 9 | description: "Your CrowdStrike OAUTH2 Client ID" 10 | required: true 11 | container_repository: 12 | description: "The container image to scan (e.g. my_image or myregistry.io/my_container)" 13 | required: true 14 | container_tag: 15 | description: "The container tag to scan against (default: latest)" 16 | default: latest 17 | crowdstrike_region: 18 | description: "The CrowdStrike Cloud region to submit for scanning (default: us-1)" 19 | default: us-1 20 | crowdstrike_score: 21 | description: "Vulnerability score threshold" 22 | default: 500 23 | retry_count: 24 | description: "Scan report download retries" 25 | default: 10 26 | log_level: 27 | description: "Set the logging level (default: INFO)" 28 | default: INFO 29 | json_report: 30 | description: "Export JSON report to specified file (Default: None)" 31 | outputs: 32 | exit-code: 33 | description: "exit code of scan" 34 | value: ${{ steps.scan-image.outputs.exit-code }} 35 | 36 | runs: 37 | using: composite 38 | steps: 39 | - run: "PIP_BREAK_SYSTEM_PACKAGES=1 pip install -r https://raw.githubusercontent.com/CrowdStrike/container-image-scan/main/requirements.txt" 40 | shell: bash 41 | - id: scan-image 42 | run: $GITHUB_ACTION_PATH/scan.sh -u '${{ inputs.falcon_client_id }}' -r '${{ inputs.container_repository }}' -t '${{ inputs.container_tag }}' -c '${{ inputs.crowdstrike_region }}' -s '${{ inputs.crowdstrike_score }}' -R '${{ inputs.retry_count }}' --log-level '${{ inputs.log_level }}' --json-report '${{ inputs.json_report }}' 43 | shell: bash 44 | -------------------------------------------------------------------------------- /scan.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright The CrowdStrike Community 4 | # See License for more info 5 | 6 | set -o errexit 7 | set -o nounset 8 | set -o pipefail 9 | 10 | install_image_scan() { 11 | local cs_py="cs_scanimage.py" 12 | if [[ ! -f "$GITHUB_ACTION_PATH/$cs_py" ]]; then 13 | echo "Installing container-image-scan..." 14 | curl -sSLo scan.tar.gz https://github.com/CrowdStrike/container-image-scan/releases/latest/download/scan.tar.gz 15 | tar -xzf scan.tar.gz 16 | rm -f scan.tar.gz 17 | fi 18 | } 19 | 20 | parse_args() { 21 | local opts="" 22 | while (( "$#" )); do 23 | case "$1" in 24 | -u|--clientid) 25 | if [[ -n ${2:-} ]] ; then 26 | opts="$opts $1 $2" 27 | shift 28 | fi 29 | ;; 30 | -r|--repo) 31 | if [[ -n ${2:-} ]]; then 32 | opts="$opts $1 $2" 33 | shift 34 | fi 35 | ;; 36 | -t|--tag) 37 | if [[ -n ${2:-} ]]; then 38 | opts="$opts $1 $2" 39 | shift 40 | fi 41 | ;; 42 | -c|--cloud) 43 | if [[ -n ${2:-} ]]; then 44 | opts="$opts $1 $2" 45 | shift 46 | fi 47 | ;; 48 | -s|--score_threshold) 49 | if [[ -n ${2:-} ]]; then 50 | opts="$opts $1 $2" 51 | shift 52 | fi 53 | ;; 54 | -R|--retry_count) 55 | if [[ -n ${2:-} ]]; then 56 | opts="$opts $1 $2" 57 | shift 58 | fi 59 | ;; 60 | --log-level) 61 | if [[ -n ${2:-} ]]; then 62 | opts="$opts $1 $2" 63 | shift 64 | fi 65 | ;; 66 | --json-report) 67 | if [[ -n ${2:-} ]]; then 68 | opts="$opts $1 $2" 69 | shift 70 | fi 71 | ;; 72 | --) # end argument parsing 73 | shift 74 | break 75 | ;; 76 | -*) # unsupported flags 77 | >&2 echo "ERROR: Unsupported flag: '$1'" 78 | exit 1 79 | ;; 80 | esac 81 | shift 82 | done 83 | 84 | # set remaining positional arguments (if any) in their proper place 85 | eval set -- "$opts" 86 | 87 | echo "${opts/ /}" 88 | return 0 89 | } 90 | 91 | main() { 92 | local opts 93 | 94 | install_image_scan 95 | 96 | if [ "$#" -gt 1 ]; then 97 | opts=$(parse_args "$@" || exit 1) 98 | 99 | python3 cs_scanimage.py --user-agent github-image-scan $opts 100 | EXIT_CODE=$? 101 | echo "exit-code=$EXIT_CODE" >> "$GITHUB_OUTPUT" 102 | exit $EXIT_CODE 103 | else 104 | python3 cs_scanimage.py --user-agent github-image-scan 105 | EXIT_CODE=$? 106 | echo "exit-code=$EXIT_CODE" >> "$GITHUB_OUTPUT" 107 | exit $EXIT_CODE 108 | fi 109 | } 110 | 111 | main "$@" 112 | --------------------------------------------------------------------------------