diff --git a/.babelrc b/.babelrc
deleted file mode 100644
index 11f1df83f7..0000000000
--- a/.babelrc
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "presets": ["es2015", "stage-3"]
-}
diff --git a/.circleci/chocolatey.config b/.circleci/chocolatey.config
new file mode 100644
index 0000000000..2c50e1d6e9
--- /dev/null
+++ b/.circleci/chocolatey.config
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 0000000000..f4b046df8a
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,1112 @@
+version: '2.1'
+
+orbs:
+ # https://circleci.com/developer/orbs/orb/circleci/windows
+ win: circleci/windows@2.4.1
+ # https://circleci.com/developer/orbs/orb/circleci/aws-cli
+ aws-cli: circleci/aws-cli@2.0.3
+ # https://circleci.com/developer/orbs/orb/circleci/github-cli
+ gh: circleci/github-cli@2.1.0
+ # https://circleci.com/developer/orbs/orb/circleci/go
+ go: circleci/go@1.7.1
+
+parameters:
+ node_version:
+ type: string
+ # https://circleci.com/developer/images/image/cimg/node
+ default: '16.16.0'
+ npm_version:
+ type: string
+ # match whatever's bundled with node_version
+ default: '8.1.2'
+ aws_version:
+ type: string
+ # https://github.com/aws/aws-cli/blob/v2/CHANGELOG.rst
+ default: '2.4.12'
+ gh_version:
+ type: string
+ # https://github.com/cli/cli/releases
+ default: '2.7.0'
+ go_version:
+ type: string
+ # https://go.dev/doc/devel/release
+ default: '1.18.2'
+ mitmproxy_version:
+ type: string
+ # https://go.dev/doc/devel/release
+ default: '7.0.4'
+
+executors:
+ alpine:
+ docker:
+ - image: alpine:3.16.2
+ docker-node:
+ parameters:
+ node_version:
+ type: string
+ default: << pipeline.parameters.node_version >>
+ docker:
+ - image: cimg/node:<< parameters.node_version >>
+ # Using RAM Disk. https://circleci.com/docs/2.0/executor-types/#ram-disks
+ working_directory: /mnt/ramdisk/snyk
+ linux:
+ machine:
+ # https://circleci.com/developer/machine/image/ubuntu-2004
+ image: ubuntu-2004:202201-01
+ linux-arm64:
+ machine:
+ # https://circleci.com/docs/2.0/arm-resources/
+ image: ubuntu-2004:202101-01
+ resource_class: arm.medium
+ macos:
+ macos:
+ # https://circleci.com/docs/2.0/testing-ios/#supported-xcode-versions
+ xcode: '13.3.0'
+
+commands:
+ setup_npm:
+ parameters:
+ node_version:
+ type: string
+ default: << pipeline.parameters.node_version >>
+ npm_version:
+ type: string
+ default: << pipeline.parameters.npm_version >>
+ npm_cache_directory:
+ type: string
+ default: /mnt/ramdisk/.npm
+ npm_global_sudo:
+ type: boolean
+ default: false
+ npm_install:
+ type: boolean
+ default: false
+ steps:
+ - restore_cache:
+ name: Restoring npm cache
+ keys:
+ - npm-cache-v2-{{ arch }}-node<< parameters.node_version >>-npm<< parameters.npm_version >>-{{ checksum "package-lock.json" }}
+ - when:
+ condition:
+ not: << parameters.npm_global_sudo >>
+ steps:
+ run:
+ name: Installing npm
+ command: npm install -g npm@<< parameters.npm_version >>
+ - when:
+ condition: << parameters.npm_global_sudo >>
+ steps:
+ run:
+ name: Installing npm
+ command: sudo npm install -g npm@<< parameters.npm_version >>
+ - run:
+ name: Configuring npm
+ command: |
+ npm config set '//registry.npmjs.org/:_authToken' '${NPM_TOKEN}'
+ npm config set cache << parameters.npm_cache_directory >>
+ npm config set prefer-offline true
+ - when:
+ condition: << parameters.npm_install >>
+ steps:
+ - run:
+ name: Installing project dependencies
+ command: npm ci
+ - save_cache:
+ name: Saving npm cache
+ key: npm-cache-v2-{{ arch }}-node<< parameters.node_version >>-npm<< parameters.npm_version >>-{{ checksum "package-lock.json" }}
+ paths:
+ - << parameters.npm_cache_directory >>
+ install_sdks_windows:
+ steps:
+ - run:
+ name: Removing pre-installed NodeJS
+ command: |
+ $current_node_version = node --version
+ nvm uninstall $current_node_version
+ - restore_cache:
+ name: Restoring Chocolatey cache
+ keys:
+ - chocolatey-cache-v2-{{ arch }}-{{ checksum ".circleci/chocolatey.config" }}
+ - run:
+ name: Installing SDKs
+ command: choco install .circleci/chocolatey.config --no-progress
+ - save_cache:
+ name: Saving Chocolatey cache
+ key: chocolatey-cache-v2-{{ arch }}-{{ checksum ".circleci/chocolatey.config" }}
+ paths:
+ - ~\AppData\Local\Temp\chocolatey
+ install_sdks_unix:
+ steps:
+ - restore_cache:
+ name: Restoring SDKMAN install cache
+ keys:
+ - sdkman-install-cache-v3-{{ arch }}-{{ checksum ".circleci/vendor/sdkman-install.sh" }}
+ - run:
+ name: Installing SDKMAN
+ # The install script comes from https://get.sdkman.io/?rcupdate=false
+ # We need to disable rcupdate as CircleCI uses a different setup.
+ command: |
+ ./.circleci/vendor/sdkman-install.sh
+ echo -e '\nsource "${HOME}/.sdkman/bin/sdkman-init.sh"' >> $BASH_ENV
+ source $BASH_ENV
+ - save_cache:
+ name: Saving SDKMAN install cache
+ key: sdkman-install-cache-v3-{{ arch }}-{{ checksum ".circleci/vendor/sdkman-install.sh" }}
+ paths:
+ - ~/.sdkman
+ - restore_cache:
+ name: Restoring SDKMAN archive cache
+ keys:
+ - sdkman-archive-cache-v3-{{ arch }}-{{ checksum ".circleci/install-sdks-unix.sh" }}
+ - run:
+ name: Installing SDKs
+ command: ./.circleci/install-sdks-unix.sh
+ - save_cache:
+ name: Saving SDKMAN archive cache
+ key: sdkman-archive-cache-v3-{{ arch }}-{{ checksum ".circleci/install-sdks-unix.sh" }}
+ paths:
+ - ~/.sdkman/archives
+ - aws-cli/install:
+ version: << pipeline.parameters.aws_version >>
+ install_sdks_linux:
+ steps:
+ - run:
+ name: Installing Python
+ command: |
+ sudo apt update
+ sudo apt install python3 python3-pip python-is-python3
+ - install_sdks_unix
+ install_sdks_macos:
+ steps:
+ - install_sdks_unix
+ install_shellspec_dependencies:
+ steps:
+ - run:
+ name: Installing ShellSpec
+ command: |
+ ./test/smoke/install-shellspec.sh --yes
+ sudo ln -s ${HOME}/.local/lib/shellspec/shellspec /usr/local/bin/shellspec
+ start_proxy_linux:
+ steps:
+ - run:
+ name: Create and import proxy certificate
+ working_directory: ./cliv2
+ command: |
+ go run cmd/make-cert/main.go test-corp-proxy
+ sudo cp test-corp-proxy.crt /usr/local/share/ca-certificates
+ sudo update-ca-certificates
+ - restore_cache:
+ name: Restoring mitmproxy install cache
+ key: mitmproxy-install-cache-{{ arch }}-<< pipeline.parameters.mitmproxy_version >>
+ - run:
+ name: Install mitmproxy
+ command: |
+ if ! test -f mitmproxy.tar.gz; then
+ curl -Lo mitmproxy.tar.gz https://snapshots.mitmproxy.org/<< pipeline.parameters.mitmproxy_version >>/mitmproxy-<< pipeline.parameters.mitmproxy_version >>-linux.tar.gz
+ fi
+ sudo tar -xvzf mitmproxy.tar.gz -C /usr/local/bin
+ - save_cache:
+ name: Saving mitmproxy install cache
+ key: mitmproxy-install-cache-{{ arch }}-<< pipeline.parameters.mitmproxy_version >>
+ paths:
+ - mitmproxy.tar.gz
+ - run:
+ name: Start proxy
+ background: true
+ working_directory: ./cliv2
+ command: mitmdump --certs *=test-corp-proxy.pem
+ - run:
+ name: Wait for proxy
+ working_directory: ./cliv2
+ command: |
+ curl -I --verbose --retry 6 --retry-delay 5 --retry-connrefused http://localhost:8080
+ curl -I --verbose --proxy http://localhost:8080 "${SNYK_API}"
+ start_proxy_windows:
+ steps:
+ - run:
+ name: Create and import proxy certificate
+ working_directory: ./cliv2
+ command: |
+ go run cmd/make-cert/main.go test-corp-proxy
+ Import-Certificate -FilePath test-corp-proxy.crt -CertStoreLocation Cert:\LocalMachine\Root
+ - run:
+ name: Start proxy
+ background: true
+ working_directory: ./cliv2
+ command: mitmdump --certs *=test-corp-proxy.pem
+ - run:
+ name: Wait for proxy
+ shell: bash.exe
+ working_directory: ./cliv2
+ command: |
+ curl -I --verbose --retry 6 --retry-delay 5 --retry-connrefused http://localhost:8080
+ curl -I --verbose --proxy http://localhost:8080 "${SNYK_API}"
+
+jobs:
+ install:
+ executor: docker-node
+ steps:
+ - checkout
+ - setup_npm:
+ npm_install: true
+ - persist_to_workspace:
+ root: .
+ paths:
+ - node_modules
+ - packages/*/node_modules
+ lint:
+ executor: docker-node
+ steps:
+ - checkout
+ - attach_workspace:
+ at: .
+ - setup_npm
+ - run:
+ name: Linting project
+ command: npm run lint
+ build:
+ executor: docker-node
+ steps:
+ - checkout
+ - attach_workspace:
+ at: .
+ - setup_npm
+ - run:
+ name: Building project
+ command: npm run build:prod
+ - persist_to_workspace:
+ root: .
+ paths:
+ - dist/
+ - packages/*/dist
+ - pysrc
+ version:
+ executor: docker-node
+ steps:
+ - checkout
+ - attach_workspace:
+ at: .
+ - setup_npm
+ - run:
+ name: Deciding version
+ command: make binary-releases/version
+ - persist_to_workspace:
+ root: .
+ paths:
+ - binary-releases/version
+ regression-test:
+ parameters:
+ test_snyk_command:
+ type: string
+ executor: docker-node
+ steps:
+ - checkout
+ - attach_workspace:
+ at: .
+ - install_sdks_linux
+ - install_shellspec_dependencies
+ - setup_npm
+ - run:
+ name: Installing test fixture dependencies
+ working_directory: ./test/fixtures/basic-npm
+ command: npm install
+ - run:
+ name: Installing Snyk CLI
+ command: |
+ sudo ln -s << parameters.test_snyk_command >> /usr/local/bin/snyk
+ snyk --version
+ - run:
+ name: Running ShellSpec tests
+ working_directory: ./test/smoke
+ command: |
+ echo "Checkout the README in test/smoke folder for more details about this step"
+ unset SNYK_API
+ unset SNYK_API_KEY
+ shellspec -f d -e REGRESSION_TEST=1
+ test-windows:
+ parameters:
+ test_snyk_command:
+ type: string
+ executor: win/default
+ working_directory: C:\Users\circleci\snyk
+ steps:
+ - run:
+ name: Configuring Git
+ command: git config --global core.autocrlf false
+ - checkout
+ - attach_workspace:
+ at: .
+ - install_sdks_windows
+ - setup_npm:
+ npm_install: true # reinstalling as workspace node_modules is for linux
+ npm_cache_directory: ~\AppData\Local\npm-cache
+ - run:
+ name: Configuring artifact
+ command: << parameters.test_snyk_command >> config set "api=$env:SNYK_API_KEY"
+ - run:
+ name: Testing artifact
+ command: npm run test:acceptance -- --selectProjects snyk
+ environment:
+ TEST_SNYK_COMMAND: << parameters.test_snyk_command >>
+ test-macos:
+ parameters:
+ test_snyk_command:
+ type: string
+ executor: macos
+ working_directory: /Users/distiller/snyk
+ steps:
+ - checkout
+ - attach_workspace:
+ at: .
+ - install_sdks_macos
+ - setup_npm:
+ npm_install: true # reinstalling as workspace node_modules is for linux
+ npm_cache_directory: /Users/distiller/.npm
+ - run:
+ name: Configuring artifact
+ command: << parameters.test_snyk_command >> config set "api=${SNYK_API_KEY}"
+ - run:
+ name: Testing artifact
+ command: npm run test:acceptance -- --selectProjects snyk
+ environment:
+ TEST_SNYK_COMMAND: << parameters.test_snyk_command >>
+ test-alpine:
+ parameters:
+ test_snyk_command:
+ type: string
+ executor: alpine
+ working_directory: /home/circleci/snyk
+ steps:
+ - checkout
+ - attach_workspace:
+ at: .
+ - run:
+ name: Installing Node.js + other test dependencies
+ command: |
+ apk add --update nodejs npm bash maven
+ - setup_npm:
+ npm_cache_directory: /home/circleci/.npm
+ npm_global_sudo: false
+ - run:
+ name: Configuring artifact
+ command: << parameters.test_snyk_command >> config set "api=${SNYK_API_KEY}"
+ - run:
+ name: Testing artifact
+ command: npm run test:acceptance -- --selectProjects snyk
+ environment:
+ TEST_SNYK_COMMAND: << parameters.test_snyk_command >>
+ test-linux:
+ parameters:
+ test_snyk_command:
+ type: string
+ executor: linux
+ working_directory: /home/circleci/snyk
+ steps:
+ - checkout
+ - attach_workspace:
+ at: .
+ - install_sdks_linux
+ - run:
+ name: Installing Node.js
+ command: |
+ sudo apt update
+ sudo apt install nodejs npm
+ - setup_npm:
+ npm_cache_directory: /home/circleci/.npm
+ npm_global_sudo: true
+ - run:
+ name: Configuring artifact
+ command: << parameters.test_snyk_command >> config set "api=${SNYK_API_KEY}"
+ - run:
+ name: Testing artifact
+ command: npm run test:acceptance -- --selectProjects snyk
+ environment:
+ TEST_SNYK_COMMAND: << parameters.test_snyk_command >>
+ test-linux-arm64:
+ parameters:
+ test_snyk_command:
+ type: string
+ executor: linux-arm64
+ working_directory: /home/circleci/snyk
+ steps:
+ - checkout
+ - attach_workspace:
+ at: .
+ - install_sdks_linux
+ - run:
+ name: Installing Node.js
+ command: |
+ sudo apt update
+ sudo apt install nodejs npm
+ - setup_npm:
+ npm_cache_directory: /home/circleci/.npm
+ npm_global_sudo: true
+ - run:
+ name: Configuring artifact
+ command: << parameters.test_snyk_command >> config set "api=${SNYK_API_KEY}"
+ - run:
+ name: Testing artifact
+ command: npm run test:acceptance -- --selectProjects snyk
+ environment:
+ TEST_SNYK_COMMAND: << parameters.test_snyk_command >>
+ test-jest:
+ parameters:
+ node_version:
+ type: string
+ npm_global_sudo:
+ type: boolean
+ executor:
+ name: docker-node
+ node_version: << parameters.node_version >>
+ environment:
+ TEMP: /mnt/ramdisk/tmp
+ steps:
+ - run:
+ name: Creating temporary directory
+ command: mkdir /mnt/ramdisk/tmp
+ - checkout
+ - attach_workspace:
+ at: .
+ - install_sdks_linux
+ - setup_npm:
+ npm_global_sudo: << parameters.npm_global_sudo >>
+ node_version: << parameters.node_version >>
+ - run:
+ name: Configuring Snyk CLI
+ command: node ./bin/snyk config set "api=${SNYK_API_KEY}"
+ - run:
+ name: Running unit tests
+ command: npm run test:unit
+ - run:
+ name: Running acceptance tests
+ command: npm run test:acceptance
+
+ test-tap:
+ executor: docker-node
+ parallelism: 2
+ steps:
+ - checkout
+ - attach_workspace:
+ at: .
+ - install_sdks_linux
+ - setup_npm
+ - run:
+ name: Configuring Snyk CLI
+ command: node ./bin/snyk config set "api=${SNYK_API_KEY}"
+ - run:
+ name: Running Tap tests
+ command: |
+ npx tap -Rspec --timeout=300 --node-arg=-r --node-arg=ts-node/register \
+ $(circleci tests glob "test/tap/*.test.*" | circleci tests split)
+
+ build-artifact:
+ parameters:
+ artifact:
+ type: string
+ executor: docker-node
+ steps:
+ - checkout
+ - attach_workspace:
+ at: .
+ - setup_npm
+ - when:
+ condition:
+ equal: ['snyk-win.exe', << parameters.artifact >>]
+ steps:
+ run:
+ name: Installing osslsigncode
+ command: |
+ sudo apt update
+ sudo apt install osslsigncode
+ - run:
+ name: Building artifact
+ command: make binary-releases/<< parameters.artifact >>
+ - persist_to_workspace:
+ root: .
+ paths:
+ - binary-releases/<< parameters.artifact >>
+ - binary-releases/<< parameters.artifact >>.sha256
+ prepare-release:
+ executor: docker-node
+ steps:
+ - checkout
+ - attach_workspace:
+ at: .
+ - setup_npm
+ - run:
+ name: Signing shasums
+ command: make binary-releases/sha256sums.txt.asc
+ - run:
+ name: Making release.json
+ command: make binary-releases/release.json
+ - run:
+ name: Making release notes
+ command: make binary-releases/RELEASE_NOTES.md
+ - store_artifacts:
+ path: ./binary-releases
+ - run:
+ name: Validating artifacts
+ command: ./release-scripts/validate-checksums.sh
+ - persist_to_workspace:
+ root: .
+ paths:
+ - binary-releases
+ release:
+ executor: docker-node
+ steps:
+ - checkout
+ - attach_workspace:
+ at: .
+ - setup_npm
+ - run:
+ name: Validating artifacts
+ command: ./release-scripts/validate-checksums.sh
+ - gh/setup:
+ token: GH_TOKEN
+ version: << pipeline.parameters.gh_version >>
+ - aws-cli/install:
+ version: << pipeline.parameters.aws_version >>
+ - run:
+ name: Ensure master branch
+ command: |
+ if [ "$CIRCLE_BRANCH" != "master" ]; then
+ echo "Release must be on 'master' branch."
+ exit 1
+ fi
+ - run:
+ name: Ensure not already released
+ command: |
+ if git describe --contains --tags; then
+ echo "This commit has already been released."
+ exit 1
+ fi
+ - run:
+ name: Publishing npm packages
+ command: |
+ npm publish ./binary-releases/snyk-fix.tgz
+ npm publish ./binary-releases/snyk-protect.tgz
+ npm publish ./binary-releases/snyk.tgz
+ - run:
+ name: Publishing artifacts
+ command: ./release-scripts/upload-artifacts.sh
+ - run:
+ name: Handling failed release
+ command: ./release-scripts/handle-failed-release.sh
+ when: on_fail
+
+ #
+ # Snyk CLI v2 Jobs
+ #
+ v2-lint:
+ executor: linux
+ working_directory: /home/circleci/snyk
+ steps:
+ - checkout
+ - attach_workspace:
+ at: .
+ - go/install:
+ version: << pipeline.parameters.go_version >>
+ - run:
+ name: Lint
+ working_directory: ./cliv2
+ command: make lint
+
+ v2-unit-test:
+ executor: linux
+ working_directory: /home/circleci/snyk
+ steps:
+ - checkout
+ - attach_workspace:
+ at: .
+ - go/install:
+ version: << pipeline.parameters.go_version >>
+ - restore_cache:
+ key: go-unit-test-{{ arch }}-{{ checksum "cliv2/go.sum" }}
+ - run:
+ name: Run unit tests
+ working_directory: ./cliv2
+ command: make configure whiteboxtest
+ - save_cache:
+ key: go-unit-test-{{ arch }}-{{ checksum "cliv2/go.sum" }}
+ paths: [/home/circleci/go/pkg/mod]
+
+ v2-build-artifact:
+ parameters:
+ go_os:
+ type: string
+ go_arch:
+ type: string
+ c_compiler:
+ type: string
+ default: ''
+ executor: linux
+ working_directory: /home/circleci/snyk
+ steps:
+ - checkout
+ - attach_workspace:
+ at: .
+ - go/install:
+ version: << pipeline.parameters.go_version >>
+ - restore_cache:
+ key: go-build-{{ arch }}-{{ checksum "cliv2/go.sum" }}
+ - run: sudo apt-get install musl-tools
+ - run:
+ name: Build << parameters.go_os >>/<< parameters.go_arch >>
+ working_directory: ./cliv2
+ environment:
+ CC: << parameters.c_compiler >>
+ GOOS: << parameters.go_os >>
+ GOARCH: << parameters.go_arch >>
+ CLI_V1_LOCATION: ../binary-releases
+ command: make build build-test install prefix=. -e
+ - save_cache:
+ key: go-build-{{ arch }}-{{ checksum "cliv2/go.sum" }}
+ paths: [/home/circleci/go/pkg/mod]
+ - persist_to_workspace:
+ root: .
+ paths:
+ - ./cliv2/bin
+
+ v2-prepare-release:
+ executor: linux
+ working_directory: /home/circleci/snyk
+ steps:
+ - checkout
+ - attach_workspace:
+ at: .
+ - store_artifacts:
+ path: ./cliv2/bin
+
+ v2-test-linux-amd64:
+ executor: linux
+ working_directory: /home/circleci/snyk
+ steps:
+ - checkout
+ - attach_workspace:
+ at: .
+ - run:
+ name: Run integration tests
+ working_directory: ./cliv2
+ environment:
+ TEST_SNYK_EXECUTABLE_PATH: ./bin/snyk_linux_amd64
+ command: |
+ export SNYK_TOKEN="${SNYK_API_KEY}"
+ ./bin/snyk_tests_linux_amd64
+ v2-test-proxy-linux-amd64:
+ executor: linux
+ working_directory: /home/circleci/snyk
+ steps:
+ - checkout
+ - attach_workspace:
+ at: .
+ - go/install:
+ version: << pipeline.parameters.go_version >>
+ - start_proxy_linux
+ - run:
+ name: Run integration tests
+ working_directory: ./cliv2
+ environment:
+ TEST_SNYK_EXECUTABLE_PATH: ./bin/snyk_linux_amd64
+ HTTPS_PROXY: http://localhost:8080
+ command: |
+ export SNYK_TOKEN="${SNYK_API_KEY}"
+ ./bin/snyk_tests_linux_amd64
+
+ v2-test-linux-arm64:
+ executor: linux-arm64
+ working_directory: /home/circleci/snyk
+ steps:
+ - checkout
+ - attach_workspace:
+ at: .
+ - run:
+ name: Run integration tests
+ working_directory: ./cliv2
+ environment:
+ TEST_SNYK_EXECUTABLE_PATH: ./bin/snyk_linux_arm64
+ command: |
+ export SNYK_TOKEN="${SNYK_API_KEY}"
+ ./bin/snyk_tests_linux_arm64
+
+ v2-test-darwin-amd64:
+ executor: macos
+ working_directory: /Users/distiller/snyk
+ steps:
+ - checkout
+ - attach_workspace:
+ at: .
+ - run:
+ name: Run integration tests
+ working_directory: ./cliv2
+ environment:
+ TEST_SNYK_EXECUTABLE_PATH: ./bin/snyk_darwin_amd64
+ command: |
+ export SNYK_TOKEN="${SNYK_API_KEY}"
+ ./bin/snyk_tests_darwin_amd64
+
+ v2-test-windows-amd64:
+ executor: win/default
+ working_directory: C:\Users\circleci\snyk
+ steps:
+ - checkout
+ - attach_workspace:
+ at: .
+ - run:
+ name: Run integration tests
+ working_directory: ./cliv2
+ environment:
+ TEST_SNYK_EXECUTABLE_PATH: ./bin/snyk_windows_amd64.exe
+ command: |
+ $env:SNYK_TOKEN = $env:SNYK_API_KEY
+ ./bin/snyk_tests_windows_amd64.exe
+ v2-test-proxy-windows-amd64:
+ executor: win/default
+ working_directory: C:\Users\circleci\snyk
+ steps:
+ - checkout
+ - attach_workspace:
+ at: .
+ - install_sdks_windows
+ - start_proxy_windows
+ - run:
+ name: Run integration tests
+ working_directory: ./cliv2
+ environment:
+ TEST_SNYK_EXECUTABLE_PATH: ./bin/snyk_windows_amd64.exe
+ HTTPS_PROXY: http://localhost:8080
+ command: |
+ $env:SNYK_TOKEN = $env:SNYK_API_KEY
+ ./bin/snyk_tests_windows_amd64.exe
+
+ v2-sign-darwin-amd64:
+ executor: macos
+ working_directory: /Users/distiller/snyk
+ steps:
+ - checkout
+ - attach_workspace:
+ at: .
+ - run:
+ name: Signing macOS artifact
+ # --ignore-errors due to 403s from Apple's service.
+ # We need to sign a new agreement for our dev account.
+ # We currently don't publish this artifact so it's safe to ignore.
+ command: make sign GOOS=darwin GOARCH=amd64 BUILD_DIR=$PWD/bin --ignore-errors
+ working_directory: ./cliv2
+ - persist_to_workspace:
+ root: .
+ paths:
+ - cliv2/bin/snyk_darwin_amd64
+ - cliv2/bin/snyk_darwin_amd64.sha256
+
+ v2-sign-windows-amd64:
+ executor: linux
+ working_directory: /home/circleci/snyk
+ steps:
+ - run:
+ name: Installing build dependencies
+ command: |
+ sudo apt update
+ sudo apt install osslsigncode
+ - checkout
+ - attach_workspace:
+ at: .
+ - run:
+ name: Signing windows artifact
+ command: make sign GOOS=windows GOARCH=amd64 BUILD_DIR=$PWD/bin
+ working_directory: ./cliv2
+ - persist_to_workspace:
+ root: .
+ paths:
+ - cliv2/bin/snyk_windows_amd64.exe
+ - cliv2/bin/snyk_windows_amd64.exe.sha256
+
+workflows:
+ version: 2
+ test_and_release:
+ jobs:
+ - install:
+ name: Install
+ - lint:
+ name: Lint
+ requires:
+ - Install
+ - build:
+ name: Build
+ requires:
+ - Install
+ - test-jest:
+ name: Jest Tests (Node v<< matrix.node_version >>)
+ context: nodejs-install
+ requires:
+ - Build
+ matrix:
+ parameters:
+ node_version: ['12.22.11', '14.20.0', '16.16.0']
+ npm_global_sudo: [true, false]
+ exclude:
+ - node_version: '12.22.11'
+ npm_global_sudo: false
+ - node_version: '14.20.0'
+ npm_global_sudo: false
+ - node_version: '16.16.0'
+ npm_global_sudo: true
+ - test-tap:
+ name: Tap Tests
+ context: nodejs-install
+ requires:
+ - Build
+ - version:
+ name: Version
+ requires:
+ - Build
+ filters:
+ branches:
+ only:
+ - /^chore\/.+$/
+ - /^.*test.*$/
+ - /^.*cliv2.*$/
+ - master
+ - build-artifact:
+ name: Build (<< matrix.artifact >>)
+ requires:
+ - Version
+ matrix:
+ parameters:
+ artifact:
+ - snyk.tgz
+ - snyk-fix.tgz
+ - snyk-protect.tgz
+ - snyk-alpine
+ - snyk-linux
+ - snyk-linux-arm64
+ - snyk-macos
+ - snyk-win.exe
+ - snyk-for-docker-desktop-darwin-x64.tar.gz
+ - snyk-for-docker-desktop-darwin-arm64.tar.gz
+ - docker-mac-signed-bundle.tar.gz
+ - test-windows:
+ name: Acceptance Tests (snyk-win.exe)
+ context: nodejs-install
+ requires:
+ - Build (snyk-win.exe)
+ test_snyk_command: C:\Users\circleci\snyk\binary-releases\snyk-win.exe
+ - test-macos:
+ name: Acceptance Tests (snyk-macos)
+ context: nodejs-install
+ requires:
+ - Build (snyk-macos)
+ test_snyk_command: /Users/distiller/snyk/binary-releases/snyk-macos
+ - test-linux:
+ name: Acceptance Tests (snyk-linux)
+ context: nodejs-install
+ requires:
+ - Build (snyk-linux)
+ test_snyk_command: /home/circleci/snyk/binary-releases/snyk-linux
+ - test-linux-arm64:
+ name: Acceptance Tests (snyk-linux-arm64)
+ context: nodejs-install
+ requires:
+ - Build (snyk-linux-arm64)
+ test_snyk_command: /home/circleci/snyk/binary-releases/snyk-linux-arm64
+ - regression-test:
+ name: Regression Tests (snyk-linux)
+ context: nodejs-install
+ requires:
+ - Build (snyk-linux)
+ test_snyk_command: /mnt/ramdisk/snyk/binary-releases/snyk-linux
+ - prepare-release:
+ name: Prepare Release
+ context:
+ - snyk-cli-pgp-signing
+ requires:
+ - Build (snyk.tgz)
+ - Build (snyk-fix.tgz)
+ - Build (snyk-protect.tgz)
+ - Build (snyk-alpine)
+ - Build (snyk-linux)
+ - Build (snyk-linux-arm64)
+ - Build (snyk-macos)
+ - Build (snyk-win.exe)
+ - Build (snyk-for-docker-desktop-darwin-x64.tar.gz)
+ - Build (snyk-for-docker-desktop-darwin-arm64.tar.gz)
+ - Build (docker-mac-signed-bundle.tar.gz)
+ - should-release:
+ name: Release?
+ type: approval
+ requires:
+ - Prepare Release
+ - Lint
+ - Tap Tests
+ - Jest Tests (Node v12.22.11)
+ - Jest Tests (Node v14.20.0)
+ - Jest Tests (Node v16.16.0)
+ - Acceptance Tests (snyk-win.exe)
+ - Acceptance Tests (snyk-macos)
+ - Acceptance Tests (snyk-linux)
+ - Acceptance Tests (snyk-linux-arm64)
+ - Regression Tests (snyk-linux)
+ filters:
+ branches:
+ only:
+ - master
+ - release:
+ name: Release
+ context: nodejs-app-release
+ requires:
+ - Release?
+ filters:
+ branches:
+ only:
+ - master
+ #
+ # Snyk CLI v2 Workflow Jobs
+ #
+ - v2-lint:
+ name: v2 / Lint
+ filters:
+ branches:
+ only:
+ - /^.*cliv2.*$/
+ - master
+ - v2-unit-test:
+ name: v2 / Unit Tests
+ filters:
+ branches:
+ only:
+ - /^.*cliv2.*$/
+ - master
+ - v2-build-artifact:
+ name: v2 / Build (linux/amd64)
+ requires:
+ - Build (snyk-linux)
+ go_os: linux
+ go_arch: amd64
+ filters:
+ branches:
+ only:
+ - /^.*cliv2.*$/
+ - master
+ - v2-build-artifact:
+ name: v2 / Build (linux/arm64)
+ requires:
+ - Build (snyk-linux-arm64)
+ go_os: linux
+ go_arch: arm64
+ filters:
+ branches:
+ only:
+ - /^.*cliv2.*$/
+ - master
+ - v2-build-artifact:
+ name: v2 / Build (darwin/amd64)
+ requires:
+ - Build (snyk-macos)
+ go_os: darwin
+ go_arch: amd64
+ filters:
+ branches:
+ only:
+ - /^.*cliv2.*$/
+ - master
+ - v2-build-artifact:
+ name: v2 / Build (windows/amd64)
+ requires:
+ - Build (snyk-win.exe)
+ go_os: windows
+ go_arch: amd64
+ filters:
+ branches:
+ only:
+ - /^.*cliv2.*$/
+ - master
+ - v2-build-artifact:
+ name: v2 / Build (alpine/amd64)
+ requires:
+ - Build (snyk-alpine)
+ go_os: alpine
+ go_arch: amd64
+ c_compiler: /usr/bin/musl-gcc
+ filters:
+ branches:
+ only:
+ - /^.*cliv2.*$/
+ - master
+ - v2-test-linux-amd64:
+ name: v2 / Integration Tests (linux/amd64)
+ requires:
+ - v2 / Build (linux/amd64)
+ - v2-test-proxy-linux-amd64:
+ name: v2 / Proxy Integration Tests (linux/amd64)
+ requires:
+ - v2 / Build (linux/amd64)
+ - v2-test-linux-arm64:
+ name: v2 / Integration Tests (linux/arm64)
+ requires:
+ - v2 / Build (linux/arm64)
+ - v2-test-darwin-amd64:
+ name: v2 / Integration Tests (darwin/amd64)
+ requires:
+ - v2 / Build (darwin/amd64)
+ - v2-test-windows-amd64:
+ name: v2 / Integration Tests (windows/amd64)
+ requires:
+ - v2 / Build (windows/amd64)
+ - v2-test-proxy-windows-amd64:
+ name: v2 / Proxy Integration Tests (windows/amd64)
+ requires:
+ - v2 / Build (windows/amd64)
+ # Tests for backwards compatibility with CLIv1
+ - test-alpine:
+ name: v2 / Jest Acceptance Tests (alpine/amd64)
+ context: nodejs-install
+ requires:
+ - v2 / Build (alpine/amd64)
+ test_snyk_command: /home/circleci/snyk/cliv2/bin/snyk_alpine_amd64
+ - test-linux:
+ name: v2 / Jest Acceptance Tests (linux/amd64)
+ context: nodejs-install
+ requires:
+ - v2 / Build (linux/amd64)
+ test_snyk_command: /home/circleci/snyk/cliv2/bin/snyk_linux_amd64
+ - test-linux-arm64:
+ name: v2 / Jest Acceptance Tests (linux/arm64)
+ context: nodejs-install
+ requires:
+ - v2 / Build (linux/arm64)
+ test_snyk_command: /home/circleci/snyk/cliv2/bin/snyk_linux_arm64
+ - test-windows:
+ name: v2 / Jest Acceptance Tests (windows/amd64)
+ context: nodejs-install
+ requires:
+ - v2 / Build (windows/amd64)
+ test_snyk_command: C:\Users\circleci\snyk\cliv2\bin\snyk_windows_amd64.exe
+ - test-macos:
+ name: v2 / Jest Acceptance Tests (darwin/amd64)
+ context: nodejs-install
+ requires:
+ - v2 / Build (darwin/amd64)
+ test_snyk_command: /Users/distiller/snyk/cliv2/bin/snyk_darwin_amd64
+ - regression-test:
+ name: v2 / Regression Tests (linux/amd64)
+ context: nodejs-install
+ requires:
+ - v2 / Build (linux/amd64)
+ test_snyk_command: /mnt/ramdisk/snyk/cliv2/bin/snyk_linux_amd64
+ - v2-sign-darwin-amd64:
+ name: v2 / Sign (darwin/amd64)
+ context: snyk-macos-signing
+ requires:
+ - v2 / Build (darwin/amd64)
+ - v2-sign-windows-amd64:
+ name: v2 / Sign (windows/amd64)
+ context: snyk-windows-signing
+ requires:
+ - v2 / Build (windows/amd64)
+ - v2-prepare-release:
+ name: v2 / Prepare Release
+ requires:
+ - v2 / Build (alpine/amd64)
+ - v2 / Build (linux/amd64)
+ - v2 / Build (linux/arm64)
+ - v2 / Sign (darwin/amd64)
+ - v2 / Sign (windows/amd64)
diff --git a/.circleci/install-sdks-unix.sh b/.circleci/install-sdks-unix.sh
new file mode 100755
index 0000000000..84b52b7b17
--- /dev/null
+++ b/.circleci/install-sdks-unix.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+# Can't set -u as sdkman has unbound variables.
+set -eo pipefail
+
+sdk install java 11.0.11.hs-adpt
+sdk install maven 3.8.2
+sdk install gradle 6.8.3
+sdk install sbt 1.5.5
diff --git a/.circleci/vendor/sdkman-install.sh b/.circleci/vendor/sdkman-install.sh
new file mode 100755
index 0000000000..c7519024db
--- /dev/null
+++ b/.circleci/vendor/sdkman-install.sh
@@ -0,0 +1,283 @@
+#!/bin/bash
+#
+# Copyright 2017 Marco Vermeulen
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# install:- channel: stable; version: 5.13.1; api: https://api.sdkman.io/2
+
+# Global variables
+SDKMAN_SERVICE="https://api.sdkman.io/2"
+SDKMAN_VERSION="5.13.1"
+SDKMAN_PLATFORM=$(uname)
+
+if [ -z "$SDKMAN_DIR" ]; then
+ SDKMAN_DIR="$HOME/.sdkman"
+ SDKMAN_DIR_RAW='$HOME/.sdkman'
+else
+ SDKMAN_DIR_RAW="$SDKMAN_DIR"
+fi
+
+# Local variables
+sdkman_tmp_folder="${SDKMAN_DIR}/tmp"
+sdkman_zip_file="${sdkman_tmp_folder}/sdkman-${SDKMAN_VERSION}.zip"
+sdkman_zip_base_folder="${sdkman_tmp_folder}/sdkman-${SDKMAN_VERSION}"
+sdkman_ext_folder="${SDKMAN_DIR}/ext"
+sdkman_etc_folder="${SDKMAN_DIR}/etc"
+sdkman_var_folder="${SDKMAN_DIR}/var"
+sdkman_archives_folder="${SDKMAN_DIR}/archives"
+sdkman_candidates_folder="${SDKMAN_DIR}/candidates"
+sdkman_config_file="${sdkman_etc_folder}/config"
+sdkman_bash_profile="${HOME}/.bash_profile"
+sdkman_profile="${HOME}/.profile"
+sdkman_bashrc="${HOME}/.bashrc"
+sdkman_zshrc="${ZDOTDIR:-${HOME}}/.zshrc"
+
+sdkman_init_snippet=$( cat << EOF
+#THIS MUST BE AT THE END OF THE FILE FOR SDKMAN TO WORK!!!
+export SDKMAN_DIR="$SDKMAN_DIR_RAW"
+[[ -s "${SDKMAN_DIR_RAW}/bin/sdkman-init.sh" ]] && source "${SDKMAN_DIR_RAW}/bin/sdkman-init.sh"
+EOF
+)
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false;
+darwin=false;
+solaris=false;
+freebsd=false;
+case "$(uname)" in
+ CYGWIN*)
+ cygwin=true
+ ;;
+ Darwin*)
+ darwin=true
+ ;;
+ SunOS*)
+ solaris=true
+ ;;
+ FreeBSD*)
+ freebsd=true
+esac
+
+
+echo ''
+echo ' -+syyyyyyys:'
+echo ' `/yho:` -yd.'
+echo ' `/yh/` +m.'
+echo ' .oho. hy .`'
+echo ' .sh/` :N` `-/o` `+dyyo:.'
+echo ' .yh:` `M- `-/osysoym :hs` `-+sys: hhyssssssssy+'
+echo ' .sh:` `N: ms/-`` yy.yh- -hy. `.N-````````+N.'
+echo ' `od/` `N- -/oM- ddd+` `sd: hNNm -N:'
+echo ' :do` .M. dMMM- `ms. /d+` `NMMs `do'
+echo ' .yy- :N` ```mMMM. - -hy. /MMM: yh'
+echo ' `+d+` `:/oo/` `-/osyh/ossssssdNMM` .sh: yMMN` /m.'
+echo ' -dh- :ymNMMMMy `-/shmNm-`:N/-.`` `.sN /N- `NMMy .m/'
+echo ' `oNs` -hysosmMMMMydmNmds+-.:ohm : sd` :MMM/ yy'
+echo ' .hN+ /d: -MMMmhs/-.` .MMMh .ss+- `yy` sMMN` :N.'
+echo ' :mN/ `N/ `o/-` :MMMo +MMMN- .` `ds mMMh do'
+echo ' /NN/ `N+....--:/+oooosooo+:sMMM: hMMMM: `my .m+ -MMM+ :N.'
+echo ' /NMo -+ooooo+/:-....`...:+hNMN. `NMMMd` .MM/ -m: oMMN. hs'
+echo ' -NMd` :mm -MMMm- .s/ -MMm. /m- mMMd -N.'
+echo ' `mMM/ .- /MMh. -dMo -MMMy od. .MMMs..---yh'
+echo ' +MMM. sNo`.sNMM+ :MMMM/ sh`+MMMNmNm+++-'
+echo ' mMMM- /--ohmMMM+ :MMMMm. `hyymmmdddo'
+echo ' MMMMh. ```` `-+yy/`yMMM/ :MMMMMy -sm:.``..-:-.`'
+echo ' dMMMMmo-.``````..-:/osyhddddho. `+shdh+. hMMM: :MmMMMM/ ./yy/` `:sys+/+sh/'
+echo ' .dMMMMMMmdddddmmNMMMNNNNNMMMMMs sNdo- dMMM- `-/yd/MMMMm-:sy+. :hs- /N`'
+echo ' `/ymNNNNNNNmmdys+/::----/dMMm: +m- mMMM+ohmo/.` sMMMMdo- .om: `sh'
+echo ' `.-----+/.` `.-+hh/` `od. NMMNmds/ `mmy:` +mMy `:yy.'
+echo ' /moyso+//+ossso:. .yy` `dy+:` .. :MMMN+---/oys:'
+echo ' /+m: `.-:::-` /d+ +MMMMMMMNh:`'
+echo ' +MN/ -yh. `+hddhy+.'
+echo ' /MM+ .sh:'
+echo ' :NMo -sh/'
+echo ' -NMs `/yy:'
+echo ' .NMy `:sh+.'
+echo ' `mMm` ./yds-'
+echo ' `dMMMmyo:-.````.-:oymNy:`'
+echo ' +NMMMMMMMMMMMMMMMMms:`'
+echo ' -+shmNMMMNmdy+:`'
+echo ''
+echo ''
+echo ' Now attempting installation...'
+echo ''
+echo ''
+
+# Sanity checks
+
+echo "Looking for a previous installation of SDKMAN..."
+if [ -d "$SDKMAN_DIR" ]; then
+ echo "SDKMAN found."
+ echo ""
+ echo "======================================================================================================"
+ echo " You already have SDKMAN installed."
+ echo " SDKMAN was found at:"
+ echo ""
+ echo " ${SDKMAN_DIR}"
+ echo ""
+ echo " Please consider running the following if you need to upgrade."
+ echo ""
+ echo " $ sdk selfupdate force"
+ echo ""
+ echo "======================================================================================================"
+ echo ""
+ exit 0
+fi
+
+echo "Looking for unzip..."
+if ! command -v unzip > /dev/null; then
+ echo "Not found."
+ echo "======================================================================================================"
+ echo " Please install unzip on your system using your favourite package manager."
+ echo ""
+ echo " Restart after installing unzip."
+ echo "======================================================================================================"
+ echo ""
+ exit 1
+fi
+
+echo "Looking for zip..."
+if ! command -v zip > /dev/null; then
+ echo "Not found."
+ echo "======================================================================================================"
+ echo " Please install zip on your system using your favourite package manager."
+ echo ""
+ echo " Restart after installing zip."
+ echo "======================================================================================================"
+ echo ""
+ exit 1
+fi
+
+echo "Looking for curl..."
+if ! command -v curl > /dev/null; then
+ echo "Not found."
+ echo ""
+ echo "======================================================================================================"
+ echo " Please install curl on your system using your favourite package manager."
+ echo ""
+ echo " Restart after installing curl."
+ echo "======================================================================================================"
+ echo ""
+ exit 1
+fi
+
+if [[ "$solaris" == true ]]; then
+ echo "Looking for gsed..."
+ if [ -z $(which gsed) ]; then
+ echo "Not found."
+ echo ""
+ echo "======================================================================================================"
+ echo " Please install gsed on your solaris system."
+ echo ""
+ echo " SDKMAN uses gsed extensively."
+ echo ""
+ echo " Restart after installing gsed."
+ echo "======================================================================================================"
+ echo ""
+ exit 1
+ fi
+else
+ echo "Looking for sed..."
+ if [ -z $(command -v sed) ]; then
+ echo "Not found."
+ echo ""
+ echo "======================================================================================================"
+ echo " Please install sed on your system using your favourite package manager."
+ echo ""
+ echo " Restart after installing sed."
+ echo "======================================================================================================"
+ echo ""
+ exit 1
+ fi
+fi
+
+
+echo "Installing SDKMAN scripts..."
+
+
+# Create directory structure
+
+echo "Create distribution directories..."
+mkdir -p "$sdkman_tmp_folder"
+mkdir -p "$sdkman_ext_folder"
+mkdir -p "$sdkman_etc_folder"
+mkdir -p "$sdkman_var_folder"
+mkdir -p "$sdkman_archives_folder"
+mkdir -p "$sdkman_candidates_folder"
+
+echo "Getting available candidates..."
+SDKMAN_CANDIDATES_CSV=$(curl -s "${SDKMAN_SERVICE}/candidates/all")
+echo "$SDKMAN_CANDIDATES_CSV" > "${SDKMAN_DIR}/var/candidates"
+
+echo "Prime the config file..."
+touch "$sdkman_config_file"
+echo "sdkman_auto_answer=false" >> "$sdkman_config_file"
+if [ -z "$ZSH_VERSION" -a -z "$BASH_VERSION" ]; then
+ echo "sdkman_auto_complete=false" >> "$sdkman_config_file"
+else
+ echo "sdkman_auto_complete=true" >> "$sdkman_config_file"
+fi
+echo "sdkman_auto_env=false" >> "$sdkman_config_file"
+echo "sdkman_beta_channel=false" >> "$sdkman_config_file"
+echo "sdkman_colour_enable=true" >> "$sdkman_config_file"
+echo "sdkman_curl_connect_timeout=7" >> "$sdkman_config_file"
+echo "sdkman_curl_max_time=10" >> "$sdkman_config_file"
+echo "sdkman_debug_mode=false" >> "$sdkman_config_file"
+echo "sdkman_insecure_ssl=false" >> "$sdkman_config_file"
+echo "sdkman_rosetta2_compatible=false" >> "$sdkman_config_file"
+echo "sdkman_selfupdate_enable=false" >> "$sdkman_config_file"
+
+echo "Download script archive..."
+curl --location --progress-bar "${SDKMAN_SERVICE}/broker/download/sdkman/install/${SDKMAN_VERSION}/${SDKMAN_PLATFORM}" > "$sdkman_zip_file"
+
+ARCHIVE_OK=$(unzip -qt "$sdkman_zip_file" | grep 'No errors detected in compressed data')
+if [[ -z "$ARCHIVE_OK" ]]; then
+ echo "Downloaded zip archive corrupt. Are you connected to the internet?"
+ echo ""
+ echo "If problems persist, please ask for help on our Slack:"
+ echo "* easy sign up: https://slack.sdkman.io/"
+ echo "* report on channel: https://sdkman.slack.com/app_redirect?channel=user-issues"
+ rm -rf "$SDKMAN_DIR"
+ exit 1
+fi
+
+echo "Extract script archive..."
+if [[ "$cygwin" == 'true' ]]; then
+ echo "Cygwin detected - normalizing paths for unzip..."
+ sdkman_tmp_folder=$(cygpath -w "$sdkman_tmp_folder")
+ sdkman_zip_file=$(cygpath -w "$sdkman_zip_file")
+ sdkman_zip_base_folder=$(cygpath -w "$sdkman_zip_base_folder")
+fi
+unzip -qo "$sdkman_zip_file" -d "$sdkman_tmp_folder"
+
+echo "Install scripts..."
+mv "${sdkman_zip_base_folder}/"* "$SDKMAN_DIR"
+rm -rf "$sdkman_zip_base_folder"
+
+echo "Set version to $SDKMAN_VERSION ..."
+echo "$SDKMAN_VERSION" > "${SDKMAN_DIR}/var/version"
+
+
+echo -e "\n\n\nAll done!\n\n"
+
+echo "Please open a new terminal, or run the following in the existing one:"
+echo ""
+echo " source \"${SDKMAN_DIR}/bin/sdkman-init.sh\""
+echo ""
+echo "Then issue the following command:"
+echo ""
+echo " sdk help"
+echo ""
+echo "Enjoy!!!"
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000000..074952f7fb
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,12 @@
+node_modules
+tmp
+dist
+fixtures
+test-output
+test-results
+test/**/workspaces
+.iac-data
+src/cli/commands/test/iac/local-execution/parsers/hcl-to-json/parser.js
+src/cli/commands/test/iac/local-execution/parsers/hcl-to-json-v2/parser.js
+release-scripts/hcl-to-json-parser-generator/src/hcltojson/test.js
+release-scripts/hcl-to-json-parser-generator-v2/src/hcltojson/test.js
diff --git a/.eslintrc.js b/.eslintrc.js
deleted file mode 100644
index fe74416e6a..0000000000
--- a/.eslintrc.js
+++ /dev/null
@@ -1,44 +0,0 @@
-module.exports = {
- parser: '@typescript-eslint/parser',
- // Pending https://github.com/typescript-eslint/typescript-eslint/issues/389
- // parserOptions: {
- // project: './tsconfig.json',
- // },
- env: {
- node: true,
- es6: true, // We support Node 6+, which is effectively compatible with ES6
- },
- plugins: ['@typescript-eslint'],
- extends: [
- 'eslint:recommended',
- 'plugin:@typescript-eslint/eslint-recommended',
- 'plugin:@typescript-eslint/recommended',
- 'prettier',
- 'prettier/@typescript-eslint',
- ],
- rules: {
- '@typescript-eslint/explicit-function-return-type': 0,
- '@typescript-eslint/no-explicit-any': 0,
-
- // non-null assertions compromise the type safety somewhat, but many
- // our types are still imprecisely defined and we don't use noImplicitAny
- // anyway, so for the time being assertions are allowed
- '@typescript-eslint/no-non-null-assertion': 1,
-
- '@typescript-eslint/no-var-requires': 0,
- '@typescript-eslint/no-use-before-define': 0,
- 'no-prototype-builtins': 0,
- 'require-atomic-updates': 0,
-
- // Required for eslint 5.
- 'no-console': 0,
- },
- "overrides": [
- {
- "files": ["*.ts"],
- "rules": {
- "id-blacklist": [2, "exports"], // in TS, use "export" instead of Node's "module.exports"
- }
- }
- ]
-};
diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 0000000000..b929c5bb74
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,61 @@
+{
+ "parser": "@typescript-eslint/parser",
+ // Pending https://github.com/typescript-eslint/typescript-eslint/issues/389
+ // parserOptions: {
+ // project: './tsconfig.json',
+ // },
+ "env": {
+ "node": true,
+ "es6": true
+ },
+ "plugins": ["@typescript-eslint"],
+ "extends": [
+ "eslint:recommended",
+ "plugin:@typescript-eslint/eslint-recommended",
+ "plugin:@typescript-eslint/recommended",
+ "prettier",
+ "prettier/@typescript-eslint"
+ ],
+ "rules": {
+ "@typescript-eslint/explicit-function-return-type": "off",
+ "@typescript-eslint/no-explicit-any": "off",
+ "@typescript-eslint/no-empty-function": "warn",
+
+ // non-null assertions compromise the type safety somewhat, but many
+ // our types are still imprecisely defined and we don't use noImplicitAny
+ // anyway, so for the time being assertions are allowed
+ "@typescript-eslint/no-non-null-assertion": "warn",
+
+ "@typescript-eslint/no-var-requires": "off",
+ "@typescript-eslint/no-use-before-define": "off",
+ "@typescript-eslint/no-unused-vars": "error",
+ "no-prototype-builtins": "off",
+ "require-atomic-updates": "off",
+ "no-restricted-imports": [
+ "error",
+ { "paths": ["lodash"], "patterns": ["lodash/*"] }
+ ],
+ "no-buffer-constructor": "error"
+ },
+ "overrides": [
+ {
+ "files": ["*.ts"],
+ "rules": {
+ "@typescript-eslint/ban-types": "warn",
+ "id-blacklist": ["error", "exports"] // in TS, use "export" instead of Node's "module.exports"
+ }
+ },
+ {
+ "files": "*.spec.*",
+ "extends": ["plugin:jest/recommended"],
+ "rules": {
+ "jest/no-done-callback": "warn",
+ "jest/valid-title": "warn",
+ "jest/no-conditional-expect": "warn",
+ "jest/no-try-expect": "warn",
+ "jest/no-identical-title": "warn",
+ "@typescript-eslint/ban-ts-comment": "warn"
+ }
+ }
+ ]
+}
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index a3484b2b5a..a5268d56c1 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,3 +1,113 @@
-# Snyk Boost will be required for a review on every PR
-* @snyk/boost
+* @snyk/hammer @snyk/snyk-open-source
+
+# monorepo packages
+packages/snyk-fix/ @snyk/tech-services
+packages/snyk-protect/ @snyk/hammer
+packages/cli-alert/ @snyk/hammer
+
+README.md @snyk/hammer
+CONTRIBUTING.md @snyk/hammer
+.circleci @snyk/hammer
+
+cliv2/ @snyk/hammer
+release-scripts/hcl-to-json-parser-generator/ @snyk/group-infrastructure-as-code
+release-scripts/hcl-to-json-parser-generator-v2/ @snyk/group-infrastructure-as-code
+src/cli/commands/test @snyk/snyk-open-source
+src/cli/commands/monitor @snyk/snyk-open-source
+help/ @snyk/hammer
+help/cli-commands/iac*.md @snyk/group-infrastructure-as-code @snyk/hammer
+src/cli/commands/test/iac/ @snyk/group-infrastructure-as-code
+src/cli/commands/describe.ts @snyk/group-infrastructure-as-code
+src/cli/commands/update-exclude-policy.ts @snyk/group-infrastructure-as-code
+src/cli/commands/apps @snyk/moose
+src/lib/apps @snyk/moose
+src/lib/container @snyk/mycelium
+src/lib/plugins @snyk/snyk-open-source
+test/fixtures/sast/ @snyk/zenith
+src/lib/plugins/sast/ @snyk/zenith
+test/jest/unit/snyk-code/ @snyk/zenith
+src/lib/formatters/iac-output/ @snyk/group-infrastructure-as-code
+src/lib/iac/ @snyk/group-infrastructure-as-code
+src/lib/snyk-test/iac-test-result.ts @snyk/group-infrastructure-as-code
+test/fixtures/basic-apk/ @snyk/mycelium
+test/fixtures/container-app-vulns/ @snyk/mycelium
+test/fixtures/container-projects/ @snyk/mycelium @snyk/potion
+test/fixtures/docker/ @snyk/mycelium @snyk/potion
+test/fixtures/iac/ @snyk/group-infrastructure-as-code
+test/smoke/spec/iac/ @snyk/group-infrastructure-as-code
+test/smoke/spec/snyk_code_spec.sh @snyk/zenith
+test/smoke/spec/snyk_basic_spec.sh @snyk/hammer
+test/smoke/.iac-data/ @snyk/group-infrastructure-as-code
+test/jest/unit/lib/endpoint-config-test.spec.ts @snyk/nebula
+test/jest/unit/lib/formatters/iac-output/ @snyk/group-infrastructure-as-code
+test/jest/unit/lib/formatters/test/format-test-results.spec.ts @snyk/hammer @snyk/snyk-open-source @snyk/mycelium
+test/jest/unit/iac/ @snyk/group-infrastructure-as-code
+test/jest/unit/cli/commands/test/iac @snyk/group-infrastructure-as-code
+test/jest/unit/lib/iac/ @snyk/group-infrastructure-as-code
+test/jest/acceptance/iac/ @snyk/group-infrastructure-as-code
+test/jest/acceptance/snyk-apps @snyk/moose
+src/lib/code-config.ts @snyk/nebula
+src/lib/errors/describe-required-argument-error.ts @snyk/group-infrastructure-as-code
+src/lib/errors/describe-exclusive-argument-error.ts @snyk/group-infrastructure-as-code
+src/lib/errors/no-supported-sast-files-found.ts @snyk/zenith
+help/commands-docs/iac-examples.md @snyk/group-infrastructure-as-code
+help/commands-docs/iac.md @snyk/group-infrastructure-as-code
+test/jest/util/ @snyk/hammer
+dangerfile.js @snyk/hammer
+/package.json @snyk/hammer
+/package-lock.json @snyk/hammer
+/config.default.js @snyk/hammer
+/webpack.common.js @snyk/hammer
+/webpack.dev.js @snyk/hammer
+/webpack.prod.js @snyk/hammer
+/tsconfig.json @snyk/hammer
+/tsconfig.settings.json @snyk/hammer
+CODEOWNERS @snyk/hammer
+/docker @snyk/hammer
+src/cli/commands/log4shell-hashes.ts @snyk/tundra
+src/cli/commands/log4shell.ts @snyk/tundra
+test/fixtures/unmanaged-log4j-fixture @snyk/tundra
+test/jest/acceptance/snyk-log4shell/log4shell-detection.spec.ts @snyk/tundra
+test/jest/acceptance/snyk-test/app-vuln-container-project.spec.ts @snyk/mycelium
+/.github @snyk/hammer
+
+# tap tests ownership
+test/tap/cli-monitor/ @snyk/snyk-open-source
+test/tap/cli-test/ @snyk/snyk-open-source
+test/tap/args.test.ts @snyk/hammer
+test/tap/auth.test.ts @snyk/hammer
+test/tap/cli-fail-on-docker.test.ts @snyk/hammer
+test/tap/cli-fail-on-pinnable.test.ts @snyk/snyk-open-source
+test/tap/cli-monitor.acceptance.test.ts @snyk/snyk-open-source
+test/tap/cli-test.acceptance.test.ts @snyk/snyk-open-source
+test/tap/cli.test.ts @snyk/hammer
+test/tap/container.test.ts @snyk/potion
+test/tap/display-test-results.test.ts @snyk/snyk-open-source
+test/tap/docker-token.test.ts @snyk/potion
+test/tap/endpoint-config.test.ts @snyk/hammer
+test/tap/find-files.test.ts @snyk/snyk-open-source
+test/tap/monitor-target.test.ts @snyk/snyk-open-source
+test/tap/proxy.test.js @snyk/hammer
+test/tap/remote-package.test.ts @snyk/snyk-open-source
+test/tap/request.test.ts @snyk/hammer
+test/tap/run-test.test.ts @snyk/snyk-open-source
+test/tap/severity-threshold.test.ts @snyk/snyk-open-source
+test/tap/sln-app.test.ts @snyk/snyk-open-source
+test/tap/sub-process.test.js @snyk/snyk-open-source
+test/tap/user-config.test.ts @snyk/snyk-open-source
+test/tap/vulnerable-path-output.js @snyk/snyk-open-source
+
+# protect
+test/tap/cli-protect-no-vulns-to-patch.test.ts @snyk/hammer
+test/tap/patch-fetch-fail.test.ts @snyk/hammer
+test/tap/cli-protect.test.ts @snyk/hammer
+test/tap/protect-apply-same-patch-again.test.js @snyk/hammer
+test/tap/protect-config.test.ts @snyk/hammer
+test/tap/protect-factors-pre-tarred.test.ts @snyk/hammer
+test/tap/protect-fail.test.js @snyk/hammer
+test/tap/protect-ignore.test.js @snyk/hammer
+test/tap/protect-patch-filter.test.js @snyk/hammer
+test/tap/protect-patch-order.test.ts @snyk/hammer
+test/tap/protect-patch-same-pkg.test.js @snyk/hammer
+test/tap/protect-semver-patch.test.ts @snyk/hammer
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
deleted file mode 100644
index 276ff5c5b9..0000000000
--- a/.github/CONTRIBUTING.md
+++ /dev/null
@@ -1,48 +0,0 @@
-# Contributing
-
-## Contributor Agreement
-A pull-request will only be considered for merging into the upstream codebase after you have signed our [contributor agreement](https://github.com/snyk/snyk/blob/master/Contributor-Agreement.md), assigning us the rights to the contributed code and granting you a license to use it in return. If you submit a pull request, you will be prompted to review and sign the agreement with one click (we use [CLA assistant](https://cla-assistant.io/)).
-
-## Commit messages
-
-Commit messages must follow the [Angular-style](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#commit-message-format) commit format (but excluding the scope).
-
-i.e:
-
-```text
-fix: minified scripts being removed
-
-Also includes tests
-```
-
-This will allow for the automatic changelog to generate correctly.
-
-### Commit types
-
-Must be one of the following:
-
-* **feat**: A new feature
-* **fix**: A bug fix
-* **docs**: Documentation only changes
-* **test**: Adding missing tests
-* **chore**: Changes to the build process or auxiliary tools and libraries such as documentation generation
-* **refactor**: A code change that neither fixes a bug nor adds a feature
-* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
-* **perf**: A code change that improves performance
-
-To release a major you need to add `BREAKING CHANGE: ` to the start of the body and the detail of the breaking change.
-
-## Code standards
-
-Ensure that your code adheres to the included `.eslintrc` config by running `npm run lint`.
-
-## Sending pull requests
-
-- new command line options are generally discouraged unless there's a *really* good reason
-- add tests for newly added code (and try to mirror directory and file structure if possible)
-- spell check
-- PRs will not be code reviewed unless all tests are passing
-
-*Important:* when fixing a bug, please commit a **failing test** first so that Travis CI (or I can) can show the code failing. Once that commit is in place, then commit the bug fix, so that we can test *before* and *after*.
-
-Remember that you're developing for multiple platforms and versions of node, so if the tests pass on your Mac or Linux or Windows machine, it *may* not pass elsewhere.
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
deleted file mode 100644
index 1390962924..0000000000
--- a/.github/ISSUE_TEMPLATE.md
+++ /dev/null
@@ -1,17 +0,0 @@
-- `node -v`:
-- `npm -v`:
-- `snyk -v`:
-- Command run:
-
-### Expected behaviour
-
-
-### Actual behaviour
-
-
-### Steps to reproduce
-
-
----
-
-If applicable, please append the `--debug` flag on your command and include the output here **ensuring to remove any sensitive/personal details or tokens.
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000000..e0b7a22042
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,8 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Snyk Support
+ url: https://support.snyk.io
+ about: Contact Snyk Support
+ - name: Snyk User Docs
+ url: https://docs.snyk.io
+ about: Snyk User Documentation
diff --git a/.github/ISSUE_TEMPLATE/stop--don-t-use-github-issues.md b/.github/ISSUE_TEMPLATE/stop--don-t-use-github-issues.md
new file mode 100644
index 0000000000..5c15e4c3a6
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/stop--don-t-use-github-issues.md
@@ -0,0 +1,11 @@
+---
+name: Please don't use GitHub Issues for support requests
+about: Email us at support@snyk.io
+title: Stop, this is not a support channel
+labels: ''
+assignees: ''
+---
+
+[Contact Snyk Support on the web](http://support.snyk.io) or email us at support@snyk.io
+
+**GitHub Issues are not monited by Snyk Support**
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 812d07ff46..5056832e08 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,6 +1,4 @@
-- [ ] Ready for review
-- [ ] Follows [CONTRIBUTING](https://github.com/snyk/snyk/blob/master/.github/CONTRIBUTING.md) rules
-- [ ] Reviewed by Snyk internal team
+- [ ] Follows [CONTRIBUTING](https://github.com/snyk/snyk/blob/master/CONTRIBUTING.md) rules
#### What does this PR do?
diff --git a/.github/workflows/check-dependencies.yml b/.github/workflows/check-dependencies.yml
new file mode 100644
index 0000000000..45dbf9338e
--- /dev/null
+++ b/.github/workflows/check-dependencies.yml
@@ -0,0 +1,17 @@
+name: 'Check Dependencies'
+
+on:
+ pull_request:
+ branches: [master]
+
+jobs:
+ check-dependencies:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-node@v2
+ with:
+ node-version: '16.16.0'
+ cache: 'npm'
+ - run: npm ci
+ - run: npx ts-node ./scripts/check-dependencies.ts
diff --git a/.github/workflows/cli-alert.yml b/.github/workflows/cli-alert.yml
new file mode 100644
index 0000000000..9d017c63b9
--- /dev/null
+++ b/.github/workflows/cli-alert.yml
@@ -0,0 +1,25 @@
+name: CLI alert
+
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: '0 * * * *'
+
+jobs:
+ check_tests:
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: ./packages/cli-alert
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-node@v2
+ with:
+ node-version: '16.16.0'
+ cache: 'npm'
+ - run: npm ci
+ - run: npm start
+ env:
+ USER_GITHUB_TOKEN: ${{ secrets.USER_GITHUB_TOKEN }}
+ SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
+ PD_ROUTING_KEY: ${{ secrets.PD_ROUTING_KEY }}
diff --git a/.github/workflows/danger-zone.yml b/.github/workflows/danger-zone.yml
new file mode 100644
index 0000000000..13015b3a31
--- /dev/null
+++ b/.github/workflows/danger-zone.yml
@@ -0,0 +1,19 @@
+name: 'Danger Zone'
+on:
+ pull_request:
+ branches: [master]
+
+jobs:
+ build:
+ name: Danger JS
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-node@v2
+ with:
+ node-version: '16.16.0'
+ cache: 'npm'
+ - run: npm ci
+ - run: npx danger ci
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml
new file mode 100644
index 0000000000..f66a0b9773
--- /dev/null
+++ b/.github/workflows/smoke-tests.yml
@@ -0,0 +1,191 @@
+name: Smoke Tests
+
+on:
+ push:
+ branches: [feat/smoke-test, smoke/**]
+ release:
+ types: [published]
+ schedule:
+ - cron: '0 * * * *'
+
+jobs:
+ smoke_test:
+ # The type of runner that the job will run on
+ runs-on: ${{ matrix.os }}-latest
+ strategy:
+ fail-fast: false # we care about other platforms and channels building
+ matrix:
+ os: [ubuntu, macos, windows]
+ snyk_install_method: [binary, npm, yarn]
+ node_version: [12, 14, 16]
+ exclude:
+ # Skip yarn for Windows, as it's a bit crazy to get it working in CI environment. Unless we see evidence we need it, I'd avoid it
+ - snyk_install_method: yarn
+ os: windows
+ # For binary, use only the Node 14
+ - snyk_install_method: binary
+ node_version: 12
+ - snyk_install_method: binary
+ node_version: 16
+ include:
+ - snyk_install_method: binary
+ os: ubuntu
+ snyk_cli_dl_file: snyk-linux
+ - snyk_install_method: binary
+ os: macos
+ snyk_cli_dl_file: snyk-macos
+ # Homebrew installation
+ - snyk_install_method: brew
+ os: macos
+ - snyk_install_method: alpine-binary
+ os: ubuntu
+ snyk_cli_dl_file: snyk-alpine
+ - snyk_install_method: npm-root-user
+ os: ubuntu
+ - snyk_install_method: docker-bundle
+ os: macos
+ snyk_cli_dl_file: snyk-for-docker-desktop-darwin-x64.tar.gz
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - uses: actions/setup-node@v1 # Needed for fixtures installation
+ with:
+ node-version: ${{ matrix.node_version }}
+
+ - name: Install Snyk with npm
+ if: ${{ matrix.snyk_install_method == 'npm' }}
+ run: |
+ echo "node_version: ${{ matrix.node_version }}"
+ node -v
+ echo "install snyk with npm"
+ npm install -g snyk
+
+ - name: Install Snyk with Yarn globally
+ if: ${{ matrix.snyk_install_method == 'yarn' }}
+ run: |
+ npm install yarn -g
+ echo "Yarn global path"
+ yarn global bin
+ echo 'export PATH="$PATH:$(yarn global bin)"' >> ~/.bash_profile
+ yarn global add snyk
+
+ - name: npm install for fixture project
+ working-directory: test/fixtures/basic-npm
+ run: |
+ npm install
+
+ - name: Run alpine test
+ if: ${{ matrix.snyk_install_method == 'alpine-binary' }}
+ env:
+ SMOKE_TESTS_SNYK_TOKEN: ${{ secrets.SMOKE_TESTS_SNYK_TOKEN }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ docker build -t snyk-cli-alpine -f ./test/smoke/alpine/Dockerfile ./test
+ docker run -eCI=1 -eSMOKE_TESTS_SNYK_TOKEN -eGITHUB_TOKEN snyk-cli-alpine
+
+ - name: Install snyk from Docker bundle
+ if: ${{ matrix.snyk_install_method == 'docker-bundle' }}
+ run: |
+ pushd "$(mktemp -d)"
+ curl 'https://static.snyk.io/cli/latest/${{ matrix.snyk_cli_dl_file }}' | tar -xz
+ pushd ./docker
+ ls -la
+ sudo ln -s "$(pwd)/snyk-mac.sh" ./snyk
+ export PATH="$(pwd):${PATH}"
+ echo "$(pwd)" >> "${GITHUB_PATH}"
+ popd
+ popd
+ which snyk
+ snyk version
+
+ - name: Run npm test with Root user
+ if: ${{ matrix.snyk_install_method == 'npm-root-user' }}
+ env:
+ SMOKE_TESTS_SNYK_TOKEN: ${{ secrets.SMOKE_TESTS_SNYK_TOKEN }}
+ run: |
+ docker build -t snyk-docker-root -f ./test/smoke/docker-root/Dockerfile ./test
+ docker run -eCI=1 -eSMOKE_TESTS_SNYK_TOKEN snyk-docker-root
+
+ - name: Install Snyk with binary - Non-Windows
+ if: ${{ matrix.snyk_install_method == 'binary' && matrix.os != 'windows' }}
+ run: |
+ curl -Lo ./snyk-cli 'https://static.snyk.io/cli/latest/${{ matrix.snyk_cli_dl_file }}'
+ chmod -R +x ./snyk-cli
+ sudo mv ./snyk-cli /usr/local/bin/snyk
+ snyk --version
+
+ - name: Install Snyk with binary - Windows
+ if: ${{ matrix.snyk_install_method == 'binary' && matrix.os == 'windows' }}
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ shell: powershell
+ run: |
+ echo "install snyk with binary"
+ echo $env:PATH
+ sh ./test/smoke/install-snyk-binary-win.sh
+
+ - name: Install Shellspec - non-windows
+ if: ${{ matrix.os != 'windows' && matrix.snyk_install_method != 'alpine-binary' }}
+ run: |
+ ./test/smoke/install-shellspec.sh --yes
+ sudo ln -s ${HOME}/.local/lib/shellspec/shellspec /usr/local/bin/shellspec
+ ls -la ${HOME}/.local/lib/shellspec
+ echo "shellspec symlink:"
+ ls -la /usr/local/bin/shellspec
+ /usr/local/bin/shellspec --version
+ which shellspec
+ shellspec --version
+
+ - name: Install test utilities with homebrew on macOS
+ if: ${{ matrix.os == 'macos' }}
+ # We need "timeout" and "jq" util and we'll use brew to check our brew package as well
+ run: |
+ brew install coreutils
+ brew install jq
+
+ - name: Install Snyk CLI with homebrew on macOS
+ if: ${{ matrix.snyk_install_method == 'brew' }}
+ run: |
+ brew tap snyk/tap
+ brew install snyk
+
+ - name: Install scoop on Windows
+ if: ${{ matrix.os == 'windows'}}
+ run: |
+ iwr -useb get.scoop.sh -outfile 'install-scoop.ps1'
+ .\install-scoop.ps1 -RunAsAdmin
+ scoop install jq
+
+ - name: Install jq on Ubuntu
+ if: ${{ matrix.os == 'ubuntu' && matrix.snyk_install_method != 'alpine-binary' && matrix.snyk_install_method != 'npm-root-user' }}
+ run: |
+ sudo apt-get install jq
+
+ - name: Install Shellspec - Windows
+ shell: powershell
+ if: ${{ matrix.os == 'windows' }}
+ run: |
+ Get-Host | Select-Object Version
+ Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
+ sh ./test/smoke/install-shellspec.sh --yes
+
+ - name: Run shellspec tests - non-Windows
+ if: ${{ matrix.os != 'windows' && matrix.snyk_install_method != 'alpine-binary' && matrix.snyk_install_method != 'npm-root-user' }}
+ working-directory: test/smoke
+ shell: bash -l {0} # run bash with --login flag to load .bash_profile that's used by yarn install method
+ env:
+ SMOKE_TESTS_SNYK_TOKEN: ${{ secrets.SMOKE_TESTS_SNYK_TOKEN }}
+ run: |
+ which snyk
+ snyk version
+ shellspec -f d --skip-message quiet
+
+ - name: Run shellspec tests - Windows
+ if: ${{ matrix.os == 'windows' }}
+ working-directory: test/smoke
+ shell: powershell
+ env:
+ SMOKE_TESTS_SNYK_TOKEN: ${{ secrets.SMOKE_TESTS_SNYK_TOKEN }}
+ run: |
+ sh ./run-shellspec-win.sh
diff --git a/.github/workflows/snyk-protect-production-smoke-tests.yml b/.github/workflows/snyk-protect-production-smoke-tests.yml
new file mode 100644
index 0000000000..a8534bb2b6
--- /dev/null
+++ b/.github/workflows/snyk-protect-production-smoke-tests.yml
@@ -0,0 +1,44 @@
+name: '@snyk/protect: Production Smoke Tests'
+
+on:
+ push:
+ branches: [protect/smoke-tests]
+ release:
+ types: [published]
+ schedule:
+ - cron: '0 */6 * * *'
+
+jobs:
+ smoke-test:
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu, macos, windows]
+ node_version: [12, 14, 16]
+ runs-on: ${{ matrix.os }}-latest
+ steps:
+ # Avoid modifying line endings in fixtures.
+ - run: git config --global core.autocrlf false
+ - uses: actions/checkout@v2
+
+ - uses: actions/setup-node@v2
+ with:
+ node-version: ${{ matrix.node_version }}
+
+ - name: npx npm@7 config get cache
+ id: npm7-cache-dir
+ run: |
+ echo "::set-output name=dir::$(npx npm@7 config get cache)"
+ - uses: actions/cache@v2
+ id: npm7-cache
+ with:
+ path: ${{ steps.npm7-cache-dir.outputs.dir }}
+ key: ${{ runner.os }}-node${{ matrix.node_version }}-npm7-${{ hashFiles('**/package.json') }}
+ restore-keys: |
+ ${{ runner.os }}-node${{ matrix.node_version }}-npm7-
+
+ - run: npx npm@7 install
+ - run: npx npm@7 run test:smoke -w @snyk/protect
+ env:
+ SNYK_TOKEN: ${{ secrets.SMOKE_TESTS_SNYK_TOKEN }}
+ PRODUCTION_TEST: '1'
diff --git a/.github/workflows/sync-cli-help-to-user-docs.yml b/.github/workflows/sync-cli-help-to-user-docs.yml
new file mode 100644
index 0000000000..5118deb6dc
--- /dev/null
+++ b/.github/workflows/sync-cli-help-to-user-docs.yml
@@ -0,0 +1,42 @@
+name: Synchronize Help
+
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: '0 12 * * 1-5' # Mon-Fri at 12
+ push:
+ branches: [chore/docs-action]
+
+jobs:
+ build:
+ name: synchronize-help
+ runs-on: ubuntu-latest
+ steps:
+ - run: |
+ gh auth setup-git
+ git config --global user.email "noreply@snyk.io"
+ git config --global user.name "$GITHUB_ACTOR"
+ gh repo clone snyk/snyk cli -- --depth=1 --quiet
+ gh repo clone snyk/user-docs docs -- --depth=1 --quiet
+ git -C ./cli checkout -b docs/automatic-gitbook-update
+
+ cp ./docs/docs/snyk-cli/commands/*.md ./cli/help/cli-commands/
+
+ if [[ $(git -C ./cli status --porcelain) ]]; then
+ echo "Documentation changes detected"
+ cd ./cli
+ npx prettier --write ./help/cli-commands
+ git --no-pager diff --name-only
+ git add .
+ git commit -m "docs: synchronizing help from snyk/user-docs"
+ git push --force --set-upstream origin docs/automatic-gitbook-update
+ if [[ ! $(gh pr view docs/automatic-gitbook-update 2>&1 | grep -q "no open pull requests";) ]]; then
+ echo "Creating PR"
+ gh pr create --title="Synchronizing CLI help from user-docs" --body="Automatic PR controlled by GitHub Action" --head docs/automatic-gitbook-update
+ fi
+ echo "PR exists, pushed changes to it."
+ else
+ echo "No documentation changes detected, exiting."
+ fi
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 4f5338738e..68f8047ad7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,12 +1,43 @@
config.local.json
/.nyc_output/
/node_modules/
+/packages/*/node_modules/
local.log
/patches/
-/dist
+**/dist
+/dist-docker
+/pysrc
+binary-releases
tmp
.DS_Store
-/package-lock.json
!/test/fixtures/**/package-lock.json
.idea
.eslintcache
+snyk-error.log
+snyk-result.json
+snyk_report.css
+snyk_report.html
+!/docker/snyk_report.css
+cert.pem
+key.pem
+**/tsconfig.tsbuildinfo
+__outputs__
+*.tgz
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# test fixture build artifacts
+/test/acceptance/workspaces/**/project/
+/test/acceptance/workspaces/**/target/
+test/acceptance/workspaces/**/.gradle
+test/**/.gradle
+.iac-data
+.dccache
+!test/smoke/.iac-data
+test-output
+test-results
+tap-output
+
+# Jest
+coverage
diff --git a/.npmignore b/.npmignore
deleted file mode 100644
index adb76ec9e1..0000000000
--- a/.npmignore
+++ /dev/null
@@ -1,6 +0,0 @@
-.git*
-/test
-config.*.json
-!config.default.json
-tmp
-.nyc_output
diff --git a/.npmrc b/.npmrc
index 43c97e719a..22bad7d136 100644
--- a/.npmrc
+++ b/.npmrc
@@ -1 +1,3 @@
-package-lock=false
+engine-strict=false
+audit=false
+fund=false
diff --git a/.nvmrc b/.nvmrc
index 1e8b314962..431076a948 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-6
+16.16.0
diff --git a/.prettierignore b/.prettierignore
index 6c6b57ac33..156c518739 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1 +1,17 @@
-test/fixtures/**/*
\ No newline at end of file
+node_modules
+tmp
+
+dist
+/pysrc
+
+fixtures
+test-output
+test-results
+test/**/workspaces
+.iac-data
+
+src/cli/commands/test/iac/local-execution/parsers/hcl-to-json/parser.js
+src/cli/commands/test/iac/local-execution/parsers/hcl-to-json-v2/parser.js
+
+# Has empty lines for templating convenience
+.github/PULL_REQUEST_TEMPLATE.md
diff --git a/.releaserc b/.releaserc
deleted file mode 100644
index beb9e4970a..0000000000
--- a/.releaserc
+++ /dev/null
@@ -1,63 +0,0 @@
-{
- "prepare": [
- "@semantic-release/npm",
- {
- "//": "build the alpine, macos, linux and windows binaries",
- "path": "@semantic-release/exec",
- "cmd": "npm i -g pkg && pkg . -t node8-alpine-x64,node8-linux-x64,node8-macos-x64,node8-win-x64"
- },
- {
- "//": "shasum all binaries",
- "path": "@semantic-release/exec",
- "cmd": "shasum -a 256 snyk-linux > snyk-linux.sha256 && shasum -a 256 snyk-macos > snyk-macos.sha256 && shasum -a 256 snyk-win.exe > snyk-win.exe.sha256 && shasum -a 256 snyk-alpine > snyk-alpine.sha256"
- }
- ],
- "publish": [
- "@semantic-release/npm",
- {
- "path": "@semantic-release/github",
- "assets": [
- {
- "path": "./snyk-linux",
- "name": "snyk-linux",
- "label": "snyk-linux"
- },
- {
- "path": "./snyk-linux.sha256",
- "name": "snyk-linux.sha256",
- "label": "snyk-linux.sha256"
- },
- {
- "path": "./snyk-macos",
- "name": "snyk-macos",
- "label": "snyk-macos"
- },
- {
- "path": "./snyk-macos.sha256",
- "name": "snyk-macos.sha256",
- "label": "snyk-macos.sha256"
- },
- {
- "path": "./snyk-win.exe",
- "name": "snyk-win.exe",
- "label": "snyk-win.exe"
- },
- {
- "path": "./snyk-win.exe.sha256",
- "name": "snyk-win.exe.sha256",
- "label": "snyk-win.exe.sha256"
- },
- {
- "path": "./snyk-alpine",
- "name": "snyk-alpine",
- "label": "snyk-alpine"
- },
- {
- "path": "./snyk-alpine.sha256",
- "name": "snyk-alpine.sha256",
- "label": "snyk-alpine.sha256"
- }
- ]
- }
- ]
-}
diff --git a/.snyk b/.snyk
index 361bd6fe7c..467c6b7dcb 100644
--- a/.snyk
+++ b/.snyk
@@ -1,19 +1,10 @@
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
-version: v1.13.5
+version: v1.22.1
# ignores vulnerabilities until expiry date; change duration by modifying expiry date
ignore:
- 'npm:mem:20180117':
- - nyc > yargs > os-locale > mem:
- reason: DoS vulnerability is not valid for CLI tool
- expires: '2019-12-19T10:35:25.346Z'
- - tap > nyc > yargs > os-locale > mem:
- reason: DoS vulnerability is not valid for CLI tool
- expires: '2019-12-19T10:35:25.346Z'
- SNYK-JS-EXECA-174564:
- - os-name > windows-release > execa:
- reason: None given
- expires: '2019-05-31T17:15:04.209Z'
- - update-notifier > boxen > term-size > execa:
- reason: None given
- expires: '2019-05-31T17:15:04.209Z'
+ SNYK-JS-ANSIREGEX-1583908:
+ - '*':
+ reason: Not affecting Snyk CLI. No upgrade path currently available
+ expires: 2022-02-01T00:00:00.000Z
+ created: 2021-11-29T17:25:19.200Z
patch: {}
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index fa7d5df739..0000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,39 +0,0 @@
-sudo: false
-language: node_js
-node_js:
- - "12"
- - "10"
- - "8"
- - "6"
-cache:
- directories:
- - node_modules
-notifications:
- email: false
- slack:
- on_failure: always
- on_success: never
- on_pull_requests: false
- rooms:
- secure: mWzYwXXkaPJ/t24kG9gz6hYyRGoGX+zsjKDa+IzjhY4NeMJyzgHyN3x1vWwSXhfG1jQuBTFyT5RTWN8Jfo2Za/XDKYfMXXP5gCMtNkeWdflYWaUC7sAZepRANUB3gzkCTvkc/DOY/FI07xCcLMJoZ7jGiADNakTaWvzdExJUBs6NuhGdIUmOT+chbip02yN7mSPeqyU7/vFqpCxxEoPeNzaNshLufbZUEWgmLm0bm5Uxdo7Yi5rL0/nA7oqXUzzWTtgQEu0mOG4Oqu7oXAki2rLISw8enZwt5/fUbxGgK1J3UB86vgnDrxbTAhuNUuddaSxUuDsg1+3xyRzU74cyKUWnWqL10Tyy9KgDR0A+48w2v8DH/pOvnvfXA+FL0zLtDJ9jPuSK0dFbceRYmolEGMDF53Q/s2W+waC13Bi3nHRQJKYmT+bOnoLABLpfm5fbV/2br4LVTQwiP80HJ+19Vy4lriF55zu1yjESUBzdvvX1Dhp5E3AXZZv6xB0v4gyZZeOoIv6BxqinLauZiS3nM7O9vu1QnFvbo4HH0Df651fyy1kOU5UAAD+CNRgpZ8GMc+EegvnLbS3nzbNOPlkACmhxMNcpEvD6MomcB4UV2dPWAYIQSfrRV5h+iZlNSqCkA2pl3p6TTZhLMvoSxziRzUZZx7GQe/cnUGN6GbzyyRk=
-install:
- - npm install
-script:
- - npm run snyk-auth
- # some tests are failing on npm@5, downgrade to npm@3 if >3 is installed
- # namely: protect-patch.test.js, wizard-prepare.test.js, wizard-prepublish.test.js
- - test "$(npm -v | cut -d'.' -f1)" -le 3 || npm i -g npm@^3.0.0
- - npm test
-jobs:
- include:
- - stage: test
- script: npx danger ci --failOnErrors
- name: "Danger Zone"
- - stage: Release
- if: branch = master AND type != pull_request
- node_js: "8"
- script:
- - npm i -g semantic-release @semantic-release/exec && semantic-release
-branches:
- only:
- - "master"
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000000..904ff0fd3f
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,40 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "node",
+ "request": "launch",
+ "name": "Tap Current File",
+ "console": "integratedTerminal",
+ "program": "${workspaceFolder}/node_modules/.bin/tap",
+ "args": [
+ "${relativeFile}",
+ "-Rspec",
+ "--timeout=300",
+ "--node-arg=-r",
+ "--node-arg=ts-node/register"
+ ]
+ },
+ {
+ "type": "node",
+ "request": "launch",
+ "name": "Jest Current File",
+ "console": "integratedTerminal",
+ "program": "${workspaceFolder}/node_modules/.bin/jest",
+ "args": ["${relativeFile}"]
+ },
+ {
+ "type": "node",
+ "request": "launch",
+ "name": "Debug Jest Current File",
+ "console": "integratedTerminal",
+ "program": "${workspaceFolder}/node_modules/.bin/jest",
+ "args": ["${relativeFile}"],
+ "runtimeArgs": [
+ "--inspect-brk",
+ "${workspaceRoot}/node_modules/.bin/jest",
+ "--runInBand"
+ ]
+ }
+ ]
+}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000000..9ca387f4df
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,297 @@
+# Contributing
+
+> This guide is for internal Snyk contributors with write access to this repository. If you are an external contributor, before working on any contributions, please first [contact support](https://support.snyk.io) to discuss the issue or feature request with us.
+
+## Prerequisites
+
+You will need the following software installed:
+
+- Git
+- Node.js (and bundled npm)
+ - Use whichever version is in [`.nvmrc`](./.nvmrc).
+
+Open a terminal and make sure they are available.
+
+```sh
+git --version
+node --version
+npm --version
+```
+
+## Setting up
+
+Clone this repository with git.
+
+```sh
+git clone git@github.com/snyk/cli.git
+cd snyk
+```
+
+You will now be on our `master` branch. You should never commit to this branch, but you should keep it up-to-date to ensure you have the latest changes.
+
+```sh
+git fetch
+git pull --ff-only
+```
+
+If you encounter vague errors without a clear solution at any point, try starting over by cloning a new copy or cleaning the project.
+
+```
+npm run clean
+```
+
+## Building
+
+Install project dependencies.
+
+```sh
+npm ci
+```
+
+Build the project.
+
+```sh
+npm run build
+```
+
+Ensure the build is working. The version should be `1.0.0-monorepo`.
+
+```sh
+npx . --version
+```
+
+For faster rebuilds, you can watch for changes. This command will keep running so you will want to run this in a separate terminal or background.
+
+```
+npm run watch
+```
+
+## Running tests
+
+You can run tests using standard Jest commands. See: [Jest CLI docs](https://jestjs.io/docs/cli).
+
+```
+npx jest --runInBand
+```
+
+If you are working on a specific project, you can filter by project.
+
+```
+npx jest --runInBand --selectProjects @snyk/protect
+```
+
+Debugger configuration is available for VS Code. Open "Run and Debug" and choose "Jest Current File".
+
+Typically, you should not run the full test suite locally. Snyk CLI includes a variety of features which require various tools and configuration to be installed. Our PR pipeline will take care of most of that. Locally, you should focus on the tests related to your changes.
+
+## Writing tests
+
+All tests end in `.spec.ts` and use Jest. There are two types of tests:
+
+- `./test/jest/unit` - Unit tests targeting source code (`./src`).
+- `./test/jest/acceptance` - Acceptance tests targeting distributables (`./dist`).
+
+Each test must start from a clean slate to avoid pollution. Do not share state between tests and always perform any setup within the test itself or in specific before and after hooks.
+
+To avoid hard wiring tests to your specific implementation, try writing the test first.
+
+### Unit tests
+
+Unit tests enforce the correctness of our source code. Ensure the path to the test mirrors the path to the source.
+
+Unit tests must be fast. They should not test services outside the code itself. Services include filesystems, processes and networks.
+
+Avoid using mocks as these can go out of sync and be difficult to maintain; prefer interfaces instead.
+
+If you are mostly testing functions calling other functions, consider writing an acceptance test instead. Otherwise, your tests will likely mirror the implementation and rely heavily on mocks; making future changes difficult.
+
+### Acceptance tests
+
+Acceptance tests enforce the correctness of our distribution and are written from the perspective of an user.
+
+Snyk CLI's acceptance tests execute a specific command line as a standalone process, then assert on `stdout`, `stdin` and the exit code. As an example, see: [`oauth-token.spec.ts`](test/jest/acceptance/oauth-token.spec.ts).
+
+Your tests should never call remote endpoints. Otherwise, our release pipelines will require those services to be available. To avoid this, we can assume external services are kept compatible. If any of these services cause issues, we can rely on production monitoring to alert us.
+
+Use [fake-server](./test/acceptance/fake-server.ts) to mock any Snyk API calls. If you are using other endpoints, mock those too in a similar way.
+
+Place fixtures in `./test/fixtures`. Keep them minimal to reduce maintenance. Use [`createProject`](./test/jest/util/createProject.ts) to use your fixtures in isolated working directories for your tests.
+
+### Smoke Tests
+
+Smoke tests typically don't run on branches unless the branch is specifically prefixed with `smoke/`. They usually run on an hourly basis against the latest published version of the CLI.
+
+If you merge a PR that changes smoke tests, remember that the tests will fail until your changes are deployed.
+
+See [the smoke tests readme](./test/smoke/README.md) for more info
+
+## Code ownership
+
+For current ownership assignments, see: [CODEOWNERS](./.github/CODEOWNERS).
+
+To avoid mixing ownership into a single file, move team-specific logic into separate files. To reduce blockers and save time, design with ownership in mind.
+
+## Adding dependencies
+
+When adding and upgrading dependencies, ensure the `package-lock.json` results in minimal updates. To do this you can:
+
+```
+npm ci
+npm install
+```
+
+It's best to avoid adding external dependencies. All dependency changes are reviewed by Hammer.
+
+## Code formatting
+
+To ensure your changes follow formatting guidelines, you can run the linter.
+
+```
+npm run lint
+```
+
+To fix various issues automatically you can install ESLint and Prettier plugins for your IDE or run the following:
+
+```
+npm run format
+```
+
+You will need to fix any remaining issues manually.
+
+## Updating documentation
+
+When making changes, ensure documentation is updated accordingly.
+
+User-facing documentation is [available on GitBook](https://docs.snyk.io/features/snyk-cli).
+
+`snyk help` output must also be [edited on GitBook](https://docs.snyk.io/features/snyk-cli/commands). Changes will automatically be pulled into Snyk CLI as pull requests.
+
+## Creating a branch
+
+Create a new branch before making any changes. Make sure to give it a descriptive name so that you can find it later.
+
+```sh
+git checkout -b type/topic
+```
+
+For example:
+
+```sh
+git checkout -b docs/contributing
+```
+
+### Branch types
+
+You can use these patterns in your branch name to enable additional checks.
+
+| Pattern | Examples | Description |
+| ------------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
+| `chore/*`, `*test*` | `chore/change`, `test/change`, `feat/change+test` | Build and test all artifacts, excluding CLIv2. Same as a [release pipeline](#creating-a-release) without the release step. |
+| `smoke/*` | `smoke/change` | Run [smoke tests](https://github.com/snyk/cli/actions/workflows/smoke-tests.yml) against the latest release. |
+| `*cliv2*` | `feat/cliv2-feature` | Build and test all artifacts, including CLIv2. |
+| default | `fix/a-bug` | Build and test your changes. |
+
+For more information, see: [Pull request checks](#pull-request-checks).
+
+## Creating commits
+
+Each commit must provide some benefit on its own without breaking the release pipeline.
+
+For larger changes, break down each step into multiple commits so that it's easy to review in pull requests and git history.
+
+Commits must follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) structure:
+
+```
+type: summary of your changes
+
+reasoning behind your changes
+```
+
+For example:
+
+```
+docs: update contributing guide
+
+We often get questions on how to contribute to this repo. What versions to use, what the workflow is, and so on. This change updates our CONTRIBUTING guide to answer those types of questions.
+```
+
+### No breaking changes
+
+Your changes must be backwards compatible and cannot break existing user pipelines.
+
+Don't use `BREAKING CHANGE` or exclamation mark `!` from the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
+
+### Commit types
+
+The commit type is used to summarize intent and to automate various steps.
+
+| Type | Description |
+| ---------- | ----------------------------------------------- |
+| `feat` | A new user-facing feature. |
+| `fix` | A bug fix for an existing feature. |
+| `refactor` | Changes which do not affect existing features. |
+| `test` | Changes to tests for existing features. |
+| `docs` | Changes to documentation for existing features. |
+| `chore` | Build, workflow and pipeline changes. |
+| `revert` | Reverting a previous commit. |
+
+## Pushing changes
+
+Once you have committed your changes, review them locally, then push them to GitHub.
+
+```
+git push
+```
+
+Do not hold onto your changes for too long. Commit and push frequently and create a pull request as soon as possible for backup and visibility.
+
+## Creating pull requests
+
+You can now [create a Draft PR](https://github.com/snyk/cli/compare) on GitHub. Make sure to switch the big green button to a "Draft Pull Request". Draft PRs allow you to ensure your PR checks pass before asking for a review.
+
+To keep things simple, try to use the most important commit as the PR's title. Provide some context and summarize the changes in your body.
+
+If your PR becomes too large, consider breaking it up into multiple PRs so that it's easier to explain, review, and integrate.
+
+## Pull request checks
+
+Your PR checks will run every time you push changes to your branch.
+
+| Name | Failure action |
+| ------------------ | --------------------------------------------------------------------- |
+| `test_and_release` | See: [Test pipeline](#test-pipeline). |
+| `Danger` | Check the comment created on your PR. |
+| `license/cla` | Visit [CLA Assistant](https://cla-assistant.io) to sign or re-run it. |
+| Everything else. | Ask Hammer. |
+
+### Test pipeline
+
+The [test pipeline](https://app.circleci.com/pipelines/github/snyk/cli?filter=mine) is on CircleCI. This is where your changes are built and tested.
+
+If any checks fail, fix them and force push your changes again. Make sure to review and tidy up your branch so that it remains easy to follow.
+
+Some tests may "flake", meaning they failed due to some external factor. While we try to fix these tests immediately, that's not always possible. You can use CircleCI's "Re-run from Failed" option to re-run only that job without needing to re-run the entire pipeline.
+
+## Review cycle
+
+Once your checks have passed, you can publish your Draft PR. Codeowners will be automatically assigned. Ask each codeowner for a review using relevant channels on Slack. Iterate on feedback.
+
+Once you have received the necessary approvals, you can merge.
+
+## Creating a release
+
+Merges will create a [release pipeline](https://app.circleci.com/pipelines/github/snyk/cli?branch=master&filter=all) which will build and test your changes against a range of target platforms.
+
+Once all tests have passed, you will be given the choice to publish a new release containing your changes.
+
+All releases are minor version bumps. For the latest releases, see: [Releases](https://github.com/snyk/cli/releases).
+
+If you do not want to publish your changes immediately, you can "Cancel Workflow".
+
+If your release pipeline fails at any step, notify Hammer.
+
+You may see some "Docker Hub" checks on your merge commit fail. This is normal and safe to ignore.
+
+---
+
+Questions? Ask Hammer 🔨
diff --git a/Contributor-Agreement.md b/Contributor-Agreement.md
index ba66800b50..dfdbdad935 100644
--- a/Contributor-Agreement.md
+++ b/Contributor-Agreement.md
@@ -1,39 +1,61 @@
-# Snyk CLI tool contributor agreement
+This Contributor Licence Agreement (“Agreement”) sets out the terms under which contributions are made to open source projects of Snyk Ltd (“Snyk”) by or on behalf of the Contributor. This Agreement is legally binding on the Contributor.
-This Snyk CLI tool Agreement (this **"Agreement"**) applies to any Contribution you make to any Work.
+Who the “Contributor” is depends on whether the person submitting the contribution is a private individual acting on their own behalf, or is acting on behalf of someone else (for example, their employer). The “Contributor” in this Agreement is therefore either: (i) if the individual who Submits a Contribution does so on behalf of their employer or another Legal Entity, any Legal Entity on behalf of whom a Contribution has been received by Snyk; or in all other cases (ii) the individual who Submits a Contribution to Snyk. "Legal Entity" means an entity which is not a natural person (for example, a limited company or corporation).
-This is a binding legal agreement on you and any organization you represent. If you are signing this Agreement on behalf of your employer or other organization, you represent and warrant that you have the authority to agree to this Agreement on behalf of the organization.
+** 1. Interpretation**
-## 1. Definitions
+The following definitions and rules of interpretation apply in this Agreement.
-**"Contribution"** means any original work, including any modification of or addition to an existing work, that you submit to Snyk CLI tool repo in any manner for inclusion in any Work.
+1.1 Definitions:
-**"Snyk", "we"** and **"use"** means Snyk Ltd.
+**Affiliates**: means, in respect of a Legal Entity, any other Legal Entities that control, are controlled by, or under common control with that Legal Entity.
-**"Work"** means any project, work or materials owned or managed by Snyk Ltd.
+**Contribution**: means any software or code that is Submitted by the Contributor to Snyk for inclusion in a Project.
-**"You"** and **"your"** means you and any organization on whose behalf you are entering this Agreement.
+**Copyright**: all copyright and rights in the nature of copyright subsisting in the Contribution in any part of the world, to which the Contributor is, or may become, entitled.
-## 2. Copyright Assignment, License and Waiver
+**Effective Date**: the earlier of the date on which the Contributor Submits the Contribution, or the date of the Contributor’s acceptance of this Agreement.
-**(a) Assignment.** By submitting a Contribution, you assign to Snyk all right, title and interest in any copright you have in the Contribution, and you waive any rights, including any moral rights, database rights, etc., that may affect your ownership of the copyright in the Contribution.
+**Patent Rights**: any patent claims which the Contributor or its Affiliates owns, controls or has the right to grant, now or in the future, to the extent infringed by the exercise of the rights licensed under this Agreement.
-**(b) License to Snyk.** If your assignment in Section 2(a) is ineffective for any reason, you grant to us and to any recipient of any Work distributed by use, a perpetual, worldwide, transferable, non-exclusive, no-charge, royalty-free, irrevocable, and sublicensable licence to use, reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Contributions and any derivative work created based on a Contribution. If your license grant is ineffective for any reason, you irrevocably waive and covenant to not assert any claim you may have against us, our successors in interest, and any of our direct or indirect licensees and customers, arising out of our, our successors in interest's, or any of our direct or indirect licensees' or customers' use, reproduction, preparation of derivative works, public display, public performance, sublicense, and distribution of a Contribution. You also agree that we may publicly use your name and the name of any organization on whose behalf you're entering into this Agreement in connection with publicizing the Work.
+**Project**: a software project to which the Contribution is Submitted.
-**(c) License to you.** We grant to you a perpetual, worldwide, transferable, non-exclusive, no-charge, royalty-free, irrevocable, and sublicensable license to use, reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute a Contribution and any derivative works you create based on a Contribution.
+**Submit**: means to submit or send to Snyk or its representatives by any form of electronic, verbal, or written communication, for example, by means of code repositories or control systems, and issue tracking systems, that are managed by or on behalf of Snyk.
-## 3. Patent License
-You grant to us and to any recipient of any Work distributed by us, a perpetual, worldwide, transferable, non-exclusive, no-charge, royalty-free, irrevocable, and sublicensable patent license to make, have made, use, sell, offer to sell, import, and otherwise transfer the Contribution in whole or in part, along or included in any Work under any patent you own, or license from a third party, that is necessarily infringed by the Contribution or by combination of the Contribution with any Work.
+**2. Licence Grant**
-## 4. Your Representation and Warranties.
-By submitting a Contribution, you represent and warrant that: (a) each Contribution you submit is an original work and you can legally grant the rights set out in this Agreement; (b) the Contribution does not, and any exercise of the rights granted by you will not, infringe any third party's intellectual property or other right; and (c) you are not aware of any claims, suits, or actions pertaining to the Contribution. You will notify us immediately if you become aware or have reason to believe that any of your representations and warranties is or becomes inaccurate.
+2.1 Copyright: The Contributor grants to Snyk a perpetual, irrevocable, worldwide, transferable, fully sublicenseable through multiple tiers, fee-free, non-exclusive licence under the Copyright to do the following acts, subject to, and in accordance with, the terms of this Agreement: to reproduce, prepare derivative works of, publicly display, publicly perform, communicate to the public, and distribute by any means Contributions and such derivative works.
-##5. Intellectual Property
-Except for the assignment and licenses set forth in this Agreement, this Agreement does not transfer any right, title or interest in any intellectual property right of either party to the other. If you choose to provide us with suggestions, ideas for improvement, recommendations or other feedback, on any Work we may use your feedback without any restriction or payment.
+2.2. Patent Rights: The Contributor grants to Snyk a perpetual, irrevocable, worldwide, transferable, fully sublicenseable through multiple tiers, fee-free, non-exclusive licence under the Patent Rights to do the following acts, subject to, and in accordance with, the terms of this Agreement: to make, have made, use, sell, offer for sale, import and otherwise transfer the Contribution and the Contribution in combination with a Project (and portions of such combination).
-## Miscellaneous
-English law governs this Agreement, excluding any applicable conflict of laws rules or principles, and the parties agree to the exclusive jurisdiction of the courts in England, UK. This Agreement does not create a partnership, agency relationship, or joint venture between the parties. We may assign this Agreement without notice or restriction. If any provision of this Agreement is unenforcable, that provision will be modified to render it enforceable to the extent possible to effect the parties' intention and the remaining provisions will not be affected. The parties may amend this Agreement only in a written amendment signed by both parties. This Agreement comprises the parties' entire agreement relating to the subject matter of this Agreement.
+2.3 The Contributor acknowledges that Snyk is not obliged to include the Contribution in any Project.
-**Agreed and accepted on my behalf and on behalf of my organization**
+2.4 If Snyk includes the Contribution in a Project, Snyk may license the Contribution under any licence terms, including copyleft, permissive, commercial, or proprietary licenses, provided that it shall also license the Contribution under the terms of any licenses which are approved by the Open Source Initiative on or after the Effective Date, including both permissive and copyleft licenses, whether or not such licenses are subsequently disapproved (including any right to adopt any future version of a license if permitted).
-Our contributor agreement is based on the [mongoDB contributor agreement] (https://www.mongodb.com/legal/contributor-agreement).
+2.5 In the event that any moral rights apply in respect of the Contribution, the Contributor, being the sole author of the Contribution, waives all moral rights in respect of the use to be made of the Contribution under this Agreement to which the Contributor may now or at any future time be entitled.
+
+**3. Warranties and Disclaimers**
+
+3.1 The Contributor warrants and represents that:
+
+(a) it is the sole owner of the Copyright and any Patent Rights and legally entitled to grant the licence in section 2;
+
+(b) the Contribution is its own original creation;
+
+(c) the licence in section 2 does not conflict with or infringe any rights granted by the Contributor or (if applicable) its Affiliates; and
+
+(d) it is not aware of any claims, suits, or actions in respect of the Contribution.
+
+3.2 All other conditions, warranties or other terms which might have effect between the parties in respect of the Contribution or be implied or incorporated into this Agreement are excluded.
+
+3.3 The Contributor is not required to provide any support for the Contribution.
+
+**4. Other important terms**
+
+4.1 Assignment/Transfer: Snyk may assign and transfer all of its rights and obligations under this Agreement to any person.
+
+4.2 Further Assurance: The Contributor shall at Snyk’s expense execute and deliver such documents and perform such acts as may reasonably be required by Snyk for the purpose of giving full effect to this Agreement.
+
+4.3 Agreement: This Agreement constitutes the entire Agreement between the parties and supersedes and extinguishes all previous Agreements, promises, assurances, warranties, representations and understandings between them, whether written or oral, relating to its subject matter.
+
+4.4 Governing law: This Agreement and any dispute or claim (including non-contractual disputes or claims) arising out of or in connection with it or its subject matter or formation shall be governed by and construed in accordance with the law of England and Wales.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000..e7b366df91
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,108 @@
+#!make
+#
+# This Makefile is only for building release artifacts. Use `npm run` for CLIv1 scripts.
+#
+# Documentation: https://www.gnu.org/software/make/manual/make.html
+#
+
+PKG := npx pkg ./ --compress Brotli
+
+.DEFAULT: help
+.PHONY: help
+help:
+ @echo 'Usage: make '
+ @echo
+ @echo 'This Makefile is currently only for building release artifacts.'
+ @echo 'Use `npm run` for CLIv1 scripts.'
+
+binary-releases:
+ mkdir binary-releases
+
+binary-releases/version: | binary-releases
+ ./release-scripts/next-version.sh > binary-releases/version
+
+# prepack is not a typical target.
+# It modifies package.json files rather than only creating new files.
+# INTERMEDIATE prevents dependants from rebuilding when prepack is stale.
+# It will act as a passthrough and only rebuild dependants when version has changed.
+# SECONDARY disables INTERMEDIATE's auto-remove feature.
+# Only removing "prepack" is not enough. We need to do additional cleanup (see clean-prepack).
+.INTERMEDIATE: prepack
+.SECONDARY: prepack
+prepack: binary-releases/version
+ @echo "'make prepack' was run. Run 'make clean-prepack' to rollback your package.json changes and this file." > prepack
+ npm version "$(shell cat binary-releases/version)" --no-git-tag-version --workspaces --include-workspace-root
+ npx ts-node ./release-scripts/prune-dependencies-in-packagejson.ts
+
+.PHONY: clean-prepack
+clean-prepack:
+ git checkout package.json package-lock.json packages/*/package.json packages/*/package-lock.json
+ rm -f prepack
+
+binary-releases/sha256sums.txt.asc: $(wildcard binary-releases/*.sha256)
+ ./release-scripts/sha256sums.txt.asc.sh
+
+binary-releases/release.json: binary-releases/version $(wildcard binary-releases/*.sha256)
+ ./release-scripts/release.json.sh
+
+# --commit-path is forwarded to `git log `.
+# We're using this to remove CLIv2 changes in v1's changelogs.
+# :(exclude) syntax: https://git-scm.com/docs/gitglossary.html#Documentation/gitglossary.txt-exclude
+# Release notes uses version from package.json so we need to prepack beforehand.
+binary-releases/RELEASE_NOTES.md: prepack | binary-releases
+ npx conventional-changelog-cli -p angular -l -r 1 --commit-path ':(exclude)cliv2' > binary-releases/RELEASE_NOTES.md
+
+# Generates a shasum of a target with the same name.
+# See "Automatic Variables" in GNU Make docs (linked at the top)
+%.sha256: %
+ cd $(@D); shasum -a 256 $( $(@F); shasum -a 256 -c $(@F)
+
+binary-releases/snyk.tgz: prepack | binary-releases
+ mv $(shell npm pack) binary-releases/snyk.tgz
+ $(MAKE) binary-releases/snyk.tgz.sha256
+
+binary-releases/snyk-fix.tgz: prepack | binary-releases
+ mv $(shell npm pack --workspace '@snyk/fix') binary-releases/snyk-fix.tgz
+ $(MAKE) binary-releases/snyk-fix.tgz.sha256
+
+binary-releases/snyk-protect.tgz: prepack | binary-releases
+ mv $(shell npm pack --workspace '@snyk/protect') binary-releases/snyk-protect.tgz
+ $(MAKE) binary-releases/snyk-protect.tgz.sha256
+
+binary-releases/snyk-alpine: prepack | binary-releases
+ $(PKG) -t node16-alpine-x64 -o binary-releases/snyk-alpine
+ $(MAKE) binary-releases/snyk-alpine.sha256
+
+binary-releases/snyk-linux: prepack | binary-releases
+ $(PKG) -t node16-linux-x64 -o binary-releases/snyk-linux
+ $(MAKE) binary-releases/snyk-linux.sha256
+
+# Why `--no-bytecode` for Linux/arm64:
+# arm64 bytecode generation requires various build tools on an x64 build
+# environment. So disabling until we can support it. It's an optimisation.
+# https://github.com/vercel/pkg#targets
+binary-releases/snyk-linux-arm64: prepack | binary-releases
+ $(PKG) -t node16-linux-arm64 -o binary-releases/snyk-linux-arm64 --no-bytecode
+ $(MAKE) binary-releases/snyk-linux-arm64.sha256
+
+binary-releases/snyk-macos: prepack | binary-releases
+ $(PKG) -t node16-macos-x64 -o binary-releases/snyk-macos
+ $(MAKE) binary-releases/snyk-macos.sha256
+
+binary-releases/snyk-win.exe: prepack | binary-releases
+ $(PKG) -t node16-win-x64 -o binary-releases/snyk-win-unsigned.exe
+ ./release-scripts/sign-windows-binary.sh
+ rm binary-releases/snyk-win-unsigned.exe
+ $(MAKE) binary-releases/snyk-win.exe.sha256
+
+binary-releases/snyk-for-docker-desktop-darwin-x64.tar.gz: prepack | binary-releases
+ ./docker-desktop/build.sh darwin x64
+ $(MAKE) binary-releases/snyk-for-docker-desktop-darwin-x64.tar.gz.sha256
+
+binary-releases/snyk-for-docker-desktop-darwin-arm64.tar.gz: prepack | binary-releases
+ ./docker-desktop/build.sh darwin arm64
+ $(MAKE) binary-releases/snyk-for-docker-desktop-darwin-arm64.tar.gz.sha256
+
+binary-releases/docker-mac-signed-bundle.tar.gz: prepack | binary-releases
+ ./release-scripts/docker-desktop-release.sh
+ $(MAKE) binary-releases/docker-mac-signed-bundle.tar.gz.sha256
diff --git a/README.md b/README.md
index a5a68df0ba..2237ef928a 100644
--- a/README.md
+++ b/README.md
@@ -2,215 +2,324 @@
-
- Documentation |
- Test your project
-
-
-
- Snyk helps you find, fix and monitor known vulnerabilities in open source
-
+# Snyk CLI
-
-
-
-
+[Snyk](https://snyk.io) scans and monitors your projects for security vulnerabilities.
----
+
-## What is Snyk?
+# What is [Snyk](https://snyk.io)?
-
-
-
-
-
+[Snyk](https://snyk.io) is a developer-first cloud-native security tool.
+It covers multiple areas of application security:
-## Table Of Contents:
+1. [**Snyk Open Source**](https://snyk.io/product/open-source-security-management/): Find and automatically fix open source vulnerabilities
+2. [**Snyk Code**](https://snyk.io/product/snyk-code/): Find and fix vulnerabilities in your application code in real time
+3. [**Snyk Container**](https://snyk.io/product/container-vulnerability-management/): Find and fix vulnerabilities in container images and Kubernetes applications
+4. [**Snyk Infrastructure as Code**](https://snyk.io/product/infrastructure-as-code-security/): Find and fix insecure configurations in Terraform and Kubernetes code
-- [Installation](#installation)
-- [CLI](#cli)
-- [Features](#features)
-- [Docker](#docker)
-- [Badge](#badge)
+[Learn more about what Snyk can do and sign up for a free account »](https://snyk.io)
-## Installation
+# What is Snyk CLI?
-1. Install the Snyk utility using `npm install -g snyk`.
-2. Once installed you will need to authenticate with your Snyk account: `snyk auth`
+Snyk CLI brings the functionality of [Snyk](https://snyk.io) into your development workflow. It can be run locally or in your CI/CD pipeline to scan your projects for security issues.
-For more detail on how to authenticate take a look at the [CLI authentication](https://snyk.io/docs/using-snyk#authentication?utm_campaign=docs&utm_medium=github&utm_source=CLI_authentication) section of the Snyk documentation.
+## Supported languages and tools
-## CLI
+Snyk supports many languages and tools, including Java, .NET, JavaScript, Python, Golang, PHP, C/C++, Ruby, Scala and more. See our [Language Support documentation](https://support.snyk.io/hc/en-us/articles/360020352437-Language-support-summary).
-```console
-snyk [options] [command] [package]
-```
+CLI also supports [Docker scanning](https://support.snyk.io/hc/en-us/articles/360003946897-Snyk-Container-security-overview) and [Terraform, k8s and other Infrastructure as Code files scanning](https://support.snyk.io/hc/en-us/categories/360001342678-Infrastructure-as-code).
-Run `snyk --help` to get a quick overview of all commands or for full details on the CLI read the snyk.io [CLI docs](https://snyk.io/docs/using-snyk?utm_campaign=docs&utm_medium=github&utm_source=cli).
+---
-The package argument is optional. If no package is given, Snyk will run the command against the current working directory allowing you test you non-public applications.
+# Install Snyk CLI
-## Features
+Snyk CLI can be installed through multiple channels.
-- **Find** known vulnerabilities by running `snyk test` on a project either as a one off or as part of your CI process.
-- **Fix** vulnerabilities using `snyk wizard` and `snyk protect`.
- - `snyk wizard` walks you through finding and fixing known vulnerabilities in your project. Remediation options include configuring your policy file to update, auto patch and ignore vulnerabilities. (npm only)
- - `snyk protect` your code from vulnerabilities by applying patches and optionally suppressing specific vulnerabilities.
-- **Alert** `snyk monitor` records the state of dependencies and any vulnerabilities on snyk.io so you can be alerted when new vulnerabilities or updates/patches are disclosed that affect your repositories.
-- **Prevent** new vulnerable dependencies from being added to your project by running `snyk test` as part of your CI to fail tests when vulnerable Node.js or Ruby dependencies are added.
+## Install with npm or Yarn
-## Docker
+[Snyk CLI is available as an npm package](https://www.npmjs.com/package/snyk). If you have Node.js installed locally, you can install it by running:
-Snyk is also provided as a set of Docker images that carry the runtime environment of each package manager. For example, the npm image will carry all of the needed setup to run `npm install` on the currently running container. Currently there are images for npm, Ruby, Maven, Gradle and SBT.
+```bash
+npm install snyk@latest -g
+```
-The images can perform `snyk test` by default on the specified project which is mounted to the container as a read/write volume, and `snyk monitor` if the `MONITOR` environment variable is set when running the docker container. If you want an HTML report for `test` command, make sure `--json` parameter is provided. `monitor` command appends it automatically. An HTML file called `snyk_report.html` and a CSS file called `snyk_report.css` will be generated. The image also writes a file called `snyk-res.json` for internal use and `snyk-error.log` for errors that we can look at if something goes wrong.
+or if you are using Yarn:
-The following environment variables can be used when running the container on docker:
+```bash
+yarn global add snyk
+```
-- `SNYK_TOKEN` - Snyk API token, obtained from [https://app.snyk.io/account](https://app.snyk.io/account).
-- `USER_ID` - [OPTIONAL] Current user ID on the host machine. If not provided will take the user ID of the currently running user inside the container. This is used for CI builds such as Jenkins where we are running with a non-privileged user and want to allow the user to access the mounted project folder.
-- `MONITOR` - [OPTIONAL] If set, tells the image that we want to run `snyk monitor` after running `snyk test`.
-- `PROJECT_FOLDER` - [OPTIONAL] If set, this will cd to the directory inside the mounted project dir to run snyk inside it.
-- `ENV_FLAGS` - [OPTIONAL] additional environment parameters to pass to `snyk test` when running the container.
+## More installation methods
-Docker images are tagged according to the package manager runtime they include, the package manager version and snyk version.
-The general format of tags is [snyk-version]-[package-manager]-[package-manager-version] or just [package-manager]-[package-manager-version] if we want to use the latest version of snyk. Please see available tags to see the available options.
+
+ Standalone executables (macOS, Linux, Windows)
-[snyk-version] - The version of snyk that is installed in the image, if version is omitted it will use the latest version.
-[package-manager] - One of the available package managers (e.g: npm, mvn, gradle, etc...).
-[package-manager-version] - The version of the package manager that is installed inside the image.
+### Standalone executables
-Please see the following examples on how to run Snyk inside docker:
+Use [GitHub Releases](https://github.com/snyk/snyk/releases) to download a standalone executable of Snyk CLI for your platform.
-### Node.js (npm)
+We also provide these standalone executables on our official CDN. See [the `release.json` file](https://static.snyk.io/cli/latest/release.json) for the download links:
-We will need to mount the project root folder when running the image so that Snyk can access the code within the container. The host project folder will be mounted to `/project` on the container and will be used to read the dependencies file and write results for CI builds. Here's an example of running `snyk test` and `snyk monitor` in the image (with the latest version of Snyk) for npm:
+```text
+https://static.snyk.io/cli/latest/release.json
+# Or for specific version or platform
+https://static.snyk.io/cli/v1.666.0/release.json
+https://static.snyk.io/cli/latest/snyk-macos
```
-docker run -it
- -e "SNYK_TOKEN="
- -e "USER_ID=1234"
- -e "MONITOR=true"
- -v ":/project"
- snyk/snyk-cli:npm test --org=my-org-name
+
+For example, to download and run the latest Snyk CLI on macOS, you could run:
+
+```bash
+curl https://static.snyk.io/cli/latest/snyk-macos -o snyk
+chmod +x ./snyk
+mv ./snyk /usr/local/bin/
```
-### RubyGems
+You can also use these direct links to download the executables:
-We will need to mount the project root folder when running the image so that Snyk can access the code within the container. The host project folder will be mounted to `/project` on the container and will be used to read the dependencies file and write results for CI builds. Here's an example of running `snyk test` and `snyk monitor` in the image (with the latest version of Snyk) for RubyGems:
+- macOS: https://static.snyk.io/cli/latest/snyk-macos
+- Windows: https://static.snyk.io/cli/latest/snyk-win.exe
+- Linux: https://static.snyk.io/cli/latest/snyk-linux
+- Linux (arm64): https://static.snyk.io/cli/latest/snyk-linux-arm64
+- Alpine: https://static.snyk.io/cli/latest/snyk-alpine
-```
-docker run -it
- -e "SNYK_TOKEN="
- -e "USER_ID=1234"
- -e "MONITOR=true"
- -v ":/project"
- snyk/snyk-cli:rubygems test --org=my-org-name
-```
+Drawback of this method is, that you will have to manually keep the Snyk CLI up to date.
+
+#### Verifying standalone binaries
+
+You can verify both shasum of downloaded binaries and their GPG signatures.
-### Maven 3.5.4
+Download location on `static.snyk.io` contains a file called `sha256sums.txt.asc`.
+You can download it directly `https://static.snyk.io/cli/latest/sha256sums.txt.asc` or for a specific version like `https://static.snyk.io/cli/v1.666.0/sha256sums.txt.asc`.
-We will need to mount the project root folder when running the image so that Snyk can access the code within the container and mount the local .m2 and .ivy2 folders. The host project folder will be mounted to `/project` on the container and will be used to read the dependencies file and write results for CI builds. Here's an example of running `snyk test` and `snyk monitor` in the image (with the latest version of Snyk) for Maven:
+To check that a downloaded file matches the checksum, use a `sha256sum` command like so:
+```bash
+grep snyk-macos sha256sums.txt.asc | sha256sum -c -
```
-docker run -it
- -e "SNYK_TOKEN="
- -e "USER_ID=1234"
- -e "MONITOR=true"
- -v ":/project"
- -v "/home/user/.m2:/home/node/.m2"
- -v "/home/user/.ivy2:/home/node/.ivy2"
- snyk/snyk-cli:maven-3.5.4 test --org=my-org-name
+
+If you want to verify Snyk CLI standalone binaries against [Snyk CLI GPG key](help/_about-this-project/snyk-code-signing-public.pgp), you will need to import it first:
+
+```bash
+# 68BFBCCEB7794E6FC06A2044A29C32E91F4B9569 is the key belonging to code-signing@snyk.io
+# Copy of this public key is also in this repository /help/_about-this-project/snyk-code-signing-public.pgp
+gpg --keyserver hkps://keys.openpgp.org --recv-keys 68BFBCCEB7794E6FC06A2044A29C32E91F4B9569
```
-### SBT 0.13.16 / SBT 1.0.4
+Then verify the file is signed with:
-We will need to mount the project root folder when running the image so that Snyk can access the code within the container and mount the local .m2 and .ivy2 folders. The host project folder will be mounted to `/project` on the container and will be used to read the dependencies file and write results for CI builds. Here are examples of running `snyk test` and `snyk monitor` in the image (with the latest version of Snyk) for SBT:
+```bash
+gpg --verify sha256sums.txt.asc
+```
-NOTE: the `dependency-tree` module is required for `snyk` to process Scala projects. Use [version 0.8.2](https://github.com/jrudolph/sbt-dependency-graph/tree/v0.8.2) for SBT 0.13.16 and [version 0.9.0](https://github.com/jrudolph/sbt-dependency-graph/tree/v0.9.0) for version SBT 1.0.4.
+Command output should look like:
+```plain
+gpg: Signature made Mon Apr 25 16:55:01 2022 CEST
+gpg: using RSA key 68BFBCCEB7794E6FC06A2044A29C32E91F4B9569
+gpg: Good signature from "Snyk Limited " [unknown]
+gpg: WARNING: This key is not certified with a trusted signature!
+gpg: There is no indication that the signature belongs to the owner.
+Primary key fingerprint: 68BF BCCE B779 4E6F C06A 2044 A29C 32E9 1F4B 9569
```
-docker run -it
- -e "SNYK_TOKEN="
- -e "USER_ID=1234"
- -e "MONITOR=true"
- -v ":/project"
- -v "/home/user/.m2:/home/node/.m2"
- -v "/home/user/.ivy2:/home/node/.ivy2"
- snyk/snyk-cli:sbt-0.13.16 test --org=my-org-name
+
+
+
+
+ Install with Homebrew (macOS, Linux)
+
+### Homebrew
+
+Install Snyk CLI from [Snyk tap](https://github.com/snyk/homebrew-tap) with [Homebrew](https://brew.sh) by running:
+
+```bash
+brew tap snyk/tap
+brew install snyk
```
+
+
+
+ Scoop (Windows)
+
+### Scoop
+
+Install Snyk CLI from our [Snyk bucket](https://github.com/snyk/scoop-snyk) with [Scoop](https://scoop.sh) on Windows:
+
```
-docker run -it
- -e "SNYK_TOKEN="
- -e "USER_ID=1234"
- -e "MONITOR=true"
- -v ":/project"
- -v "/home/user/.m2:/home/node/.m2"
- -v "/home/user/.ivy2:/home/node/.ivy2"
- snyk/snyk-cli:sbt-1.0.4 test --org=my-org-name
+scoop bucket add snyk https://github.com/snyk/scoop-snyk
+scoop install snyk
```
-### Gradle 2.8 / Gradle 4.4 / Gradle 5.4
+
-We will need to mount the project root folder when running the image so that Snyk can access the code within the container and mount the local .m2 and .ivy2 folders. The host project folder will be mounted to `/project` on the container and will be used to read the dependencies file and write results for CI builds. Here's an example of running `snyk test` and `snyk monitor` in the image (with the latest version of Snyk) for Gradle:
+
+ Snyk CLI in a Docker image
+### Snyk CLI in a Docker image
+
+Snyk CLI can also be run from a Docker image. Snyk offers multiple Docker tags under [`snyk/snyk`](https://hub.docker.com/r/snyk/snyk). These images wrap the Snyk CLI and depending on the Tag come with a relevant tooling for different projects. [See the snyk/images on GitHub for more details and examples](https://github.com/snyk/snyk-images).
+
+
+
+## Install as a part of a Snyk CLI integration
+
+Snyk also offers many integrations into developer tooling. These integrations will install and manage the Snyk CLI for you. For example:
+
+- [Snyk Jenkins plugin](https://github.com/jenkinsci/snyk-security-scanner-plugin)
+- [CircleCI Orb](https://github.com/snyk/snyk-orb)
+- [Azure Pipelines Task](https://github.com/snyk/snyk-azure-pipelines-task)
+- [GitHub Actions](https://github.com/snyk/actions)
+- [IntelliJ IDE Plugin](https://github.com/snyk/snyk-intellij-plugin)
+- [VS Code Extension](https://marketplace.visualstudio.com/items?itemName=snyk-security.snyk-vulnerability-scanner)
+- [Eclipse IDE Extension](https://github.com/snyk/snyk-eclipse-plugin)
+- [Maven plugin](https://github.com/snyk/snyk-maven-plugin)
+- And many more. See [the Integrations documentation](https://support.snyk.io/hc/en-us/categories/360000598398-Integrations)
+
+
+
+
+
+
+
+---
+
+# Getting started with Snyk CLI
+
+Once you installed the Snyk CLI, you can verify it's working by running:
+
+```bash
+snyk --help
```
-docker run -it
- -e "SNYK_TOKEN="
- -e "USER_ID=1234"
- -e "MONITOR=true"
- -v ":/project"
- -v "/home/user/.m2:/home/node/.m2"
- -v "/home/user/.ivy2:/home/node/.ivy2"
- snyk/snyk-cli:gradle-2.8 test --org=my-org-name
-```
+See the [full Snyk CLI help](./help/cli-commands).
+
+## Authenticating Snyk CLI
+
+Snyk CLI depends on [Snyk.io](https://snyk.io) APIs. Connect your Snyk CLI with [Snyk.io](https://snyk.io) by running:
+
+```bash
+snyk auth
```
-docker run -it
- -e "SNYK_TOKEN="
- -e "USER_ID=1234"
- -e "MONITOR=true"
- -v ":/project"
- -v "/home/user/.m2:/home/node/.m2"
- -v "/home/user/.ivy2:/home/node/.ivy2"
- snyk/snyk-cli:gradle-4.4 test --org=my-org-name
+
+## Setting up language support
+
+Depending on your project's language, you might need to setup your language environment before using Snyk.
+
+See our [Language Support documentation](https://support.snyk.io/hc/en-us/articles/360020352437-Language-support-summary).
+
+## Scanning your project
+
+If you are already in a folder with a supported project, start by running:
+
+```bash
+snyk test
```
+Or scan a Docker image by its tag with [Snyk Container](https://snyk.io/product/container-vulnerability-management/):
+
+```bash
+snyk container test ubuntu:18.04
```
-docker run -it
- -e "SNYK_TOKEN="
- -e "USER_ID=1234"
- -e "MONITOR=true"
- -v ":/project"
- -v "/home/user/.m2:/home/node/.m2"
- -v "/home/user/.ivy2:/home/node/.ivy2"
- snyk/snyk-cli:gradle-5.4 test --org=my-org-name
+
+Or a k8s file:
+
+```bash
+snyk iac test /path/to/kubernetes_file.yaml
```
-### Docker
+## Monitoring your project
+
+Snyk can also monitor your project periodically and alert you for new vulnerabilities. The `snyk monitor` is similar to `snyk test` and can be used to create a project on the Snyk website that will be continuously monitored for new vulnerabilities.
-We will need to mount the project root folder when running the image so that Snyk can access the code within the container and Docker socket so that Snyk can access Docker daemon. The host project folder will be mounted to `/project` on the container and will be used to read the Docker file (with --file). Here's an example of running `snyk test` and `snyk monitor` in the image (with the latest version of Snyk) for Docker:
+
+
+
+
+
```
-docker run -it
- -e "SNYK_TOKEN="
- -e "USER_ID=1234"
- -e "MONITOR=true"
- -v ":/project"
- -v "/var/run/docker.sock:/var/run/docker.sock"
- snyk/snyk-cli:docker test --docker myapp:mytag --file=
+> snyk monitor
+Monitoring /project (project-name)...
+
+Explore this snapshot at https://app.snyk.io/org/my-org/project/29361c2c-9005-4692-8df4-88f1c040fa7c/history/e1c994b3-de5d-482b-9281-eab4236c851e
+
+Notifications about newly disclosed issues related to these dependencies will be emailed to you.
```
-## Badge
+### Add Snyk to your CI/CD
+
+Snyk is really powerful when you are continuously scanning and monitoring your projects for vulnerabilities.
+
+Use one of [our integrations](#install-as-a-part-of-a-snyk-cli-integration) to stay secure.
-Make users feel more confident in using your website by adding your Snyk badge!
+You can authorize Snyk CLI in your CI/CD programatically:
-
+```bash
+# Using a SNYK_TOKEN envvar (preferred)
+SNYK_TOKEN= snyk test
+# Or using a Snyk auth command
+snyk auth
+snyk test
```
-[](https://snyk.io/package/npm/snyk)
+
+## More flags and options to try
+
+Here are some flags that you might find useful:
+
+- `--severity-threshold=low|medium|high|critical`
+
+ Only report vulnerabilities of provided level or higher.
+
+- `--json`
+
+ Prints results in JSON format.
+
+- `--all-projects`
+
+ Auto-detect all projects in working directory
+
+[See all the available commands and options](./help/cli-commands) by running `--help`:
+
+```bash
+snyk --help
+# or get help for a specific command like
+snyk iac --help
+snyk code --help
```
-[](https://snyk.io/)
+# Getting support
+
+If you need support using Snyk CLI, please [contact support](https://support.snyk.io).
+
+We do not actively monitor GitHub Issues so any issues there may go unnoticed.
+
+# Contributing
+
+If you are an external contributor, before working on any contributions, please first [contact support](https://support.snyk.io) to discuss the issue or feature request with us.
+
+If you are contributing to Snyk CLI, see [our contributing guidelines](CONTRIBUTING.md)
+
+For information on how Snyk CLI is implemented, see [our design decisions](help/_about-this-project/README.md).
+
+This repository is a monorepo, also covering other projects and tools:
+
+- [`@snyk/fix`](packages/snyk-fix): npm package for `snyk fix` libraries.
+- [`@snyk/protect`](packages/snyk-protect): npm package for [`snyk-protect`](https://www.npmjs.com/package/@snyk/protect) command.
+
+# Security
+
+For any security issues or concerns, please see [SECURITY.md](SECURITY.md) file in this repository.
+
+# Notices
+
+## Snyk API usage policy
+
+The use of Snyk's API, whether through the use of the 'snyk' npm package or otherwise, is subject to the [Terms & Conditions](https://snyk.co/ucT6N).
+
+---
+
+Made with 💜 by Snyk
diff --git a/SECURITY.md b/SECURITY.md
index 721a9df983..ed448c67e4 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -6,7 +6,7 @@ If you believe you have found a security vulnerability on Snyk, we encourage you
Submit your report to security@snyk.io (one issue per report) and respond to the report with any updates. Please do not contact employees directly or through other channels about a report.
-Report security bugs in third-party modules to the person or team maintaining the module. You can also report a vulnerability through our [Snyk Vulnerability Disclosure](https://docs.google.com/a/snyk.io/forms/d/e/1FAIpQLSemwgWZ0JgK1ZULKhy9DZCQ5KulbLEldvmokAuRtt-_nrqNlA/viewform) program.
+Report security bugs in third-party modules to the person or team maintaining the module. You can also report a vulnerability through our [Snyk Vulnerability Disclosure](https://snyk.io/vulnerability-disclosure/) program.
### Responsible Disclosure Policy
@@ -18,4 +18,4 @@ We ask that:
- You do not exploit a security issue you discover for any reason. (This includes demonstrating additional risk, such as attempted compromise of sensitive company data or probing for additional issues).
- You do not violate any other applicable laws or regulations.
-Find out more about our [security policy](https://snyk.io/docs/security) and [Bug Bounty program](https://snyk.io/docs/security#snyk-s-vulnerability-disclosure-program)
+Find out more about our [Bug Bounty program](https://docs.snyk.io/more-info/disclosing-vulnerabilities/disclose-a-vulnerability-in-snyk-services)
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index c11492b394..0000000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,58 +0,0 @@
-# https://www.appveyor.com/docs/appveyor-yml
-
-# to disable automatic builds
-build: off
-
-# branches to build
-branches:
- # whitelist
- only:
- - develop
- - master
-
-# Do not build on tags (GitHub and BitBucket)
-skip_tags: true
-
-# Fix line endings on Windows
-init:
- - git config --global core.autocrlf true
-
-# quicker git clone
-shallow_clone: true
-clone_depth: 1
-
-# cache
-cache:
- - node_modules -> package.json # cache node_modules, reset if package.json is modified
-
-# Test against these versions of Node.js
-environment:
- matrix:
- - nodejs_version: "6"
- - nodejs_version: "8"
- - nodejs_version: "10"
- - nodejs_version: "12"
-
-# fastfail
-matrix:
- fast_finish: true
-
-# Install scripts. (runs after repo cloning)
-install:
- # Get the latest stable version of Node.js or io.js
- - ps: Install-Product node $env:nodejs_version
- # Output useful info for debugging.
- - node --version
- - npm --version
- # install modules
- - npm install
-
-# Post-install test scripts.
-test_script:
- - npm run snyk-auth-windows
- # some tests are failing on npm@5, downgrade to npm@3
- # namely: protect-patch.test.js, wizard-prepare.test.js, wizard-prepublish.test.js
- - npm i -g npm@^3.10.10
- - npm --version
- - npm run build
- - npm run tap
diff --git a/bin/snyk b/bin/snyk
new file mode 100755
index 0000000000..b0169e7a4a
--- /dev/null
+++ b/bin/snyk
@@ -0,0 +1,2 @@
+#!/usr/bin/env node
+require('../dist/cli/index.js');
diff --git a/check-dependencies.config.ts b/check-dependencies.config.ts
new file mode 100644
index 0000000000..d086336226
--- /dev/null
+++ b/check-dependencies.config.ts
@@ -0,0 +1,15 @@
+import { Options } from 'depcheck';
+
+export const config: Options = {
+ ignoreMatches: [
+ 'sarif', // we only use @types/sarif. https://github.com/depcheck/depcheck/issues/640
+ '@types/jest', // jest is a global so impossible to detect usage of types
+ 'ts-loader', // used by webpack
+ 'node-loader', // used by webpack
+ 'webpack-cli', // used in package.json scripts
+ 'pkg', // used for binary builds
+ 'conventional-changelog-cli', // used for generating release notes
+ 'ts-node', // used for various scripts to avoid separate compile step
+ ],
+ ignoreDirs: ['node_modules', 'dist', 'fixtures', 'test-output'],
+};
diff --git a/cliv2/.gitignore b/cliv2/.gitignore
new file mode 100644
index 0000000000..378ccbc090
--- /dev/null
+++ b/cliv2/.gitignore
@@ -0,0 +1,12 @@
+.DS_STORE
+internal/cliv2/cliv2.version
+internal/embedded/cliv1/cliv1.version
+internal/embedded/cliv1/snyk-*
+internal/embedded/cliv1/embedded*.go
+main
+sbin
+_bin
+_cache
+.dccache
+.vscode
+bin
diff --git a/cliv2/Makefile b/cliv2/Makefile
new file mode 100644
index 0000000000..50d5f06815
--- /dev/null
+++ b/cliv2/Makefile
@@ -0,0 +1,259 @@
+# Build system related variables
+GOCMD = go
+GOOS = $(shell go env GOOS)
+GOARCH = $(shell go env GOARCH)
+HASH = sha
+HASH_ALGORITHM = 256
+CLI_V2_VERSION_TAG = 2.0.0-prerelease
+CLI_V1_VERSION_TAG =
+CLI_V1_LOCATION =
+
+# Make directories per convention
+prefix = /usr/local
+exec_prefix = $(prefix)
+bindir = $(exec_prefix)/bin
+
+# Project related variables
+WORKING_DIR = $(CURDIR)
+BUILD_DIR = $(WORKING_DIR)/_bin
+CACHE_DIR = $(WORKING_DIR)/_cache
+SRCS = $(shell find $(WORKING_DIR) -type f -name '*.go')
+
+# load cached variables if available
+-include $(CACHE_DIR)/variables.mk
+-include $(CACHE_DIR)/version.mk
+
+# the platform string used by the deployed binaries is not excatly OS-ARCH so we need to translate a bit
+_GO_OS = $(GOOS)
+_V1_OS = $(GOOS)
+_V1_ARCH = $(GOARCH)
+_EMPTY =
+_EXE_POSTFIX =
+_SEPARATOR = _
+
+ifeq ($(_V1_OS), darwin)
+ _V1_OS = macos
+#temporarily irgnoring any architecture for macos v1 binaries, this will enable to natively compile v2 as darwin arm64 and bundle v1 as amd64
+ _V1_ARCH =
+else ifeq ($(_V1_OS), windows)
+ _V1_OS = win
+ _EXE_POSTFIX = .exe
+endif
+
+ifeq ($(_V1_ARCH), amd64)
+ _V1_ARCH =
+endif
+
+ifeq ($(_V1_ARCH), $(_EMPTY))
+ V1_PLATFORM_STING = $(_V1_OS)
+else
+ V1_PLATFORM_STING = $(_V1_OS)-$(_V1_ARCH)
+endif
+
+# find out whether to download CLIv1 executable
+_V1_DOWNLOAD = true
+ifneq ($(CLI_V1_LOCATION), $(_EMPTY))
+ ifeq ($(CLI_V1_VERSION_TAG), $(_EMPTY))
+ CLI_V1_VERSION_TAG = $(shell cat "$(CLI_V1_LOCATION)/version")
+ endif
+ _V1_DOWNLOAD = false
+endif
+
+ifeq ($(_GO_OS), alpine)
+ _GO_OS = linux
+endif
+
+# some globally assembled variables
+APPLICATION_NAME = snyk
+TEST_NAME = $(APPLICATION_NAME)$(_SEPARATOR)tests
+V2_PLATFORM_STRING = $(GOOS)$(_SEPARATOR)$(GOARCH)
+V2_EXECUTABLE_NAME = $(APPLICATION_NAME)$(_SEPARATOR)$(V2_PLATFORM_STRING)$(_EXE_POSTFIX)
+V1_EXECUTABLE_NAME = $(APPLICATION_NAME)-$(V1_PLATFORM_STING)$(_EXE_POSTFIX)
+V2_DIRECTORY = $(WORKING_DIR)/internal/cliv2
+V1_DIRECTORY = $(WORKING_DIR)/internal/embedded/cliv1
+V1_DOWNLOAD_LINK = https://static.snyk.io/cli/v$(CLI_V1_VERSION_TAG)/$(V1_EXECUTABLE_NAME)
+V1_EMBEDDED_FILE_TEMPLATE = $(V1_DIRECTORY)/embedded_binary_template.txt
+V1_EMBEDDED_FILE_OUTPUT = embedded$(_SEPARATOR)$(V2_PLATFORM_STRING).go
+HASH_STRING = $(HASH)$(HASH_ALGORITHM)
+TEST_SNYK_EXECUTABLE_PATH=$(BUILD_DIR)/$(V2_EXECUTABLE_NAME)
+TEST_EXECUTABLE_NAME = $(TEST_NAME)$(_SEPARATOR)$(V2_PLATFORM_STRING)$(_EXE_POSTFIX)
+SIGN_SCRIPT = sign_$(_GO_OS).sh
+ISSIGNED_SCRIPT = issigned_$(_GO_OS).sh
+
+# some make file variables
+LOG_PREFIX = --
+
+# determine the latest cli version if no version was explicitly defined
+$(CACHE_DIR)/version.mk: $(CACHE_DIR)
+ifeq ($(CLI_V1_VERSION_TAG), $(_EMPTY))
+ $(eval CLI_V1_VERSION_TAG := $(shell curl --fail --progress-bar -L https://static.snyk.io/cli/latest/version))
+endif
+ @echo "CLI_V1_VERSION_TAG=$(CLI_V1_VERSION_TAG)\nCLI_V2_VERSION_TAG=$(CLI_V2_VERSION_TAG)" > $(CACHE_DIR)/version.mk
+ @echo "$(LOG_PREFIX) Using cliv1 version ( $(CLI_V1_VERSION_TAG) )"
+ @echo "$(LOG_PREFIX) Building cliv2 version ( $(CLI_V2_VERSION_TAG) )"
+
+$(BUILD_DIR):
+ @mkdir $@
+
+$(CACHE_DIR):
+ @mkdir $@
+
+$(CACHE_DIR)/variables.mk: $(CACHE_DIR)
+ @echo "GOOS=$(GOOS)\nGOARCH=$(GOARCH)\n" > $(CACHE_DIR)/variables.mk
+
+$(V1_DIRECTORY)/$(V1_EMBEDDED_FILE_OUTPUT):
+ @echo "$(LOG_PREFIX) Generating ( $(V1_DIRECTORY)/$(V1_EMBEDDED_FILE_OUTPUT) )"
+ @sed -e 's/FILENAME/$(V1_EXECUTABLE_NAME)/g' $(V1_EMBEDDED_FILE_TEMPLATE) > $(V1_DIRECTORY)/$(V1_EMBEDDED_FILE_OUTPUT)
+
+$(V1_DIRECTORY)/cliv1.version:
+ @echo "$(CLI_V1_VERSION_TAG)" > $(V1_DIRECTORY)/cliv1.version
+
+$(V2_DIRECTORY)/cliv2.version:
+ @echo "$(CLI_V2_VERSION_TAG)" > $(V2_DIRECTORY)/cliv2.version
+
+$(V1_DIRECTORY)/$(V1_EXECUTABLE_NAME): $(V1_DIRECTORY)/cliv1.version
+ifeq ($(_V1_DOWNLOAD), true)
+ @echo "$(LOG_PREFIX) Downloading cliv1 executable ( $(V1_DIRECTORY)/$(V1_EXECUTABLE_NAME) )"
+ @curl --fail --progress-bar -Lo $(V1_DIRECTORY)/$(V1_EXECUTABLE_NAME) $(V1_DOWNLOAD_LINK)
+else
+ @echo "$(LOG_PREFIX) Copying cliv1 executable ( $(V1_DIRECTORY)/$(V1_EXECUTABLE_NAME) )"
+ @cp $(CLI_V1_LOCATION)/$(V1_EXECUTABLE_NAME) $(V1_DIRECTORY)/$(V1_EXECUTABLE_NAME)
+endif
+
+$(V1_DIRECTORY)/$(V1_EXECUTABLE_NAME).$(HASH_STRING): $(V1_DIRECTORY)/$(V1_EXECUTABLE_NAME)
+ifeq ($(_V1_DOWNLOAD), true)
+ @echo "$(LOG_PREFIX) Downloading cliv1 checksum ( $(V1_DIRECTORY)/$(V1_EXECUTABLE_NAME).$(HASH_STRING) )"
+ @curl --fail --progress-bar -Lo $(V1_DIRECTORY)/$(V1_EXECUTABLE_NAME).$(HASH_STRING) $(V1_DOWNLOAD_LINK).$(HASH_STRING)
+else
+ @echo "$(LOG_PREFIX) Copying cliv1 checksum ( $(V1_DIRECTORY)/$(V1_EXECUTABLE_NAME).$(HASH_STRING) )"
+ @cp $(CLI_V1_LOCATION)/$(V1_EXECUTABLE_NAME).$(HASH_STRING) $(V1_DIRECTORY)/$(V1_EXECUTABLE_NAME).$(HASH_STRING)
+endif
+
+.PHONY: _validate_sha_v1
+_validate_sha_v1: $(V1_DIRECTORY)/$(V1_EXECUTABLE_NAME).$(HASH_STRING)
+ @echo "$(LOG_PREFIX) Validating checksum ( $(V1_DIRECTORY)/$(V1_EXECUTABLE_NAME).$(HASH_STRING) )"
+ @cd $(V1_DIRECTORY) && shasum -b -q -a $(HASH_ALGORITHM) -c $(V1_DIRECTORY)/$(V1_EXECUTABLE_NAME).$(HASH_STRING)
+
+# separate dependency target
+.PHONY: dependencies
+dependencies: $(V1_DIRECTORY)/$(V1_EXECUTABLE_NAME) $(V1_DIRECTORY)/$(V1_EXECUTABLE_NAME).$(HASH_STRING) _validate_sha_v1
+
+# prepare the workspace and cache global parameters
+.PHONY: configure
+configure: $(V2_DIRECTORY)/cliv2.version $(CACHE_DIR) $(CACHE_DIR)/version.mk $(CACHE_DIR)/variables.mk $(V1_DIRECTORY)/$(V1_EMBEDDED_FILE_OUTPUT) dependencies
+
+$(BUILD_DIR)/$(V2_EXECUTABLE_NAME): $(BUILD_DIR) $(SRCS)
+ @echo "$(LOG_PREFIX) Building ( $(BUILD_DIR)/$(V2_EXECUTABLE_NAME) )"
+ @GOOS=$(_GO_OS) GOARCH=$(GOARCH) $(GOCMD) build -o $(BUILD_DIR)/$(V2_EXECUTABLE_NAME) $(WORKING_DIR)/cmd/cliv2/main.go
+
+$(BUILD_DIR)/$(V2_EXECUTABLE_NAME).$(HASH_STRING):
+ @echo "$(LOG_PREFIX) Generating checksum ( $(BUILD_DIR)/$(V2_EXECUTABLE_NAME).$(HASH_STRING) )"
+ @cd $(BUILD_DIR) && shasum -a $(HASH_ALGORITHM) --binary $(V2_EXECUTABLE_NAME) > $(V2_EXECUTABLE_NAME).$(HASH_STRING)
+
+$(BUILD_DIR)/$(TEST_EXECUTABLE_NAME):
+ @echo "$(LOG_PREFIX) Building test executable ( $(BUILD_DIR)/$(TEST_EXECUTABLE_NAME) )"
+ @GOOS=$(_GO_OS) GOARCH=$(GOARCH) $(GOCMD) test -c -o $(BUILD_DIR)/$(TEST_EXECUTABLE_NAME) main_integration_test.go
+
+.PHONY: build
+build: configure $(BUILD_DIR)/$(V2_EXECUTABLE_NAME) $(BUILD_DIR)/$(V2_EXECUTABLE_NAME).$(HASH_STRING)
+
+.PHONY: build-test
+build-test: build-blackboxtest
+
+.PHONY: build-blackboxtest
+build-blackboxtest: build $(BUILD_DIR)/$(TEST_EXECUTABLE_NAME)
+
+.PHONY: blackboxtest
+blackboxtest: build-blackboxtest
+ @echo "$(LOG_PREFIX) Running $@"
+ TEST_SNYK_EXECUTABLE_PATH=$(TEST_SNYK_EXECUTABLE_PATH) $(BUILD_DIR)/$(TEST_EXECUTABLE_NAME) -test.v
+
+$(WORKING_DIR)/internal/httpauth/generated/httpauth_generated_mock.go:
+ @$(GOCMD) generate ./internal/httpauth/
+
+$(WORKING_DIR)/internal/httpauth/generated/spnego_generated_mock.go:
+ @$(GOCMD) generate ./internal/httpauth/
+
+.PHONY: generate
+generate: $(WORKING_DIR)/internal/httpauth/generated/httpauth_generated_mock.go $(WORKING_DIR)/internal/httpauth/generated/spnego_generated_mock.go
+
+.PHONY: whiteboxtest
+whiteboxtest:
+ @echo "$(LOG_PREFIX) Running $@"
+ @$(GOCMD) test -cover ./...
+
+.PHONY: acceptancetest
+acceptancetest: build
+ TEST_SNYK_COMMAND="$(BUILD_DIR)/$(V2_EXECUTABLE_NAME)" npx jest
+
+.PHONY: test
+test: whiteboxtest blackboxtest acceptancetest
+
+.PHONY: lint
+lint:
+ ./scripts/lint.sh
+
+.PHONY: format
+format:
+ gofmt -w -l -e .
+
+.PHONY: $(SIGN_SCRIPT)
+$(SIGN_SCRIPT):
+ @echo "$(LOG_PREFIX) Running $(SIGN_SCRIPT) ( $(BUILD_DIR) )"
+ @scripts/$(SIGN_SCRIPT) $(BUILD_DIR) $(V2_EXECUTABLE_NAME)
+
+.PHONY: _cleanup_sha_v2
+_cleanup_sha_v2:
+ @echo "$(LOG_PREFIX) Removing ( $(BUILD_DIR)/$(V2_EXECUTABLE_NAME).$(HASH_STRING) )"
+ @rm -f $(BUILD_DIR)/$(V2_EXECUTABLE_NAME).$(HASH_STRING)
+
+.PHONY: $(ISSIGNED_SCRIPT)
+$(ISSIGNED_SCRIPT):
+ @scripts/$(ISSIGNED_SCRIPT) $(BUILD_DIR)/$(V2_EXECUTABLE_NAME)
+
+
+.PHONY: sign
+sign: _cleanup_sha_v2 $(SIGN_SCRIPT) $(BUILD_DIR)/$(V2_EXECUTABLE_NAME).$(HASH_STRING) $(ISSIGNED_SCRIPT)
+
+.PHONY: test-signature
+test-signature: $(ISSIGNED_SCRIPT)
+
+.PHONY: clean
+clean:
+ @$(GOCMD) clean
+ @rm -f -r $(BUILD_DIR)
+ @rm -f -r $(CACHE_DIR)
+ @rm -f $(V1_DIRECTORY)/$(APPLICATION_NAME)-*
+ @rm -f $(V1_DIRECTORY)/$(V1_EMBEDDED_FILE_OUTPUT)
+ @rm -f $(V1_DIRECTORY)/cliv1.version
+ @rm -f $(V2_DIRECTORY)/cliv2.version
+
+.PHONY: install
+install:
+ @echo "$(LOG_PREFIX) Installing $(V2_EXECUTABLE_NAME) ( $(DESTDIR)$(bindir) )"
+ @mkdir -p $(DESTDIR)$(bindir)
+ @cp $(BUILD_DIR)/$(V2_EXECUTABLE_NAME) $(DESTDIR)$(bindir)
+ @cp $(BUILD_DIR)/$(V2_EXECUTABLE_NAME).$(HASH_STRING) $(DESTDIR)$(bindir)
+ @cp $(BUILD_DIR)/$(TEST_EXECUTABLE_NAME) $(DESTDIR)$(bindir)
+
+.PHONY: help
+help:
+ @echo "Main targets:"
+ @echo "$(LOG_PREFIX) lint"
+ @echo "$(LOG_PREFIX) format"
+ @echo "$(LOG_PREFIX) build"
+ @echo "$(LOG_PREFIX) sign"
+ @echo "$(LOG_PREFIX) build-test"
+ @echo "$(LOG_PREFIX) test"
+ @echo "$(LOG_PREFIX) test-signature"
+ @echo "$(LOG_PREFIX) install"
+ @echo "$(LOG_PREFIX) clean"
+ @echo "\nAvailable parameter:"
+ @echo "$(LOG_PREFIX) GOOS Specify Operating System to compile for (see golang GOOS, default=$(GOOS))"
+ @echo "$(LOG_PREFIX) GOARCH Specify Architecture to compile for (see golang GOARCH, default=$(GOARCH))"
+ @echo "$(LOG_PREFIX) CLI_V2_VERSION_TAG Version of the CLIv2 without the CLIv1 version (default=$(CLI_V2_VERSION_TAG))"
+ @echo "$(LOG_PREFIX) CLI_V1_VERSION_TAG Version of the CLIv1 to bundle"
+ @echo "$(LOG_PREFIX) CLI_V1_LOCATION Filesystem location of CLIv1 binaries to bundle, if specified, CLI_V1_VERSION_TAG is also required"
+ @echo "$(LOG_PREFIX) TEST_SNYK_EXECUTABLE_PATH Filesystem location of binary under test (default=$(TEST_SNYK_EXECUTABLE_PATH))"
+ @echo "$(LOG_PREFIX) prefix Installation prefix (default=$(prefix))"
+ @echo "$(LOG_PREFIX) DESTDIR For staged installations"
diff --git a/cliv2/cmd/cliv2/main.go b/cliv2/cmd/cliv2/main.go
new file mode 100644
index 0000000000..9e81264bd7
--- /dev/null
+++ b/cliv2/cmd/cliv2/main.go
@@ -0,0 +1,114 @@
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+
+ "github.com/snyk/cli/cliv2/internal/cliv2"
+ "github.com/snyk/cli/cliv2/internal/httpauth"
+ "github.com/snyk/cli/cliv2/internal/proxy"
+ "github.com/snyk/cli/cliv2/internal/utils"
+)
+
+type EnvironmentVariables struct {
+ CacheDirectory string
+ Insecure bool
+ ProxyAuthenticationMechanism httpauth.AuthenticationMechanism
+}
+
+func getDebugLogger(args []string) *log.Logger {
+ debugLogger := log.New(os.Stderr, "", log.Ldate|log.Ltime|log.Lmicroseconds|log.Lshortfile)
+ debug := utils.Contains(args, "--debug")
+
+ if !debug {
+ debug = utils.Contains(args, "-d")
+ }
+
+ if !debug {
+ debugLogger.SetOutput(ioutil.Discard)
+ }
+
+ return debugLogger
+}
+
+func GetConfiguration(args []string) (EnvironmentVariables, []string) {
+ envVariables := EnvironmentVariables{
+ CacheDirectory: os.Getenv("SNYK_CACHE_PATH"),
+ ProxyAuthenticationMechanism: httpauth.AnyAuth,
+ Insecure: false,
+ }
+
+ if utils.Contains(args, "--proxy-noauth") {
+ envVariables.ProxyAuthenticationMechanism = httpauth.NoAuth
+ }
+
+ envVariables.Insecure = utils.Contains(args, "--insecure")
+
+ // filter args not meant to be forwarded to CLIv1 or an Extensions
+ elementsToFilter := []string{"--proxy-noauth"}
+ filteredArgs := args
+ for _, element := range elementsToFilter {
+ filteredArgs = utils.RemoveSimilar(filteredArgs, element)
+ }
+
+ return envVariables, filteredArgs
+}
+
+func main() {
+ config, args := GetConfiguration(os.Args[1:])
+ errorCode := MainWithErrorCode(config, args)
+ os.Exit(errorCode)
+}
+
+func MainWithErrorCode(envVariables EnvironmentVariables, args []string) int {
+ var err error
+ debugLogger := getDebugLogger(args)
+ debugLogger.Println("debug: true")
+
+ debugLogger.Println("cacheDirectory:", envVariables.CacheDirectory)
+ debugLogger.Println("insecure:", envVariables.Insecure)
+
+ if envVariables.CacheDirectory == "" {
+ envVariables.CacheDirectory, err = utils.SnykCacheDir()
+ if err != nil {
+ fmt.Println("Failed to determine cache directory!")
+ fmt.Println(err)
+ return cliv2.SNYK_EXIT_CODE_ERROR
+ }
+ }
+
+ // init cli object
+ var cli *cliv2.CLI
+ cli = cliv2.NewCLIv2(envVariables.CacheDirectory, debugLogger)
+ if cli == nil {
+ return cliv2.SNYK_EXIT_CODE_ERROR
+ }
+
+ // init proxy object
+ wrapperProxy, err := proxy.NewWrapperProxy(envVariables.Insecure, envVariables.CacheDirectory, cli.GetFullVersion(), debugLogger)
+ if err != nil {
+ fmt.Println("Failed to create proxy")
+ fmt.Println(err)
+ return cliv2.SNYK_EXIT_CODE_ERROR
+ }
+
+ wrapperProxy.SetUpstreamProxyAuthentication(envVariables.ProxyAuthenticationMechanism)
+
+ port, err := wrapperProxy.Start()
+ if err != nil {
+ fmt.Println("Failed to start the proxy")
+ fmt.Println(err)
+ return cliv2.SNYK_EXIT_CODE_ERROR
+ }
+
+ // run the cli
+ exitCode := cli.Execute(port, wrapperProxy.CertificateLocation, args)
+
+ debugLogger.Println("in main, cliv1 is done")
+ wrapperProxy.Close()
+ debugLogger.Printf("Exiting with %d\n", exitCode)
+
+ return exitCode
+}
diff --git a/cliv2/cmd/cliv2/main_test.go b/cliv2/cmd/cliv2/main_test.go
new file mode 100644
index 0000000000..d19a693def
--- /dev/null
+++ b/cliv2/cmd/cliv2/main_test.go
@@ -0,0 +1,68 @@
+package main_test
+
+import (
+ "os"
+ "strings"
+ "testing"
+
+ main "github.com/snyk/cli/cliv2/cmd/cliv2"
+ "github.com/snyk/cli/cliv2/internal/httpauth"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func Test_MainWithErrorCode(t *testing.T) {
+ cacheDirectory := ""
+
+ variables := main.EnvironmentVariables{
+ CacheDirectory: cacheDirectory,
+ }
+
+ err := main.MainWithErrorCode(variables, os.Args[1:])
+ assert.Equal(t, err, 0)
+}
+
+func Test_MainWithErrorCode_no_cache(t *testing.T) {
+ cacheDirectory := "MADE_UP_NAME"
+
+ variables := main.EnvironmentVariables{
+ CacheDirectory: cacheDirectory,
+ }
+
+ mainErr := main.MainWithErrorCode(variables, os.Args[1:])
+
+ assert.Equal(t, mainErr, 0)
+ assert.DirExists(t, cacheDirectory)
+}
+
+func Test_GetConfiguration(t *testing.T) {
+ cmd := "_bin/snyk_darwin_arm64 --debug --insecure test"
+ args := strings.Split(cmd, " ")
+
+ expectedConfig := main.EnvironmentVariables{
+ Insecure: true,
+ ProxyAuthenticationMechanism: httpauth.AnyAuth,
+ }
+ expectedArgs := []string{"_bin/snyk_darwin_arm64", "--debug", "--insecure", "test"}
+
+ actualConfig, actualArgs := main.GetConfiguration(args)
+
+ assert.Equal(t, expectedArgs, actualArgs)
+ assert.Equal(t, expectedConfig, actualConfig)
+}
+
+func Test_GetConfiguration02(t *testing.T) {
+ cmd := "_bin/snyk_darwin_arm64 --debug --proxy-noauth --insecure test"
+ args := strings.Split(cmd, " ")
+
+ expectedConfig := main.EnvironmentVariables{
+ Insecure: true,
+ ProxyAuthenticationMechanism: httpauth.NoAuth,
+ }
+ expectedArgs := []string{"_bin/snyk_darwin_arm64", "--debug", "--insecure", "test"}
+
+ actualConfig, actualArgs := main.GetConfiguration(args)
+
+ assert.Equal(t, expectedArgs, actualArgs)
+ assert.Equal(t, expectedConfig, actualConfig)
+}
diff --git a/cliv2/cmd/make-cert/main.go b/cliv2/cmd/make-cert/main.go
new file mode 100644
index 0000000000..8c72f8baf5
--- /dev/null
+++ b/cliv2/cmd/make-cert/main.go
@@ -0,0 +1,51 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "os"
+ "path"
+ "strings"
+
+ "github.com/snyk/cli/cliv2/internal/certs"
+ "github.com/snyk/cli/cliv2/internal/utils"
+)
+
+func main() {
+ certName := os.Args[1]
+
+ debugLogger := log.Default()
+
+ snykDNSNamesStr := os.Getenv("SNYK_DNS_NAMES")
+ var snykDNSNames []string
+ fmt.Println("SNYK_DNS_NAMES:", snykDNSNamesStr)
+ if snykDNSNamesStr != "" {
+ snykDNSNames = strings.Split(snykDNSNamesStr, ",")
+ } else {
+ // We use app.dev.snyk.io for development
+ snykDNSNames = []string{"snyk.io", "*.snyk.io", "*.dev.snyk.io"}
+ }
+
+ debugLogger.Println("certificate name:", certName)
+ debugLogger.Println("SNYK_DNS_NAMES:", snykDNSNames)
+
+ certPEMBlockBytes, keyPEMBlockBytes, err := certs.MakeSelfSignedCert(certName, snykDNSNames, debugLogger)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // certString := certPEMBytesBuffer.String()
+ certPEMString := string(certPEMBlockBytes)
+ keyPEMString := string(keyPEMBlockBytes)
+
+ keyAndCert := keyPEMString + certPEMString
+
+ // write to file
+ certFilePath := path.Join(".", certName+".crt")
+ keyFilePath := path.Join(".", certName+".key")
+ joinedPemFilePath := path.Join(".", certName+".pem") // key and cert in one file - used by mitmproxy
+
+ _ = utils.WriteToFile(certFilePath, certPEMString)
+ _ = utils.WriteToFile(keyFilePath, keyPEMString)
+ _ = utils.WriteToFile(joinedPemFilePath, keyAndCert)
+}
diff --git a/cliv2/go.mod b/cliv2/go.mod
new file mode 100644
index 0000000000..2f60c0cd1e
--- /dev/null
+++ b/cliv2/go.mod
@@ -0,0 +1,26 @@
+module github.com/snyk/cli/cliv2
+
+go 1.18
+
+require (
+ github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74
+ github.com/elazarl/goproxy v0.0.0-20220328115640-894aeddb713e
+ github.com/golang/mock v1.6.0
+ github.com/jcmturner/gokrb5/v8 v8.4.2
+ github.com/stretchr/testify v1.7.0
+ golang.org/x/net v0.0.0-20220630215102-69896b714898
+)
+
+require (
+ github.com/davecgh/go-spew v1.1.0 // indirect
+ github.com/hashicorp/go-uuid v1.0.2 // indirect
+ github.com/jcmturner/aescts/v2 v2.0.0 // indirect
+ github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
+ github.com/jcmturner/gofork v1.0.0 // indirect
+ github.com/jcmturner/goidentity/v6 v6.0.1 // indirect
+ github.com/jcmturner/rpc/v2 v2.0.3 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 // indirect
+ golang.org/x/text v0.3.7 // indirect
+ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
+)
diff --git a/cliv2/go.sum b/cliv2/go.sum
new file mode 100644
index 0000000000..f5d4e59039
--- /dev/null
+++ b/cliv2/go.sum
@@ -0,0 +1,71 @@
+github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA=
+github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/elazarl/goproxy v0.0.0-20220328115640-894aeddb713e h1:99KFda6F/mw8xSfceY2JEVCrYWX7l+Ms6BcO5wEct+Q=
+github.com/elazarl/goproxy v0.0.0-20220328115640-894aeddb713e/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
+github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM=
+github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
+github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
+github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
+github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
+github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
+github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
+github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
+github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
+github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
+github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
+github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
+github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
+github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8=
+github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
+github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
+github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
+github.com/jcmturner/gokrb5/v8 v8.4.2 h1:6ZIM6b/JJN0X8UM43ZOM6Z4SJzla+a/u7scXFJzodkA=
+github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
+github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
+github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 h1:umElSU9WZirRdgu2yFHY0ayQkEnKiOC1TtM3fWXFnoU=
+golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw=
+golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/cliv2/internal/certs/certs.go b/cliv2/internal/certs/certs.go
new file mode 100644
index 0000000000..a57ff1e000
--- /dev/null
+++ b/cliv2/internal/certs/certs.go
@@ -0,0 +1,69 @@
+package certs
+
+import (
+ "bytes"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/pem"
+ "fmt"
+ "log"
+ "math/big"
+ "time"
+)
+
+func MakeSelfSignedCert(certName string, dnsNames []string, debugLogger *log.Logger) (certPEMBlock []byte, keyPEMBlock []byte, err error) {
+ // create a key
+ privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // create a self-signed cert using the key
+ template := x509.Certificate{
+ SerialNumber: big.NewInt(1),
+ Subject: pkix.Name{
+ CommonName: certName,
+ },
+ NotBefore: time.Now(),
+ NotAfter: time.Now().Add(time.Hour * 24 * 365),
+
+ KeyUsage: x509.KeyUsageDigitalSignature |
+ x509.KeyUsageKeyEncipherment |
+ x509.KeyUsageKeyAgreement |
+ x509.KeyUsageCertSign, // needed for sure
+
+ ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
+ BasicConstraintsValid: true,
+ IsCA: true,
+ }
+
+ for _, dnsName := range dnsNames {
+ template.DNSNames = append(template.DNSNames, dnsName)
+ debugLogger.Println("MakeSelfSignedCert added ", dnsName)
+ }
+
+ certDERBytes, err_CreateCertificate := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
+ if err_CreateCertificate != nil {
+ return nil, nil, err
+ }
+
+ certPEMBytesBuffer := &bytes.Buffer{}
+ if err := pem.Encode(certPEMBytesBuffer, &pem.Block{Type: "CERTIFICATE", Bytes: certDERBytes}); err != nil {
+ fmt.Println(err)
+ return nil, nil, err
+ }
+
+ // make the key pem
+ keyDERBytes := x509.MarshalPKCS1PrivateKey(privateKey)
+ keyPEMBytesBuffer := &bytes.Buffer{}
+ if err := pem.Encode(keyPEMBytesBuffer, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyDERBytes}); err != nil {
+ return nil, nil, err
+ }
+
+ certPEMBlockBytes := certPEMBytesBuffer.Bytes()
+ keyPEMBlockBytes := keyPEMBytesBuffer.Bytes()
+
+ return certPEMBlockBytes, keyPEMBlockBytes, nil
+}
diff --git a/cliv2/internal/cliv2/cliv2.go b/cliv2/internal/cliv2/cliv2.go
new file mode 100644
index 0000000000..d0d153385d
--- /dev/null
+++ b/cliv2/internal/cliv2/cliv2.go
@@ -0,0 +1,260 @@
+/*
+Entry point class for the CLIv2 version.
+*/
+package cliv2
+
+import (
+ _ "embed"
+ "fmt"
+ "log"
+ "os"
+ "os/exec"
+ "strings"
+
+ "github.com/snyk/cli/cliv2/internal/embedded"
+ "github.com/snyk/cli/cliv2/internal/embedded/cliv1"
+ "github.com/snyk/cli/cliv2/internal/utils"
+)
+
+type Handler int
+
+type CLI struct {
+ DebugLogger *log.Logger
+ CacheDirectory string
+ v1BinaryLocation string
+ v1Version string
+ v2Version string
+}
+
+type EnvironmentWarning struct {
+ message string
+}
+
+const SNYK_EXIT_CODE_OK = 0
+const SNYK_EXIT_CODE_ERROR = 2
+const SNYK_INTEGRATION_NAME = "CLI_V1_PLUGIN"
+const SNYK_INTEGRATION_NAME_ENV = "SNYK_INTEGRATION_NAME"
+const SNYK_INTEGRATION_VERSION_ENV = "SNYK_INTEGRATION_VERSION"
+const SNYK_HTTPS_PROXY_ENV = "HTTPS_PROXY"
+const SNYK_HTTP_PROXY_ENV = "HTTP_PROXY"
+const SNYK_HTTP_NO_PROXY_ENV = "NO_PROXY"
+const SNYK_NPM_PROXY_ENV = "NPM_CONFIG_PROXY"
+const SNYK_NPM_HTTPS_PROXY_ENV = "NPM_CONFIG_HTTPS_PROXY"
+const SNYK_NPM_HTTP_PROXY_ENV = "NPM_CONFIG_HTTP_PROXY"
+const SNYK_NPM_NO_PROXY_ENV = "NPM_CONFIG_NO_PROXY"
+const SNYK_NPM_ALL_PROXY = "ALL_PROXY"
+const SNYK_CA_CERTIFICATE_LOCATION_ENV = "NODE_EXTRA_CA_CERTS"
+
+const (
+ V1_DEFAULT Handler = iota
+ V2_VERSION Handler = iota
+)
+
+//go:embed cliv2.version
+var SNYK_CLIV2_VERSION_PART string
+
+func NewCLIv2(cacheDirectory string, debugLogger *log.Logger) *CLI {
+
+ v1BinaryLocation, err := cliv1.GetFullCLIV1TargetPath(cacheDirectory)
+ if err != nil {
+ fmt.Println(err)
+ return nil
+ }
+
+ cli := CLI{
+ DebugLogger: debugLogger,
+ CacheDirectory: cacheDirectory,
+ v1Version: cliv1.CLIV1Version(),
+ v2Version: strings.TrimSpace(SNYK_CLIV2_VERSION_PART),
+ v1BinaryLocation: v1BinaryLocation,
+ }
+
+ err = cli.ExtractV1Binary()
+ if err != nil {
+ fmt.Println(err)
+ return nil
+ }
+
+ return &cli
+}
+
+func (c *CLI) ExtractV1Binary() error {
+ cliV1ExpectedSHA256 := cliv1.ExpectedSHA256()
+
+ isValid, err := embedded.ValidateFile(c.v1BinaryLocation, cliV1ExpectedSHA256, c.DebugLogger)
+ if err != nil || !isValid {
+ c.DebugLogger.Println("cliv1 is not valid, start extracting ", c.v1BinaryLocation)
+
+ err = cliv1.ExtractTo(c.v1BinaryLocation)
+ if err != nil {
+ return err
+ }
+
+ isValid, err := embedded.ValidateFile(c.v1BinaryLocation, cliV1ExpectedSHA256, c.DebugLogger)
+ if err != nil {
+ return err
+ }
+
+ if isValid {
+ c.DebugLogger.Println("cliv1 is valid after extracting", c.v1BinaryLocation)
+ } else {
+ fmt.Println("cliv1 is not valid after sha256 check")
+ return err
+ }
+ } else {
+ c.DebugLogger.Println("cliv1 already exists and is valid at", c.v1BinaryLocation)
+ }
+
+ return nil
+}
+
+func (c *CLI) GetFullVersion() string {
+ return c.v2Version + "." + c.v1Version
+}
+
+func (c *CLI) GetIntegrationName() string {
+ return SNYK_INTEGRATION_NAME
+}
+
+func (c *CLI) GetBinaryLocation() string {
+ return c.v1BinaryLocation
+}
+
+func (c *CLI) printVersion() {
+ fmt.Println(c.GetFullVersion())
+}
+
+func (c *CLI) commandVersion(passthroughArgs []string) int {
+ if utils.Contains(passthroughArgs, "--json-file-output") {
+ fmt.Println("The following option combination is not currently supported: version + json-file-output")
+ return SNYK_EXIT_CODE_ERROR
+ } else {
+ c.printVersion()
+ return SNYK_EXIT_CODE_OK
+ }
+}
+
+func determineHandler(passthroughArgs []string) Handler {
+ result := V1_DEFAULT
+
+ if utils.Contains(passthroughArgs, "--version") ||
+ utils.Contains(passthroughArgs, "-v") ||
+ utils.Contains(passthroughArgs, "version") {
+ result = V2_VERSION
+ }
+
+ return result
+}
+
+func PrepareV1EnvironmentVariables(input []string, integrationName string, integrationVersion string, proxyAddress string, caCertificateLocation string) (result []string, err error) {
+
+ inputAsMap := utils.ToKeyValueMap(input, "=")
+ result = input
+
+ _, integrationNameExists := inputAsMap[SNYK_INTEGRATION_NAME_ENV]
+ _, integrationVersionExists := inputAsMap[SNYK_INTEGRATION_VERSION_ENV]
+
+ if !integrationNameExists && !integrationVersionExists {
+ inputAsMap[SNYK_INTEGRATION_NAME_ENV] = integrationName
+ inputAsMap[SNYK_INTEGRATION_VERSION_ENV] = integrationVersion
+ } else if !(integrationNameExists && integrationVersionExists) {
+ err = EnvironmentWarning{message: fmt.Sprintf("Partially defined environment, please ensure to provide both %s and %s together!", SNYK_INTEGRATION_NAME_ENV, SNYK_INTEGRATION_VERSION_ENV)}
+ }
+
+ if err == nil {
+
+ // apply blacklist: ensure that no existing no_proxy or other configuration causes redirecting internal communication that is meant to stay between cliv1 and cliv2
+ blackList := []string{
+ SNYK_HTTPS_PROXY_ENV,
+ SNYK_HTTP_PROXY_ENV,
+ SNYK_CA_CERTIFICATE_LOCATION_ENV,
+ SNYK_HTTP_NO_PROXY_ENV,
+ SNYK_NPM_NO_PROXY_ENV,
+ SNYK_NPM_HTTPS_PROXY_ENV,
+ SNYK_NPM_HTTP_PROXY_ENV,
+ SNYK_NPM_PROXY_ENV,
+ SNYK_NPM_ALL_PROXY,
+ }
+
+ for _, key := range blackList {
+ inputAsMap = utils.Remove(inputAsMap, key)
+ }
+
+ // fill expected values
+ inputAsMap[SNYK_HTTPS_PROXY_ENV] = proxyAddress
+ inputAsMap[SNYK_HTTP_PROXY_ENV] = proxyAddress
+ inputAsMap[SNYK_CA_CERTIFICATE_LOCATION_ENV] = caCertificateLocation
+
+ result = utils.ToSlice(inputAsMap, "=")
+ }
+
+ return result, err
+
+}
+
+func PrepareV1Command(cmd string, args []string, proxyPort int, caCertLocation string, integrationName string, integrationVersion string) (snykCmd *exec.Cmd, err error) {
+
+ proxyAddress := fmt.Sprintf("http://127.0.0.1:%d", proxyPort)
+
+ snykCmd = exec.Command(cmd, args...)
+ snykCmd.Env, err = PrepareV1EnvironmentVariables(os.Environ(), integrationName, integrationVersion, proxyAddress, caCertLocation)
+ snykCmd.Stdin = os.Stdin
+ snykCmd.Stdout = os.Stdout
+ snykCmd.Stderr = os.Stderr
+
+ return snykCmd, err
+}
+
+func (c *CLI) executeV1Default(wrapperProxyPort int, fullPathToCert string, passthroughArgs []string) int {
+ c.DebugLogger.Println("launching snyk with path: ", c.v1BinaryLocation)
+ c.DebugLogger.Println("fullPathToCert:", fullPathToCert)
+
+ snykCmd, err := PrepareV1Command(
+ c.v1BinaryLocation,
+ passthroughArgs,
+ wrapperProxyPort,
+ fullPathToCert,
+ c.GetIntegrationName(),
+ c.GetFullVersion(),
+ )
+
+ if err != nil {
+ if evWarning, ok := err.(EnvironmentWarning); ok {
+ fmt.Println("WARNING! ", evWarning)
+ }
+ }
+
+ err = snykCmd.Run()
+ if err != nil {
+ if exitError, ok := err.(*exec.ExitError); ok {
+ exitCode := exitError.ExitCode()
+ return exitCode
+ } else {
+ // got an error but it's not an ExitError
+ fmt.Println(err)
+ return SNYK_EXIT_CODE_ERROR
+ }
+ }
+
+ return SNYK_EXIT_CODE_OK
+}
+
+func (c *CLI) Execute(wrapperProxyPort int, fullPathToCert string, passthroughArgs []string) int {
+ c.DebugLogger.Println("passthroughArgs", passthroughArgs)
+
+ returnCode := SNYK_EXIT_CODE_OK
+ handler := determineHandler(passthroughArgs)
+
+ switch {
+ case handler == V2_VERSION:
+ returnCode = c.commandVersion(passthroughArgs)
+ default:
+ returnCode = c.executeV1Default(wrapperProxyPort, fullPathToCert, passthroughArgs)
+ }
+
+ return returnCode
+}
+
+func (e EnvironmentWarning) Error() string {
+ return e.message
+}
diff --git a/cliv2/internal/cliv2/cliv2_test.go b/cliv2/internal/cliv2/cliv2_test.go
new file mode 100644
index 0000000000..99c840484d
--- /dev/null
+++ b/cliv2/internal/cliv2/cliv2_test.go
@@ -0,0 +1,178 @@
+package cliv2_test
+
+import (
+ "io/ioutil"
+ "log"
+ "os"
+ "sort"
+ "testing"
+
+ "github.com/snyk/cli/cliv2/internal/cliv2"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func Test_PrepareV1EnvironmentVariables_Fill_and_Filter(t *testing.T) {
+
+ input := []string{
+ "something=1",
+ "in=2",
+ "here=3=2",
+ "no_proxy=something",
+ "NPM_CONFIG_PROXY=something",
+ "NPM_CONFIG_HTTPS_PROXY=something",
+ "NPM_CONFIG_HTTP_PROXY=something",
+ "npm_config_no_proxy=something",
+ "ALL_PROXY=something",
+ }
+ expected := []string{"something=1", "in=2", "here=3=2", "SNYK_INTEGRATION_NAME=foo", "SNYK_INTEGRATION_VERSION=bar", "HTTP_PROXY=proxy", "HTTPS_PROXY=proxy", "NODE_EXTRA_CA_CERTS=cacertlocation"}
+
+ actual, err := cliv2.PrepareV1EnvironmentVariables(input, "foo", "bar", "proxy", "cacertlocation")
+
+ sort.Strings(expected)
+ sort.Strings(actual)
+ assert.Equal(t, expected, actual)
+ assert.Nil(t, err)
+}
+
+func Test_PrepareV1EnvironmentVariables_DontOverrideExistingIntegration(t *testing.T) {
+
+ input := []string{"something=1", "in=2", "here=3", "SNYK_INTEGRATION_NAME=exists", "SNYK_INTEGRATION_VERSION=already"}
+ expected := []string{"something=1", "in=2", "here=3", "SNYK_INTEGRATION_NAME=exists", "SNYK_INTEGRATION_VERSION=already", "HTTP_PROXY=proxy", "HTTPS_PROXY=proxy", "NODE_EXTRA_CA_CERTS=cacertlocation"}
+
+ actual, err := cliv2.PrepareV1EnvironmentVariables(input, "foo", "bar", "proxy", "cacertlocation")
+
+ sort.Strings(expected)
+ sort.Strings(actual)
+ assert.Equal(t, expected, actual)
+ assert.Nil(t, err)
+}
+
+func Test_PrepareV1EnvironmentVariables_OverrideProxyAndCerts(t *testing.T) {
+
+ input := []string{"something=1", "in=2", "here=3", "http_proxy=exists", "https_proxy=already", "NODE_EXTRA_CA_CERTS=again", "no_proxy=312123"}
+ expected := []string{"something=1", "in=2", "here=3", "SNYK_INTEGRATION_NAME=foo", "SNYK_INTEGRATION_VERSION=bar", "HTTP_PROXY=proxy", "HTTPS_PROXY=proxy", "NODE_EXTRA_CA_CERTS=cacertlocation"}
+
+ actual, err := cliv2.PrepareV1EnvironmentVariables(input, "foo", "bar", "proxy", "cacertlocation")
+
+ sort.Strings(expected)
+ sort.Strings(actual)
+ assert.Equal(t, expected, actual)
+ assert.Nil(t, err)
+}
+
+func Test_PrepareV1EnvironmentVariables_Fail_DontOverrideExisting(t *testing.T) {
+
+ input := []string{"something=1", "in=2", "here=3", "SNYK_INTEGRATION_NAME=exists"}
+ expected := input
+
+ actual, err := cliv2.PrepareV1EnvironmentVariables(input, "foo", "bar", "unused", "unused")
+
+ sort.Strings(expected)
+ sort.Strings(actual)
+ assert.Equal(t, expected, actual)
+
+ warn, ok := err.(cliv2.EnvironmentWarning)
+ assert.True(t, ok)
+ assert.NotNil(t, warn)
+}
+
+func Test_prepareV1Command(t *testing.T) {
+ expectedArgs := []string{"hello", "world"}
+
+ snykCmd, err := cliv2.PrepareV1Command(
+ "someExecutable",
+ expectedArgs,
+ 1,
+ "certLocation",
+ "name",
+ "version",
+ )
+
+ assert.Contains(t, snykCmd.Env, "SNYK_INTEGRATION_NAME=name")
+ assert.Contains(t, snykCmd.Env, "SNYK_INTEGRATION_VERSION=version")
+ assert.Contains(t, snykCmd.Env, "HTTPS_PROXY=http://127.0.0.1:1")
+ assert.Contains(t, snykCmd.Env, "NODE_EXTRA_CA_CERTS=certLocation")
+ assert.Equal(t, expectedArgs, snykCmd.Args[1:])
+ assert.Nil(t, err)
+}
+
+func Test_executeRunV1(t *testing.T) {
+ expectedReturnCode := 0
+
+ cacheDir := "dasda"
+ logger := log.New(ioutil.Discard, "", 0)
+
+ assert.NoDirExists(t, cacheDir)
+
+ // create instance under test
+ cli := cliv2.NewCLIv2(cacheDir, logger)
+
+ // run once
+ actualReturnCode := cli.Execute(1000, "", []string{"--help"})
+ assert.Equal(t, expectedReturnCode, actualReturnCode)
+ assert.FileExists(t, cli.GetBinaryLocation())
+ fileInfo1, _ := os.Stat(cli.GetBinaryLocation())
+
+ // run twice
+ actualReturnCode = cli.Execute(1000, "", []string{"--help"})
+ assert.Equal(t, expectedReturnCode, actualReturnCode)
+ assert.FileExists(t, cli.GetBinaryLocation())
+ fileInfo2, _ := os.Stat(cli.GetBinaryLocation())
+
+ assert.Equal(t, fileInfo1.ModTime(), fileInfo2.ModTime())
+
+ // cleanup
+ os.RemoveAll(cacheDir)
+}
+
+func Test_executeRunV2only(t *testing.T) {
+ expectedReturnCode := 0
+
+ cacheDir := "dasda"
+ logger := log.New(ioutil.Discard, "", 0)
+
+ assert.NoDirExists(t, cacheDir)
+
+ // create instance under test
+ cli := cliv2.NewCLIv2(cacheDir, logger)
+ actualReturnCode := cli.Execute(1000, "", []string{"--version"})
+ assert.Equal(t, expectedReturnCode, actualReturnCode)
+ assert.FileExists(t, cli.GetBinaryLocation())
+
+ os.RemoveAll(cacheDir)
+}
+
+func Test_executeEnvironmentError(t *testing.T) {
+ expectedReturnCode := 0
+
+ cacheDir := "dasda"
+ logger := log.New(ioutil.Discard, "", 0)
+
+ assert.NoDirExists(t, cacheDir)
+
+ // fill Environment Variable
+ os.Setenv(cliv2.SNYK_INTEGRATION_NAME_ENV, "someName")
+
+ // create instance under test
+ cli := cliv2.NewCLIv2(cacheDir, logger)
+ actualReturnCode := cli.Execute(1000, "", []string{"--help"})
+ assert.Equal(t, expectedReturnCode, actualReturnCode)
+ assert.FileExists(t, cli.GetBinaryLocation())
+
+ os.RemoveAll(cacheDir)
+}
+
+func Test_executeUnknownCommand(t *testing.T) {
+ expectedReturnCode := cliv2.SNYK_EXIT_CODE_ERROR
+
+ cacheDir := "dasda"
+ logger := log.New(ioutil.Discard, "", 0)
+
+ // create instance under test
+ cli := cliv2.NewCLIv2(cacheDir, logger)
+ actualReturnCode := cli.Execute(1000, "", []string{"bogusCommand"})
+ assert.Equal(t, expectedReturnCode, actualReturnCode)
+
+ os.RemoveAll(cacheDir)
+}
diff --git a/cliv2/internal/embedded/cliv1/cliv1.go b/cliv2/internal/embedded/cliv1/cliv1.go
new file mode 100644
index 0000000000..d1c4844476
--- /dev/null
+++ b/cliv2/internal/embedded/cliv1/cliv1.go
@@ -0,0 +1,34 @@
+package cliv1
+
+import (
+ _ "embed"
+ "path"
+ "strings"
+
+ "github.com/snyk/cli/cliv2/internal/embedded"
+ "github.com/snyk/cli/cliv2/internal/utils"
+)
+
+//go:embed cliv1.version
+var snykCLIVersion string
+
+func CLIV1Version() string {
+ return strings.TrimSpace(snykCLIVersion)
+}
+
+// Get the full path to where we expect the CLIv1 to be in the cache
+// If it doesn't exist, this is the path where we will then extract it
+func GetFullCLIV1TargetPath(cacheDir string) (string, error) {
+ cliv1Filename := getCLIv1Filename()
+ versionTag := CLIV1Version()
+ relPath := path.Join(versionTag, cliv1Filename)
+ fullPath, err := utils.FullPathInSnykCacheDir(cacheDir, relPath)
+ if err != nil {
+ return "", err
+ }
+ return fullPath, nil
+}
+
+func ExtractTo(targetFullPath string) error {
+ return embedded.ExtractBytesToTarget(snykCLIBytes, targetFullPath)
+}
diff --git a/cliv2/internal/embedded/cliv1/embedded_binary_template.txt b/cliv2/internal/embedded/cliv1/embedded_binary_template.txt
new file mode 100644
index 0000000000..6d1408892b
--- /dev/null
+++ b/cliv2/internal/embedded/cliv1/embedded_binary_template.txt
@@ -0,0 +1,20 @@
+package cliv1
+
+import (
+ _ "embed"
+)
+
+//go:embed FILENAME
+var snykCLIBytes []byte
+
+func getCLIv1Filename() string {
+ return "FILENAME"
+}
+
+//go:embed FILENAME.sha256
+var snykCLISHA256 string
+
+func ExpectedSHA256() string {
+ sha256 := snykCLISHA256[0:64]
+ return sha256
+}
diff --git a/cliv2/internal/embedded/extract.go b/cliv2/internal/embedded/extract.go
new file mode 100644
index 0000000000..2bc2f50a8f
--- /dev/null
+++ b/cliv2/internal/embedded/extract.go
@@ -0,0 +1,22 @@
+package embedded
+
+import (
+ "os"
+ "path/filepath"
+)
+
+func ExtractBytesToTarget(bytes []byte, targetFullPath string) error {
+ // make sure the directory exists
+ dir := filepath.Dir(targetFullPath)
+ err := os.MkdirAll(dir, 0755)
+ if err != nil {
+ return err
+ }
+
+ err = os.WriteFile(targetFullPath, bytes, 0755)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/cliv2/internal/embedded/sha.go b/cliv2/internal/embedded/sha.go
new file mode 100644
index 0000000000..c773a53e0d
--- /dev/null
+++ b/cliv2/internal/embedded/sha.go
@@ -0,0 +1,35 @@
+package embedded
+
+import (
+ "crypto/sha256"
+ "fmt"
+ "io/ioutil"
+ "log"
+)
+
+func ComputeSHA256(filePath string, debugLogger *log.Logger) (string, error) {
+ fileBytes, err := ioutil.ReadFile(filePath)
+ if err != nil {
+ debugLogger.Println("failed to read file:", filePath)
+ return "", err
+ }
+
+ hash := sha256.Sum256(fileBytes)
+ hexString := fmt.Sprintf("%x", hash)
+
+ return hexString, nil
+}
+
+func ValidateFile(filePath string, expectedSHA256 string, debugLogger *log.Logger) (bool, error) {
+ debugLogger.Println("validating", filePath)
+
+ hashStr, err := ComputeSHA256(filePath, debugLogger)
+ if err != nil {
+ return false, err
+ }
+
+ debugLogger.Println("found sha256:", hashStr)
+ debugLogger.Println("expected sha256:", expectedSHA256)
+
+ return hashStr == expectedSHA256, nil
+}
diff --git a/cliv2/internal/httpauth/httpauth.go b/cliv2/internal/httpauth/httpauth.go
new file mode 100644
index 0000000000..42c67ffd12
--- /dev/null
+++ b/cliv2/internal/httpauth/httpauth.go
@@ -0,0 +1,190 @@
+package httpauth
+
+import (
+ "fmt"
+ "io"
+ "log"
+ "net/url"
+ "strings"
+)
+
+type AuthenticationMechanism string
+type AuthenticationState int
+
+const maxCycleCount int = 10
+
+const (
+ NoAuth AuthenticationMechanism = "noauth"
+ Negotiate AuthenticationMechanism = "negotiate"
+ AnyAuth AuthenticationMechanism = "anyauth"
+ UnknownMechanism AuthenticationMechanism = "unknownmechanism"
+)
+
+const (
+ Initial AuthenticationState = iota
+ Negotiating AuthenticationState = iota
+ Done AuthenticationState = iota
+ Error AuthenticationState = iota
+ Cancel AuthenticationState = iota
+ Close AuthenticationState = iota
+)
+
+const (
+ AuthorizationKey string = "Authorization"
+ ProxyAuthorizationKey string = "Proxy-Authorization"
+ ProxyAuthenticateKey string = "Proxy-Authenticate"
+)
+
+type AuthenticationHandlerInterface interface {
+ Close()
+ Cancel()
+ Succesful()
+ IsStopped() bool
+ GetAuthorizationValue(url *url.URL, responseToken string) (string, error)
+ Update(availableMechanism map[AuthenticationMechanism]string) (string, error)
+ SetLogger(logger *log.Logger)
+ SetSpnegoProvider(spnegoProvider SpnegoProvider)
+}
+
+type AuthenticationHandler struct {
+ spnegoProvider SpnegoProvider
+ Mechanism AuthenticationMechanism
+ activeMechanism AuthenticationMechanism
+ state AuthenticationState
+ cycleCount int
+ logger *log.Logger
+}
+
+func NewHandler(mechanism AuthenticationMechanism) AuthenticationHandlerInterface {
+ a := &AuthenticationHandler{
+ spnegoProvider: SpnegoProviderInstance(),
+ Mechanism: mechanism,
+ activeMechanism: mechanism,
+ state: Initial,
+ logger: log.New(io.Discard, "", 0),
+ }
+
+ return a
+}
+
+func (a *AuthenticationHandler) Close() {
+ a.spnegoProvider.Close()
+ a.state = Close
+}
+
+func (a *AuthenticationHandler) GetAuthorizationValue(url *url.URL, responseToken string) (authorizeValue string, err error) {
+ mechanism := StringFromAuthenticationMechanism(a.activeMechanism)
+
+ if a.activeMechanism == Negotiate { // supporting mechanism: Negotiate (SPNEGO)
+ var token string
+ var done bool
+
+ if len(responseToken) == 0 && Negotiating == a.state {
+ a.state = Error
+ return "", fmt.Errorf("Authentication failed! Unexpected empty token during negotiation!")
+ }
+
+ a.state = Negotiating
+
+ token, done, err = a.spnegoProvider.GetToken(url, responseToken)
+ if err != nil {
+ a.state = Error
+ return "", err
+ }
+
+ if done {
+ a.logger.Println("Local security context established!")
+ }
+
+ authorizeValue = mechanism + " " + token
+
+ if len(token) > 0 {
+ mechanisms, _ := GetMechanismsFromHttpFieldValue(authorizeValue)
+ a.logger.Printf("Authorization to %s using: %s", url, mechanisms)
+ }
+ }
+
+ a.cycleCount++
+ if a.cycleCount >= maxCycleCount {
+ err = fmt.Errorf("Failed to authenticate within %d cycles, stopping now!", maxCycleCount)
+ authorizeValue = ""
+ }
+
+ return authorizeValue, err
+}
+
+func (a *AuthenticationHandler) Update(availableMechanism map[AuthenticationMechanism]string) (responseToken string, err error) {
+
+ // if AnyAuth is selected, we need to determine the best supported mechanism on both sides
+ if a.activeMechanism == AnyAuth {
+ // currently we only support Negotiate, AnyAuth will use Negotiate if the communication partner proposes it
+ if _, ok := availableMechanism[Negotiate]; ok {
+ a.activeMechanism = Negotiate
+ a.logger.Printf("Selected Mechanism: %s\n", StringFromAuthenticationMechanism(a.activeMechanism))
+ }
+ }
+
+ // extract the token for the active mechanism
+ if token, ok := availableMechanism[a.activeMechanism]; ok {
+ responseToken = token
+ } else {
+ err = fmt.Errorf("Incorrect or unsupported Mechanism detected! %s", availableMechanism)
+ }
+
+ return responseToken, err
+}
+
+func (a *AuthenticationHandler) IsStopped() bool {
+ return (a.state == Done || a.state == Error || a.state == Cancel || a.state == Close)
+}
+
+func (a *AuthenticationHandler) Cancel() {
+ a.state = Cancel
+ a.logger.Println("AuthenticationHandler.Cancel()")
+}
+
+func (a *AuthenticationHandler) Succesful() {
+ a.state = Done
+ a.logger.Println("AuthenticationHandler.Succesful()")
+}
+
+func (a *AuthenticationHandler) SetLogger(logger *log.Logger) {
+ a.logger = logger
+ a.spnegoProvider.SetLogger(logger)
+}
+
+func (a *AuthenticationHandler) SetSpnegoProvider(spnegoProvider SpnegoProvider) {
+ a.spnegoProvider = spnegoProvider
+}
+
+func StringFromAuthenticationMechanism(mechanism AuthenticationMechanism) string {
+ return strings.Title(string(mechanism))
+}
+
+func AuthenticationMechanismFromString(mechanism string) AuthenticationMechanism {
+ tmp := strings.ToLower(mechanism)
+ return AuthenticationMechanism(tmp)
+}
+
+func GetMechanismAndToken(HttpFieldValue string) (AuthenticationMechanism, string) {
+ mechanism := UnknownMechanism
+ token := ""
+
+ authenticateValue := strings.Split(HttpFieldValue, " ")
+ if len(authenticateValue) >= 1 {
+ mechanism = AuthenticationMechanismFromString(authenticateValue[0])
+ }
+
+ if len(authenticateValue) == 2 {
+ token = authenticateValue[1]
+ }
+
+ return mechanism, token
+}
+
+func IsSupportedMechanism(mechanism AuthenticationMechanism) bool {
+ if mechanism == Negotiate || mechanism == AnyAuth {
+ return true
+ }
+ return false
+}
diff --git a/cliv2/internal/httpauth/httpauth_generated_mock.go b/cliv2/internal/httpauth/httpauth_generated_mock.go
new file mode 100644
index 0000000000..e814a43396
--- /dev/null
+++ b/cliv2/internal/httpauth/httpauth_generated_mock.go
@@ -0,0 +1,140 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: httpauth.go
+
+// Package httpauth is a generated GoMock package.
+package httpauth
+
+import (
+ log "log"
+ url "net/url"
+ reflect "reflect"
+
+ gomock "github.com/golang/mock/gomock"
+)
+
+// MockAuthenticationHandlerInterface is a mock of AuthenticationHandlerInterface interface.
+type MockAuthenticationHandlerInterface struct {
+ ctrl *gomock.Controller
+ recorder *MockAuthenticationHandlerInterfaceMockRecorder
+}
+
+// MockAuthenticationHandlerInterfaceMockRecorder is the mock recorder for MockAuthenticationHandlerInterface.
+type MockAuthenticationHandlerInterfaceMockRecorder struct {
+ mock *MockAuthenticationHandlerInterface
+}
+
+// NewMockAuthenticationHandlerInterface creates a new mock instance.
+func NewMockAuthenticationHandlerInterface(ctrl *gomock.Controller) *MockAuthenticationHandlerInterface {
+ mock := &MockAuthenticationHandlerInterface{ctrl: ctrl}
+ mock.recorder = &MockAuthenticationHandlerInterfaceMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockAuthenticationHandlerInterface) EXPECT() *MockAuthenticationHandlerInterfaceMockRecorder {
+ return m.recorder
+}
+
+// Cancel mocks base method.
+func (m *MockAuthenticationHandlerInterface) Cancel() {
+ m.ctrl.T.Helper()
+ m.ctrl.Call(m, "Cancel")
+}
+
+// Cancel indicates an expected call of Cancel.
+func (mr *MockAuthenticationHandlerInterfaceMockRecorder) Cancel() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Cancel", reflect.TypeOf((*MockAuthenticationHandlerInterface)(nil).Cancel))
+}
+
+// Close mocks base method.
+func (m *MockAuthenticationHandlerInterface) Close() {
+ m.ctrl.T.Helper()
+ m.ctrl.Call(m, "Close")
+}
+
+// Close indicates an expected call of Close.
+func (mr *MockAuthenticationHandlerInterfaceMockRecorder) Close() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockAuthenticationHandlerInterface)(nil).Close))
+}
+
+// GetAuthorizationValue mocks base method.
+func (m *MockAuthenticationHandlerInterface) GetAuthorizationValue(url *url.URL, responseToken string) (string, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "GetAuthorizationValue", url, responseToken)
+ ret0, _ := ret[0].(string)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// GetAuthorizationValue indicates an expected call of GetAuthorizationValue.
+func (mr *MockAuthenticationHandlerInterfaceMockRecorder) GetAuthorizationValue(url, responseToken interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAuthorizationValue", reflect.TypeOf((*MockAuthenticationHandlerInterface)(nil).GetAuthorizationValue), url, responseToken)
+}
+
+// IsStopped mocks base method.
+func (m *MockAuthenticationHandlerInterface) IsStopped() bool {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "IsStopped")
+ ret0, _ := ret[0].(bool)
+ return ret0
+}
+
+// IsStopped indicates an expected call of IsStopped.
+func (mr *MockAuthenticationHandlerInterfaceMockRecorder) IsStopped() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsStopped", reflect.TypeOf((*MockAuthenticationHandlerInterface)(nil).IsStopped))
+}
+
+// SetLogger mocks base method.
+func (m *MockAuthenticationHandlerInterface) SetLogger(logger *log.Logger) {
+ m.ctrl.T.Helper()
+ m.ctrl.Call(m, "SetLogger", logger)
+}
+
+// SetLogger indicates an expected call of SetLogger.
+func (mr *MockAuthenticationHandlerInterfaceMockRecorder) SetLogger(logger interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLogger", reflect.TypeOf((*MockAuthenticationHandlerInterface)(nil).SetLogger), logger)
+}
+
+// SetSpnegoProvider mocks base method.
+func (m *MockAuthenticationHandlerInterface) SetSpnegoProvider(spnegoProvider SpnegoProvider) {
+ m.ctrl.T.Helper()
+ m.ctrl.Call(m, "SetSpnegoProvider", spnegoProvider)
+}
+
+// SetSpnegoProvider indicates an expected call of SetSpnegoProvider.
+func (mr *MockAuthenticationHandlerInterfaceMockRecorder) SetSpnegoProvider(spnegoProvider interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSpnegoProvider", reflect.TypeOf((*MockAuthenticationHandlerInterface)(nil).SetSpnegoProvider), spnegoProvider)
+}
+
+// Succesful mocks base method.
+func (m *MockAuthenticationHandlerInterface) Succesful() {
+ m.ctrl.T.Helper()
+ m.ctrl.Call(m, "Succesful")
+}
+
+// Succesful indicates an expected call of Succesful.
+func (mr *MockAuthenticationHandlerInterfaceMockRecorder) Succesful() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Succesful", reflect.TypeOf((*MockAuthenticationHandlerInterface)(nil).Succesful))
+}
+
+// Update mocks base method.
+func (m *MockAuthenticationHandlerInterface) Update(availableMechanism map[AuthenticationMechanism]string) (string, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Update", availableMechanism)
+ ret0, _ := ret[0].(string)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// Update indicates an expected call of Update.
+func (mr *MockAuthenticationHandlerInterfaceMockRecorder) Update(availableMechanism interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockAuthenticationHandlerInterface)(nil).Update), availableMechanism)
+}
diff --git a/cliv2/internal/httpauth/httpauth_test.go b/cliv2/internal/httpauth/httpauth_test.go
new file mode 100644
index 0000000000..f036b110ae
--- /dev/null
+++ b/cliv2/internal/httpauth/httpauth_test.go
@@ -0,0 +1,232 @@
+package httpauth
+
+import (
+ "fmt"
+ "log"
+ "net/url"
+ "os"
+ "testing"
+
+ "github.com/golang/mock/gomock"
+ "github.com/stretchr/testify/assert"
+)
+
+var testLogger = log.New(os.Stderr, "", log.Ldate|log.Ltime|log.Lmicroseconds|log.Lshortfile)
+
+const (
+ NTML_01_INITIALMESSAGE = "TlRMTVNTUAABAAAAl4II4gAAAAAAAAAAAAAAAAAAAAAKADk4AAAADw=="
+ NTML_02_CHALLENGE = "TlRMTVNTUAACAAAADgAOADgAAAAVgoni63VPOgRoQ04AAAAAAAAAAKQApABGAAAABgEAAAAAAA9IAEEATQBNAEUAUgAyAAIADgBIAEEATQBNAEUAUgAyAAEAHABFAEMAMgBBAE0AQQBaAC0AVgBVAEgATABOAFEABAAeAGgAYQBtAG0AZQByADIALgBzAG4AeQBrAC4AaQBvAAMAPABlAGMAMgBhAG0AYQB6AC0AdgB1AGgAbABuAHEALgBoAGEAbQBtAGUAcgAyAC4AcwBuAHkAawAuAGkAbwAHAAgAzu0E3y2j2AEAAAAA"
+ NTML_03_RESPONSE = "TlRMTVNTUAADAAAAGAAYAI4AAABcAVwBpgAAAA4ADgBYAAAACgAKAGYAAAAeAB4AcAAAABAAEAACAgAAFYKI4goAOTgAAAAPxbettwPT/UuVcj7f+eHILmgAYQBtAG0AZQByADIAQQBkAG0AaQBuAEUAQwAyAEEATQBBAFoALQA4ADMATwBVADMARABUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/6ZPp6zbTJBkvgadfD80wBAQAAAAAAAM7tBN8to9gBZ6ejc1nCVp0AAAAAAgAOAEgAQQBNAE0ARQBSADIAAQAcAEUAQwAyAEEATQBBAFoALQBWAFUASABMAE4AUQAEAB4AaABhAG0AbQBlAHIAMgAuAHMAbgB5AGsALgBpAG8AAwA8AGUAYwAyAGEAbQBhAHoALQB2AHUAaABsAG4AcQAuAGgAYQBtAG0AZQByADIALgBzAG4AeQBrAC4AaQBvAAcACADO7QTfLaPYAQYABAACAAAACAAwADAAAAAAAAAAAQAAAAAgAADQl5cStKyz5f8Kuwh+f2BiiWY4vHopxNdJTsXVQqRoLgoAEAAAAAAAAAAAAAAAAAAAAAAACQA0AEgAVABUAFAALwBwAHIAbwB4AHkALgBoAGEAbQBtAGUAcgAyAC4AcwBuAHkAawAuAGkAbwAAAAAAAAAAACKmvM2kkvW9vroG6NaLu8s="
+)
+
+func Test_DisableAuthentication(t *testing.T) {
+
+ proxyAddr, _ := url.Parse("http://127.0.0.1")
+ expectedValue := ""
+
+ authHandler := NewHandler(NoAuth)
+
+ actualValue, err := authHandler.GetAuthorizationValue(proxyAddr, "")
+ assert.Nil(t, err)
+
+ assert.Equal(t, expectedValue, actualValue)
+
+}
+
+func Test_EnabledAuthentication_Negotiate_Success_NLTM(t *testing.T) {
+
+ // specify test data
+ proxyAddr, _ := url.Parse("http://127.0.0.1")
+ mechanism := Negotiate
+ expectedValue := StringFromAuthenticationMechanism(mechanism)
+
+ // setup test environment
+ spnegoProviderMock := NewMockSpnegoProvider(gomock.NewController(t))
+ defer spnegoProviderMock.ctrl.Finish()
+ spnegoProviderMock.EXPECT().GetToken(proxyAddr, "").Times(1).Return(NTML_01_INITIALMESSAGE, false, nil)
+ spnegoProviderMock.EXPECT().GetToken(proxyAddr, NTML_02_CHALLENGE).Times(1).Return(NTML_03_RESPONSE, true, nil)
+
+ authHandler := NewHandler(mechanism)
+ authHandler.SetSpnegoProvider(spnegoProviderMock)
+
+ // actually test
+ actualValue, err := authHandler.GetAuthorizationValue(proxyAddr, "")
+ assert.Nil(t, err)
+ assert.Contains(t, actualValue, expectedValue)
+ assert.False(t, authHandler.IsStopped())
+
+ actualValue, err = authHandler.GetAuthorizationValue(proxyAddr, NTML_02_CHALLENGE)
+ assert.Nil(t, err)
+ assert.Contains(t, actualValue, expectedValue)
+ assert.False(t, authHandler.IsStopped())
+
+ authHandler.Succesful()
+ assert.True(t, authHandler.IsStopped())
+}
+
+func Test_EnabledAuthentication_Negotiate_Fail_NLTM01(t *testing.T) {
+
+ // specify test data
+ proxyAddr, _ := url.Parse("http://127.0.0.1")
+ mechanism := Negotiate
+ expectedValue := StringFromAuthenticationMechanism(mechanism)
+
+ // setup test environment
+ spnegoProviderMock := NewMockSpnegoProvider(gomock.NewController(t))
+ defer spnegoProviderMock.ctrl.Finish()
+ spnegoProviderMock.EXPECT().GetToken(proxyAddr, "").Times(1).Return(NTML_01_INITIALMESSAGE, false, nil)
+
+ authHandler := NewHandler(mechanism)
+ authHandler.SetSpnegoProvider(spnegoProviderMock)
+
+ // actually test
+ actualValue, err := authHandler.GetAuthorizationValue(proxyAddr, "")
+ assert.Nil(t, err)
+ assert.Contains(t, actualValue, expectedValue)
+ assert.False(t, authHandler.IsStopped())
+
+ actualValue, err = authHandler.GetAuthorizationValue(proxyAddr, "") // unexpected during negotiation
+ assert.NotNil(t, err)
+ assert.Empty(t, actualValue)
+ assert.True(t, authHandler.IsStopped())
+}
+
+func Test_EnabledAuthentication_Negotiate_Fail_NLTM02(t *testing.T) {
+
+ // specify test data
+ proxyAddr, _ := url.Parse("http://127.0.0.1")
+ mechanism := Negotiate
+ expectedValue := StringFromAuthenticationMechanism(mechanism)
+ incorrectResponseToken := "Some incorrect token maybe"
+
+ // setup test environment
+ spnegoProviderMock := NewMockSpnegoProvider(gomock.NewController(t))
+ defer spnegoProviderMock.ctrl.Finish()
+ spnegoProviderMock.EXPECT().GetToken(proxyAddr, "").Times(1).Return(NTML_01_INITIALMESSAGE, false, nil)
+ spnegoProviderMock.EXPECT().GetToken(proxyAddr, incorrectResponseToken).Times(1).Return("", false, fmt.Errorf("Something went wrong")) // returning an error from the spnegoProvider
+
+ authHandler := NewHandler(mechanism)
+ authHandler.SetSpnegoProvider(spnegoProviderMock)
+
+ // actually test
+ actualValue, err := authHandler.GetAuthorizationValue(proxyAddr, "")
+ assert.Nil(t, err)
+ assert.Contains(t, actualValue, expectedValue)
+ assert.False(t, authHandler.IsStopped())
+
+ actualValue, err = authHandler.GetAuthorizationValue(proxyAddr, incorrectResponseToken)
+ assert.NotNil(t, err)
+ assert.Empty(t, actualValue)
+ assert.True(t, authHandler.IsStopped())
+}
+
+func Test_EnabledAuthentication_Negotiate_Fail_NLTM03(t *testing.T) {
+
+ // specify test data
+ proxyAddr, _ := url.Parse("http://127.0.0.1")
+ mechanism := Negotiate
+ expectedValue := StringFromAuthenticationMechanism(mechanism)
+
+ // setup test environment
+ spnegoProviderMock := NewMockSpnegoProvider(gomock.NewController(t))
+ defer spnegoProviderMock.ctrl.Finish()
+ spnegoProviderMock.EXPECT().GetToken(proxyAddr, "").Times(1).Return(NTML_01_INITIALMESSAGE, false, nil)
+ spnegoProviderMock.EXPECT().GetToken(proxyAddr, NTML_02_CHALLENGE).MinTimes(2).Return("", false, nil)
+
+ authHandler := NewHandler(mechanism)
+ authHandler.SetSpnegoProvider(spnegoProviderMock)
+
+ // actually test
+ actualValue, err := authHandler.GetAuthorizationValue(proxyAddr, "")
+ assert.Nil(t, err)
+ assert.Contains(t, actualValue, expectedValue)
+ assert.False(t, authHandler.IsStopped())
+
+ for i := 0; i < maxCycleCount-2; i++ {
+ actualValue, err = authHandler.GetAuthorizationValue(proxyAddr, NTML_02_CHALLENGE)
+ assert.Nil(t, err)
+ assert.Contains(t, actualValue, expectedValue)
+ assert.False(t, authHandler.IsStopped())
+ }
+
+ actualValue, err = authHandler.GetAuthorizationValue(proxyAddr, NTML_02_CHALLENGE) // exceed max cycles of authentication messages exchanged
+ assert.NotNil(t, err)
+ assert.Empty(t, actualValue)
+ assert.False(t, authHandler.IsStopped())
+
+}
+
+func Test_EnabledAuthentication_Negotiate_Update_AnyAuth_success(t *testing.T) {
+ // specify test data
+ mechanism := AnyAuth
+ expectedToken := "ghi"
+ availableMechanism := map[AuthenticationMechanism]string{
+ NoAuth: "abc",
+ Negotiate: expectedToken,
+ UnknownMechanism: "def",
+ }
+
+ // setup test environment
+ authHandler := NewHandler(mechanism)
+
+ actualToken, err := authHandler.Update(availableMechanism)
+ assert.Nil(t, err)
+ assert.Equal(t, expectedToken, actualToken)
+}
+
+func Test_EnabledAuthentication_Negotiate_Update_AnyAuth_fail(t *testing.T) {
+ // specify test data
+ mechanism := AnyAuth
+ expectedToken := ""
+ availableMechanism := map[AuthenticationMechanism]string{
+ NoAuth: "abc",
+ UnknownMechanism: "def",
+ }
+
+ // setup test environment
+ authHandler := NewHandler(mechanism)
+
+ actualToken, err := authHandler.Update(availableMechanism)
+ assert.NotNil(t, err)
+ assert.Equal(t, expectedToken, actualToken)
+}
+
+func Test_AuthenticationMechanismFromAndToString(t *testing.T) {
+
+ testSet := []AuthenticationMechanism{
+ AnyAuth,
+ Negotiate,
+ NoAuth,
+ UnknownMechanism,
+ }
+
+ var mechanismConverted AuthenticationMechanism
+ var mechanismString string
+
+ for _, mechanism := range testSet {
+ mechanismString = StringFromAuthenticationMechanism(mechanism)
+ mechanismConverted = AuthenticationMechanismFromString(mechanismString)
+ assert.Equal(t, mechanism, mechanismConverted)
+ }
+
+ // different casing
+ mechanismConverted = AuthenticationMechanismFromString("NEGOTIATE")
+ assert.Equal(t, Negotiate, mechanismConverted)
+}
+
+func Test_LookupSchemeFromAddress(t *testing.T) {
+ defaultValue := "none"
+
+ input := map[string]string{
+ "snyk.io:443": "https",
+ "snyk.io:80": "http",
+ "snyk.io:1080": "socks5",
+ "snyk.io": defaultValue,
+ "snyk.io:443:das": defaultValue,
+ }
+
+ for addr, expected := range input {
+ actual := LookupSchemeFromCannonicalAddress(addr, defaultValue)
+ assert.Equal(t, expected, actual)
+ }
+}
diff --git a/cliv2/internal/httpauth/proxy_authenticator.go b/cliv2/internal/httpauth/proxy_authenticator.go
new file mode 100644
index 0000000000..0a759ad16e
--- /dev/null
+++ b/cliv2/internal/httpauth/proxy_authenticator.go
@@ -0,0 +1,294 @@
+package httpauth
+
+import (
+ "bufio"
+ "context"
+ "fmt"
+ "log"
+ "net"
+ "net/http"
+ "net/url"
+ "strings"
+ "time"
+
+ "golang.org/x/net/idna"
+)
+
+type ProxyAuthenticator struct {
+ acceptedProxyAuthMechanism AuthenticationMechanism
+ debugLogger *log.Logger
+ upstreamProxy func(*http.Request) (*url.URL, error)
+ CreateHandler func(mechanism AuthenticationMechanism) AuthenticationHandlerInterface
+}
+
+func NewProxyAuthenticator(mechanism AuthenticationMechanism, upstreamProxy func(*http.Request) (*url.URL, error), logger *log.Logger) *ProxyAuthenticator {
+ authenticator := &ProxyAuthenticator{
+ acceptedProxyAuthMechanism: mechanism,
+ debugLogger: logger,
+ upstreamProxy: upstreamProxy,
+ CreateHandler: NewHandler,
+ }
+ return authenticator
+}
+
+// This is the main entry point function for the ProxyAuthenticator when being used with http.Transport.
+// It should be used like this: transport.DialContext = DialContext
+// It'll be invoked by http.Transport when it requires a new TCP connection.
+func (p *ProxyAuthenticator) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
+ var connection net.Conn
+ var err error
+ var proxyUrl *url.URL
+
+ if p.upstreamProxy != nil {
+ fakeRequest := &http.Request{URL: &url.URL{}}
+ fakeRequest.URL.Scheme = LookupSchemeFromCannonicalAddress(addr, "https")
+ fakeRequest.URL.Host = addr
+ proxyUrl, err = p.upstreamProxy(fakeRequest)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ if proxyUrl != nil {
+ proxyAddr := CanonicalAddr(proxyUrl)
+ createConnectionFunc := func() (net.Conn, error) {
+ c, e := net.Dial(network, proxyAddr)
+ if e == nil {
+ p.debugLogger.Printf("Connecting to %s from %s via %s\n", addr, c.LocalAddr(), c.RemoteAddr())
+ }
+ return c, e
+ }
+
+ connection, err = p.connectToProxy(ctx, proxyUrl, addr, createConnectionFunc)
+
+ if err != nil {
+ fmt.Println("Failed to connect to Proxy! ", proxyUrl)
+ }
+ } else {
+ p.debugLogger.Println("No Proxy defined for ", addr, "!")
+ connection, err = net.Dial(network, addr)
+ }
+
+ return connection, err
+}
+
+// This method takes the given connection and connects to the specified proxy (RFC 2817) while adding proxy authentication (RFC 4559).
+func (p *ProxyAuthenticator) connectToProxy(ctx context.Context, proxyURL *url.URL, target string, createConnection func() (net.Conn, error)) (net.Conn, error) {
+ var err error
+ var token string
+ var responseToken string
+ var connection net.Conn
+
+ if createConnection == nil {
+ return nil, fmt.Errorf("Given connection must not be nil!")
+ }
+
+ if proxyURL == nil {
+ return nil, fmt.Errorf("Given proxyUrl must not be nil!")
+ }
+
+ if len(target) == 0 {
+ return nil, fmt.Errorf("Given target address must not be empty!")
+ }
+
+ if p.acceptedProxyAuthMechanism != NoAuth {
+ newConnectionRequired := true
+ authHandler := p.CreateHandler(p.acceptedProxyAuthMechanism)
+ authHandler.SetLogger(p.debugLogger)
+ defer authHandler.Close()
+
+ p.debugLogger.Println("Proxy Address:", proxyURL)
+
+ for !authHandler.IsStopped() {
+ proxyConnectHeader := make(http.Header)
+ var response *http.Response
+ var responseError error
+
+ if token, err = authHandler.GetAuthorizationValue(proxyURL, responseToken); err != nil {
+ err = fmt.Errorf("Failed to retreive Proxy Authorization! %v", err)
+ }
+
+ if err == nil {
+ if newConnectionRequired { // open a new connection if required
+ if connection != nil {
+ connection.Close()
+ }
+ connection, err = createConnection()
+ }
+ }
+
+ if err == nil {
+ if len(token) > 0 { // Add Header if a token is available
+ proxyConnectHeader.Add(ProxyAuthorizationKey, token)
+ p.debugLogger.Printf("> %s: %s\n", ProxyAuthorizationKey, token)
+ newConnectionRequired = false
+ } else { // Connect without specific header, if this fails, we require a new connection later
+ p.debugLogger.Printf("> CONNECT without %s", ProxyAuthorizationKey)
+ newConnectionRequired = true
+ }
+
+ request := &http.Request{
+ Method: "CONNECT",
+ URL: &url.URL{Opaque: target},
+ Host: target,
+ Header: proxyConnectHeader,
+ }
+
+ response, responseError = p.SendRequest(ctx, connection, request)
+ responseToken, err = p.processResponse(authHandler, response, responseError)
+ }
+
+ if err != nil {
+ authHandler.Cancel()
+
+ if connection != nil {
+ connection.Close()
+ connection = nil
+ }
+ }
+ }
+ }
+
+ return connection, err
+}
+
+func (p *ProxyAuthenticator) processResponse(authHandler AuthenticationHandlerInterface, response *http.Response, responseError error) (responseToken string, err error) {
+ if responseError != nil {
+ err = fmt.Errorf("Failed to CONNECT to proxy! (%v)", responseError)
+ } else if response != nil && response.StatusCode == 407 {
+ responseToken, err = p.processResponse407(authHandler, response)
+ } else if response != nil && response.StatusCode <= 200 && response.StatusCode <= 299 {
+ authHandler.Succesful()
+ } else if response != nil {
+ err = fmt.Errorf("Unexpected HTTP Status Code (%d)", response.StatusCode)
+ } else {
+ err = fmt.Errorf("Failed to CONNECT to proxy due to unknown error!")
+ }
+ return responseToken, err
+}
+
+func (p *ProxyAuthenticator) processResponse407(authHandler AuthenticationHandlerInterface, response *http.Response) (responseToken string, err error) {
+
+ result := response.Header.Values(ProxyAuthenticateKey)
+ availableMechanismCount := len(result)
+ if availableMechanismCount >= 1 {
+ p.debugLogger.Printf("< %s: %s\n", ProxyAuthenticateKey, result)
+ availableMechanismList := make(map[AuthenticationMechanism]string, availableMechanismCount)
+
+ for i := range result {
+ tempMechanism, tempResponseToken := GetMechanismAndToken(result[i])
+ availableMechanismList[tempMechanism] = tempResponseToken
+
+ p.debugLogger.Printf(" %d. Detected Mechanism: %s\n", i, StringFromAuthenticationMechanism(tempMechanism))
+
+ if len(tempResponseToken) != 0 {
+ p.debugLogger.Printf(" %d. Response Token: %s\n", i, tempResponseToken)
+ }
+ }
+
+ responseToken, err = authHandler.Update(availableMechanismList)
+
+ } else {
+ err = fmt.Errorf("Received 407 but didn't find \"%s\" in the header! (%v)", ProxyAuthenticateKey, response.Header)
+ }
+
+ return responseToken, err
+}
+
+func (p *ProxyAuthenticator) GetMechanism() AuthenticationMechanism {
+ return p.acceptedProxyAuthMechanism
+}
+
+func LookupSchemeFromCannonicalAddress(addr string, defaultScheme string) string {
+ result := defaultScheme
+ port := ""
+ tempAddr := strings.Split(addr, ":")
+ tempAddrLen := len(tempAddr)
+ if tempAddrLen >= 2 {
+ port = tempAddr[tempAddrLen-1]
+ }
+
+ for k, v := range portMap {
+ if v == port {
+ result = k
+ }
+ }
+ return result
+}
+
+// the following code is partially taken and adapted from net/http/transport.go ----
+
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// HTTP client implementation. See RFC 7230 through 7235.
+//
+// This is the low-level Transport implementation of RoundTripper.
+// The high-level interface is in client.go.
+
+func (p *ProxyAuthenticator) SendRequest(ctx context.Context, connection net.Conn, request *http.Request) (*http.Response, error) {
+
+ // If there's no done channel (no deadline or cancellation
+ // from the caller possible), at least set some (long)
+ // timeout here. This will make sure we don't block forever
+ // and leak a goroutine if the connection stops replying
+ // after the TCP connect.
+ connectCtx := ctx
+ if ctx.Done() == nil {
+ newCtx, cancel := context.WithTimeout(ctx, 1*time.Minute)
+ defer cancel()
+ connectCtx = newCtx
+ }
+
+ didReadResponse := make(chan struct{}) // closed after CONNECT write+read is done or fails
+ var (
+ resp *http.Response
+ err error // write or read error
+ )
+ // Write the CONNECT request & read the response.
+ go func() {
+ defer close(didReadResponse)
+ err = request.Write(connection)
+ if err != nil {
+ return
+ }
+ // Okay to use and discard buffered reader here, because
+ // TLS server will not speak until spoken to.
+ br := bufio.NewReader(connection)
+ resp, err = http.ReadResponse(br, request)
+ }()
+ select {
+ case <-connectCtx.Done():
+ connection.Close()
+ <-didReadResponse
+ return nil, connectCtx.Err()
+ case <-didReadResponse:
+ // resp or err now set
+ }
+
+ if resp != nil {
+ resp.Body.Close()
+ }
+
+ return resp, err
+}
+
+var portMap = map[string]string{
+ "http": "80",
+ "https": "443",
+ "socks5": "1080",
+}
+
+// CanonicalAddr returns url.Host but always with a ":port" suffix
+func CanonicalAddr(url *url.URL) string {
+ addr := url.Hostname()
+ if v, err := idna.Lookup.ToASCII(addr); err == nil {
+ addr = v
+ }
+ port := url.Port()
+ if port == "" {
+ port = portMap[url.Scheme]
+ }
+ return net.JoinHostPort(addr, port)
+}
diff --git a/cliv2/internal/httpauth/proxy_authenticator_test.go b/cliv2/internal/httpauth/proxy_authenticator_test.go
new file mode 100644
index 0000000000..4f77e41ff3
--- /dev/null
+++ b/cliv2/internal/httpauth/proxy_authenticator_test.go
@@ -0,0 +1,513 @@
+package httpauth
+
+import (
+ "context"
+ "fmt"
+ "net"
+ "net/http"
+ "net/url"
+ "testing"
+
+ "github.com/golang/mock/gomock"
+ "github.com/snyk/cli/cliv2/internal/httpauth/test/helper"
+ "github.com/stretchr/testify/assert"
+)
+
+//go:generate $GOPATH/bin/mockgen -source=httpauth.go -destination ./httpauth_generated_mock.go -package httpauth -self_package github.com/snyk/cli/cliv2/internal/httpauth
+
+func createMockServer() (*helper.MockServer, net.Conn) {
+ // prepare test data
+ mockServer := helper.NewMockServer()
+ go mockServer.Listen()
+
+ connection, _ := net.Dial("tcp", ":"+fmt.Sprint(mockServer.Port))
+
+ return mockServer, connection
+}
+
+func helper_processResponse407(t *testing.T, success bool, mechanism AuthenticationMechanism, expectedToken string, response *http.Response) {
+ // prepare test environment
+ authenticator := NewProxyAuthenticator(mechanism, nil, testLogger)
+ authHandler := NewHandler(authenticator.acceptedProxyAuthMechanism)
+
+ // run method under test
+ token, err := authenticator.processResponse407(authHandler, response)
+
+ // check expectations
+ assert.Equal(t, expectedToken, token)
+
+ if success {
+ assert.Nil(t, err)
+ } else {
+ assert.NotNil(t, err)
+ }
+}
+
+func helper_processResponse(t *testing.T, success bool, mechanism AuthenticationMechanism, expectedToken string, response *http.Response, responseError error) AuthenticationHandlerInterface {
+ // prepare test environment
+ authenticator := NewProxyAuthenticator(mechanism, nil, testLogger)
+ authHandler := NewHandler(authenticator.acceptedProxyAuthMechanism)
+
+ // run method under test
+ token, err := authenticator.processResponse(authHandler, response, responseError)
+
+ // check expectations
+ assert.Equal(t, expectedToken, token)
+
+ if success {
+ assert.Nil(t, err)
+ } else {
+ assert.NotNil(t, err)
+ }
+ return authHandler
+}
+
+func helper_DialContext(t *testing.T, success bool, applyExpectations func(*MockAuthenticationHandlerInterface), mechanism AuthenticationMechanism, proxyFunc func(*http.Request) (*url.URL, error), target string) {
+ context := context.Background()
+
+ ctrl := gomock.NewController(t)
+ mockedAuthenticationHandler := NewMockAuthenticationHandlerInterface(ctrl)
+
+ // prepare test environment
+ authenticator := NewProxyAuthenticator(mechanism, proxyFunc, testLogger)
+ authenticator.CreateHandler = func(mechanism AuthenticationMechanism) AuthenticationHandlerInterface {
+ return mockedAuthenticationHandler
+ }
+
+ if applyExpectations != nil {
+ applyExpectations(mockedAuthenticationHandler)
+ }
+
+ // run method under test
+ connection, err := authenticator.DialContext(context, "tcp", target)
+
+ // check expectations
+ if success {
+ assert.Nil(t, err)
+ assert.NotNil(t, connection)
+ } else {
+ assert.NotNil(t, err)
+ assert.Nil(t, connection)
+ }
+}
+
+func Test_ProxyAuthenticator_processResponse407_fail01(t *testing.T) {
+ // prepare test data
+ mechanism := Negotiate
+ expectedToken := ""
+ response := &http.Response{}
+
+ helper_processResponse407(t, false, mechanism, expectedToken, response)
+}
+
+func Test_ProxyAuthenticator_processResponse407_fail02(t *testing.T) {
+ // prepare test data
+ mechanism := Negotiate
+ expectedToken := ""
+ response := &http.Response{
+ Header: http.Header{},
+ }
+ response.Header.Add(ProxyAuthenticateKey, "NTLM TlRMTVNTUAACAAAADgAOADgAAAAVgoni/Fy06M1ZvVQAAAAAAAAAAKQApABGAAAABgEAAAAAAA9IAEEATQBNAEUAUgAyAAIADgBIAEEATQBNAEUAUgAyAAEAHABFAEMAMgBBAE0AQQBaAC0AVgBVAEgATABOAFEABAAeAGgAYQBtAG0AZQByADIALgBzAG4AeQBrAC4AaQBvAAMAPABlAGMAMgBhAG0AYQB6AC0AdgB1AGgAbABuAHEALgBoAGEAbQBtAGUAcgAyAC4AcwBuAHkAawAuAGkAbwAHAAgAli6vQTKY2AEAAAAA")
+
+ helper_processResponse407(t, false, mechanism, expectedToken, response)
+}
+
+func Test_ProxyAuthenticator_processResponse407_fail03(t *testing.T) {
+ // prepare test data
+ mechanism := Negotiate
+ expectedToken := ""
+ response := &http.Response{
+ Header: http.Header{},
+ }
+ response.Header.Add(ProxyAuthenticateKey, "") // empty value
+
+ helper_processResponse407(t, false, mechanism, expectedToken, response)
+}
+
+func Test_ProxyAuthenticator_processResponse407_success(t *testing.T) {
+ // prepare test data
+ mechanism := Negotiate
+ expectedToken := "TlRMTVNTUAACAAAADgAOADgAAAAVgoni/Fy06M1ZvVQAAAAAAAAAAKQApABGAAAABgEAAAAAAA9IAEEATQBNAEUAUgAyAAIADgBIAEEATQBNAEUAUgAyAAEAHABFAEMAMgBBAE0AQQBaAC0AVgBVAEgATABOAFEABAAeAGgAYQBtAG0AZQByADIALgBzAG4AeQBrAC4AaQBvAAMAPABlAGMAMgBhAG0AYQB6AC0AdgB1AGgAbABuAHEALgBoAGEAbQBtAGUAcgAyAC4AcwBuAHkAawAuAGkAbwAHAAgAli6vQTKY2AEAAAAA"
+ response := &http.Response{
+ Header: http.Header{},
+ }
+ response.Header.Add(ProxyAuthenticateKey, "Negotiate "+expectedToken)
+
+ helper_processResponse407(t, true, mechanism, expectedToken, response)
+}
+
+func Test_ProxyAuthenticator_processResponse407_success02(t *testing.T) {
+ // prepare test data
+ mechanism := Negotiate
+ expectedToken := "something"
+ response := &http.Response{
+ Header: http.Header{},
+ }
+
+ response.Header.Add(ProxyAuthenticateKey, "NTLM")
+ response.Header.Add(ProxyAuthenticateKey, "NEGOTIATE "+expectedToken)
+
+ helper_processResponse407(t, true, mechanism, expectedToken, response)
+}
+
+func Test_ProxyAuthenticator_processResponse_fail01(t *testing.T) {
+ // prepare test data
+ mechanism := Negotiate
+ expectedToken := ""
+ responseError := fmt.Errorf("Some downstream error occured!")
+ response := &http.Response{
+ Header: http.Header{},
+ }
+ response.Header.Add(ProxyAuthenticateKey, "Negotiate ")
+
+ helper_processResponse(t, false, mechanism, expectedToken, response, responseError)
+}
+
+func Test_ProxyAuthenticator_processResponse_fail02(t *testing.T) {
+ // prepare test data
+ mechanism := Negotiate
+ expectedToken := ""
+ responseError := error(nil)
+ response := &http.Response{
+ StatusCode: 500, // unexpected StatusCode
+ Header: http.Header{},
+ }
+
+ helper_processResponse(t, false, mechanism, expectedToken, response, responseError)
+}
+
+func Test_ProxyAuthenticator_processResponse_fail03(t *testing.T) {
+ // prepare test data
+ mechanism := Negotiate
+ expectedToken := ""
+ responseError := error(nil)
+ var response *http.Response = nil // response is nil
+
+ helper_processResponse(t, false, mechanism, expectedToken, response, responseError)
+}
+
+func Test_ProxyAuthenticator_processResponse_success407(t *testing.T) {
+ // prepare test data
+ mechanism := Negotiate
+ expectedToken := "TlRMTVNTUAACAAAADgAOADgAAAAVgoni/Fy06M1ZvVQAAAAAAAAAAKQApABGAAAABgEAAAAAAA9IAEEATQBNAEUAUgAyAAIADgBIAEEATQBNAEUAUgAyAAEAHABFAEMAMgBBAE0AQQBaAC0AVgBVAEgATABOAFEABAAeAGgAYQBtAG0AZQByADIALgBzAG4AeQBrAC4AaQBvAAMAPABlAGMAMgBhAG0AYQB6AC0AdgB1AGgAbABuAHEALgBoAGEAbQBtAGUAcgAyAC4AcwBuAHkAawAuAGkAbwAHAAgAli6vQTKY2AEAAAAA"
+ responseError := error(nil)
+ response := &http.Response{
+ StatusCode: 407,
+ Header: http.Header{},
+ }
+ response.Header.Add(ProxyAuthenticateKey, "Negotiate "+expectedToken)
+
+ authHandler := helper_processResponse(t, true, mechanism, expectedToken, response, responseError)
+ assert.False(t, authHandler.IsStopped())
+}
+
+func Test_ProxyAuthenticator_processResponse_success200(t *testing.T) {
+ // prepare test data
+ mechanism := Negotiate
+ expectedToken := ""
+ responseError := error(nil)
+ response := &http.Response{
+ StatusCode: 200,
+ }
+
+ authHandler := helper_processResponse(t, true, mechanism, expectedToken, response, responseError)
+ assert.True(t, authHandler.IsStopped())
+}
+
+func Test_ProxyAuthenticator_connectToProxy_fail01(t *testing.T) {
+ // prepare test data
+ context := context.Background()
+ mechanism := Negotiate
+ proxyUrl := &url.URL{}
+ target := ""
+ var createConnectionFunc func() (net.Conn, error) = nil // shouldn't be nil
+
+ // prepare test environment
+ authenticator := NewProxyAuthenticator(mechanism, nil, testLogger)
+
+ // run method under test
+ connection, err := authenticator.connectToProxy(context, proxyUrl, target, createConnectionFunc)
+
+ // check expectations
+ assert.NotNil(t, err)
+ assert.Nil(t, connection)
+}
+
+func Test_ProxyAuthenticator_connectToProxy_fail02(t *testing.T) {
+ // prepare test data
+ context := context.Background()
+ mechanism := Negotiate
+ proxyUrl := &url.URL{Opaque: "https://snyk.io"}
+ target := "" // shouldn't be empty
+ createConnectionFunc := func() (net.Conn, error) { return nil, nil }
+
+ // prepare test environment
+ authenticator := NewProxyAuthenticator(mechanism, nil, testLogger)
+
+ // run method under test
+ connection, err := authenticator.connectToProxy(context, proxyUrl, target, createConnectionFunc)
+
+ // check expectations
+ assert.NotNil(t, err)
+ assert.Nil(t, connection)
+}
+
+func Test_ProxyAuthenticator_connectToProxy_fail03(t *testing.T) {
+ // prepare test data
+ context := context.Background()
+ mechanism := Negotiate
+ var proxyUrl *url.URL = nil // shouldn't be nil
+ target := "snyk.io:443"
+ createConnectionFunc := func() (net.Conn, error) { return nil, nil }
+
+ // prepare test environment
+ authenticator := NewProxyAuthenticator(mechanism, nil, testLogger)
+
+ // run method under test
+ connection, err := authenticator.connectToProxy(context, proxyUrl, target, createConnectionFunc)
+
+ // check expectations
+ assert.NotNil(t, err)
+ assert.Nil(t, connection)
+}
+
+func Test_ProxyAuthenticator_connectToProxy_fail04(t *testing.T) {
+ mockServer, masterConnection := createMockServer()
+
+ // prepare test data
+ mockServer.ResponseList = append(mockServer.ResponseList, &http.Response{StatusCode: 407, Header: http.Header{ProxyAuthenticateKey: []string{"Negotiate 123456789"}}})
+ mockServer.ResponseList = append(mockServer.ResponseList, &http.Response{StatusCode: 200})
+
+ context := context.Background()
+ mechanism := Negotiate
+ proxyUrl := &url.URL{Opaque: "http://127.0.0.1:3128"}
+ target := "snyk.io:443"
+ createConnectionFunc := func() (net.Conn, error) { return masterConnection, nil }
+
+ ctrl := gomock.NewController(t)
+ mockedAuthenticationHandler := NewMockAuthenticationHandlerInterface(ctrl)
+
+ mockedAuthenticationHandler.EXPECT().IsStopped().Return(false).Times(len(mockServer.ResponseList))
+ mockedAuthenticationHandler.EXPECT().IsStopped().Return(true).Times(1)
+ mockedAuthenticationHandler.EXPECT().GetAuthorizationValue(proxyUrl, "").Times(1).Return("Negotiate TlRMTVNTUAACAAAADgAOADgAAAAVgoni", nil)
+ mockedAuthenticationHandler.EXPECT().Update(gomock.Any()).Times(1).Return("123456789", nil)
+ mockedAuthenticationHandler.EXPECT().GetAuthorizationValue(proxyUrl, "123456789").Times(1).Return("", fmt.Errorf("Some library error!"))
+ mockedAuthenticationHandler.EXPECT().Cancel().Times(1)
+ mockedAuthenticationHandler.EXPECT().Close().Times(1)
+ mockedAuthenticationHandler.EXPECT().SetLogger(testLogger).Times(1)
+
+ // prepare test environment
+ authenticator := NewProxyAuthenticator(mechanism, nil, testLogger)
+ authenticator.CreateHandler = func(mechanism AuthenticationMechanism) AuthenticationHandlerInterface {
+ return mockedAuthenticationHandler
+ }
+
+ // run method under test
+ connection, err := authenticator.connectToProxy(context, proxyUrl, target, createConnectionFunc)
+
+ // check expectations
+ assert.NotNil(t, err)
+ assert.Nil(t, connection)
+}
+
+func Test_ProxyAuthenticator_connectToProxy_success(t *testing.T) {
+ mockServer, masterConnection := createMockServer()
+
+ // prepare test data
+ mockServer.ResponseList = append(mockServer.ResponseList, &http.Response{StatusCode: 407, Header: http.Header{ProxyAuthenticateKey: []string{"Negotiate 123456789"}}})
+ mockServer.ResponseList = append(mockServer.ResponseList, &http.Response{StatusCode: 407, Header: http.Header{ProxyAuthenticateKey: []string{"Negotiate 10111213141516171819"}}})
+ mockServer.ResponseList = append(mockServer.ResponseList, &http.Response{StatusCode: 200})
+
+ context := context.Background()
+ mechanism := Negotiate
+ proxyUrl := &url.URL{Opaque: "http://127.0.0.1:3128"}
+ target := "snyk.io:443"
+ createConnectionFunc := func() (net.Conn, error) { return masterConnection, nil }
+
+ ctrl := gomock.NewController(t)
+ mockedAuthenticationHandler := NewMockAuthenticationHandlerInterface(ctrl)
+
+ mockedAuthenticationHandler.EXPECT().IsStopped().Return(false).Times(len(mockServer.ResponseList))
+ mockedAuthenticationHandler.EXPECT().IsStopped().Return(true).Times(1)
+ mockedAuthenticationHandler.EXPECT().GetAuthorizationValue(proxyUrl, "").Times(1).Return("Negotiate TlRMTVNTUAACAAAADgAOADgAAAAVgoni", nil)
+ mockedAuthenticationHandler.EXPECT().Update(gomock.Any()).Times(1).Return("123456789", nil)
+ mockedAuthenticationHandler.EXPECT().GetAuthorizationValue(proxyUrl, "123456789").Times(1).Return("Negotiate TlRMTVNTUAACAAAADgAOADgAAAAVgoni/Fy06M1ZvVQAAAAAAAAAAKQApABGAAAA", nil)
+ mockedAuthenticationHandler.EXPECT().Update(gomock.Any()).Times(1).Return("10111213141516171819", nil)
+ mockedAuthenticationHandler.EXPECT().GetAuthorizationValue(proxyUrl, "10111213141516171819").Times(1).Return("Negotiate TlRMTVNTUAACAAAADgAOADgAAAAVgoni/Fy06M1ZvVQAAAAAAAAAAKQApABGAAAABgEAAAAAAA9IAEEATQBNAEUAUgAyAAIADgBIAEEATQBNAEUAUgAyAAEAHABFAEMAMgBBAE0AQQBaAC0AVgBVAEgATABOAFEABAAeAGgAYQBtAG0AZQByADIALgBzAG4AeQBrAC4AaQBvAAMAPABlAGMAMgBhAG0AYQB6AC0AdgB1AGgAbABuAHEALgBoAGEAbQBtAGUAcgAyAC4AcwBuAHkAawAuAGkAbwAHAAgAli6vQTKY2AEAAAAA", nil)
+ mockedAuthenticationHandler.EXPECT().Succesful().Times(1)
+ mockedAuthenticationHandler.EXPECT().Close().Times(1)
+ mockedAuthenticationHandler.EXPECT().SetLogger(testLogger).Times(1)
+
+ // prepare test environment
+ authenticator := NewProxyAuthenticator(mechanism, nil, testLogger)
+ authenticator.CreateHandler = func(mechanism AuthenticationMechanism) AuthenticationHandlerInterface {
+ return mockedAuthenticationHandler
+ }
+
+ // run method under test
+ connection, err := authenticator.connectToProxy(context, proxyUrl, target, createConnectionFunc)
+
+ // check expectations
+ assert.Nil(t, err)
+ assert.NotNil(t, connection)
+}
+
+func Test_ProxyAuthenticator_DialContext_fail01(t *testing.T) {
+ mechanism := Negotiate
+ proxyUrl, _ := url.Parse("http://localhost:12") // used random not existing port
+ target := "snyk.io:443"
+ proxyFunc := func(*http.Request) (*url.URL, error) { return proxyUrl, nil }
+
+ expectations := func(mockedAuthenticationHandler *MockAuthenticationHandlerInterface) {
+ mockedAuthenticationHandler.EXPECT().IsStopped().Return(false).Times(1)
+ mockedAuthenticationHandler.EXPECT().IsStopped().Return(true).Times(1)
+ mockedAuthenticationHandler.EXPECT().GetAuthorizationValue(proxyUrl, "").Times(1).Return("Negotiate TlRMTVNTUAACAAAADgAOADgAAAAVgoni", nil)
+ mockedAuthenticationHandler.EXPECT().Cancel().Times(1)
+ mockedAuthenticationHandler.EXPECT().Close().Times(1)
+ mockedAuthenticationHandler.EXPECT().SetLogger(testLogger).Times(1)
+ }
+
+ helper_DialContext(t, false, expectations, mechanism, proxyFunc, target)
+}
+
+func Test_ProxyAuthenticator_DialContext_fail02(t *testing.T) {
+ mechanism := Negotiate
+ proxyUrl, _ := url.Parse("http://localhost:123")
+ target := "snyk.io:7831" // used random not existing port which will not find an appropriate proxy
+ proxyFunc := func(*http.Request) (*url.URL, error) { return proxyUrl, nil }
+
+ expectations := func(mockedAuthenticationHandler *MockAuthenticationHandlerInterface) {
+ mockedAuthenticationHandler.EXPECT().IsStopped().Return(false).Times(1)
+ mockedAuthenticationHandler.EXPECT().IsStopped().Return(true).Times(1)
+ mockedAuthenticationHandler.EXPECT().GetAuthorizationValue(proxyUrl, "").Times(1).Return("Negotiate TlRMTVNTUAACAAAADgAOADgAAAAVgoni", nil)
+ mockedAuthenticationHandler.EXPECT().Cancel().Times(1)
+ mockedAuthenticationHandler.EXPECT().Close().Times(1)
+ mockedAuthenticationHandler.EXPECT().SetLogger(testLogger).Times(1)
+ }
+
+ helper_DialContext(t, false, expectations, mechanism, proxyFunc, target)
+}
+
+func Test_ProxyAuthenticator_DialContext_fail03(t *testing.T) {
+ mechanism := Negotiate
+ proxyUrl, _ := url.Parse("http://localhost:123")
+ target := "snyk.io:443"
+ proxyFunc := func(*http.Request) (*url.URL, error) { return proxyUrl, fmt.Errorf("Some random error") }
+
+ helper_DialContext(t, false, nil, mechanism, proxyFunc, target)
+}
+
+// Test case: Negotiate enabled, proxy address specified, doing 3 message authentication similar to NTLM (using spnego provider mock)
+func Test_ProxyAuthenticator_DialContext_success(t *testing.T) {
+ mockServer, _ := createMockServer()
+
+ // prepare test data
+ mockServer.ResponseList = append(mockServer.ResponseList, &http.Response{StatusCode: 407, Header: http.Header{ProxyAuthenticateKey: []string{"Negotiate " + NTML_02_CHALLENGE}}})
+ mockServer.ResponseList = append(mockServer.ResponseList, &http.Response{StatusCode: 200})
+
+ mechanism := Negotiate
+ proxyUrl, _ := url.Parse("http://localhost:" + fmt.Sprint(mockServer.Port))
+ target := "snyk.io:443"
+ proxyFunc := func(*http.Request) (*url.URL, error) { return proxyUrl, nil }
+
+ spnegoProviderMock := NewMockSpnegoProvider(gomock.NewController(t))
+ defer spnegoProviderMock.ctrl.Finish()
+ spnegoProviderMock.EXPECT().GetToken(proxyUrl, "").Times(1).Return(NTML_01_INITIALMESSAGE, false, nil)
+ spnegoProviderMock.EXPECT().GetToken(proxyUrl, NTML_02_CHALLENGE).Times(1).Return(NTML_03_RESPONSE, true, nil)
+ spnegoProviderMock.EXPECT().SetLogger(testLogger)
+ spnegoProviderMock.EXPECT().Close().Times(1)
+
+ // prepare test environment
+ authenticator := NewProxyAuthenticator(mechanism, proxyFunc, testLogger)
+ authenticator.CreateHandler = func(mechanism AuthenticationMechanism) AuthenticationHandlerInterface {
+ authHandler := NewHandler(mechanism)
+ authHandler.SetSpnegoProvider(spnegoProviderMock)
+ return authHandler
+ }
+
+ // run method under test
+ connection, err := authenticator.DialContext(context.Background(), "tcp", target)
+
+ // check expectations
+ assert.Nil(t, err)
+ assert.NotNil(t, connection)
+}
+
+// Test case: AnyAuth enabled, proxy address specified, doing 3 message authentication similar to NTLM (using spnego provider mock)
+func Test_ProxyAuthenticator_DialContext_success02(t *testing.T) {
+ mockServer, _ := createMockServer()
+
+ // prepare test data
+ mockServer.ResponseList = append(mockServer.ResponseList, &http.Response{StatusCode: 407, Header: http.Header{ProxyAuthenticateKey: []string{"NTLM", "Negotiate"}}})
+ mockServer.ResponseList = append(mockServer.ResponseList, &http.Response{StatusCode: 407, Header: http.Header{ProxyAuthenticateKey: []string{"Negotiate " + NTML_02_CHALLENGE}}})
+ mockServer.ResponseList = append(mockServer.ResponseList, &http.Response{StatusCode: 200})
+
+ mechanism := AnyAuth
+ proxyUrl, _ := url.Parse("http://localhost:" + fmt.Sprint(mockServer.Port))
+ target := "snyk.io:443"
+ proxyFunc := func(*http.Request) (*url.URL, error) { return proxyUrl, nil }
+
+ spnegoProviderMock := NewMockSpnegoProvider(gomock.NewController(t))
+ defer spnegoProviderMock.ctrl.Finish()
+ spnegoProviderMock.EXPECT().GetToken(proxyUrl, "").Times(1).Return(NTML_01_INITIALMESSAGE, false, nil)
+ spnegoProviderMock.EXPECT().GetToken(proxyUrl, NTML_02_CHALLENGE).Times(1).Return(NTML_03_RESPONSE, true, nil)
+ spnegoProviderMock.EXPECT().SetLogger(testLogger)
+ spnegoProviderMock.EXPECT().Close().Times(1)
+
+ // prepare test environment
+ authenticator := NewProxyAuthenticator(mechanism, proxyFunc, testLogger)
+ authenticator.CreateHandler = func(mechanism AuthenticationMechanism) AuthenticationHandlerInterface {
+ authHandler := NewHandler(mechanism)
+ authHandler.SetSpnegoProvider(spnegoProviderMock)
+ return authHandler
+ }
+
+ // run method under test
+ connection, err := authenticator.DialContext(context.Background(), "tcp", target)
+
+ // check expectations
+ assert.Nil(t, err)
+ assert.NotNil(t, connection)
+}
+
+// Test case: AnyAuth enabled, NO proxy address specified, normal connection will be established (using spnego provider mock)
+func Test_ProxyAuthenticator_DialContext_success03(t *testing.T) {
+ mechanism := AnyAuth
+ target := "snyk.io:443"
+ var proxyFunc func(*http.Request) (*url.URL, error) // no proxy configured should just return the tcp connection
+
+ helper_DialContext(t, true, nil, mechanism, proxyFunc, target)
+}
+
+// Test case: AnyAuth enabled, proxy address specified, proxy doesn't require authentication (using spnego provider mock)
+func Test_ProxyAuthenticator_DialContext_success04(t *testing.T) {
+ mockServer, _ := createMockServer()
+
+ // prepare test data
+ mockServer.ResponseList = append(mockServer.ResponseList, &http.Response{StatusCode: 200})
+
+ mechanism := AnyAuth
+ proxyUrl, _ := url.Parse("http://localhost:" + fmt.Sprint(mockServer.Port))
+ target := "snyk.io:443"
+ proxyFunc := func(*http.Request) (*url.URL, error) { return proxyUrl, nil }
+
+ spnegoProviderMock := NewMockSpnegoProvider(gomock.NewController(t))
+ defer spnegoProviderMock.ctrl.Finish()
+ spnegoProviderMock.EXPECT().SetLogger(testLogger)
+ spnegoProviderMock.EXPECT().Close().Times(1)
+
+ // prepare test environment
+ authenticator := NewProxyAuthenticator(mechanism, proxyFunc, testLogger)
+ authenticator.CreateHandler = func(mechanism AuthenticationMechanism) AuthenticationHandlerInterface {
+ authHandler := NewHandler(mechanism)
+ authHandler.SetSpnegoProvider(spnegoProviderMock)
+ return authHandler
+ }
+
+ // run method under test
+ connection, err := authenticator.DialContext(context.Background(), "tcp", target)
+
+ // check expectations
+ assert.Nil(t, err)
+ assert.NotNil(t, connection)
+}
diff --git a/cliv2/internal/httpauth/spnego.go b/cliv2/internal/httpauth/spnego.go
new file mode 100644
index 0000000000..300c669a01
--- /dev/null
+++ b/cliv2/internal/httpauth/spnego.go
@@ -0,0 +1,61 @@
+package httpauth
+
+import (
+ "encoding/asn1"
+ "encoding/base64"
+ "fmt"
+ "log"
+ "net/url"
+ "reflect"
+ "strings"
+
+ "github.com/jcmturner/gokrb5/v8/gssapi"
+)
+
+const (
+ NTLMSSP_NAME string = "NTLMSSP"
+ SPNEGO_NAME string = string(gssapi.OIDSPNEGO)
+)
+
+type SpnegoProvider interface {
+ GetToken(url *url.URL, responseToken string) (string, bool, error)
+ Close() error
+ SetLogger(logger *log.Logger)
+}
+
+func IsNTLMToken(token string) bool {
+ isNtlm := strings.Contains(token, "TlRMTVNTU")
+ return isNtlm
+}
+
+func GetMechanismsFromHttpFieldValue(token string) ([]string, error) {
+ var result []string
+ var err error
+
+ if strings.Contains(token, " ") {
+ temp := strings.Split(token, " ")
+ token = temp[1]
+ }
+
+ if IsNTLMToken(token) {
+ result = append(result, NTLMSSP_NAME)
+ } else {
+ var decodedToken []byte
+ decodedToken, err = base64.StdEncoding.DecodeString(token)
+ if err == nil {
+ var oid asn1.ObjectIdentifier
+ _, err = asn1.UnmarshalWithParams(decodedToken, &oid, fmt.Sprintf("application,explicit,tag:%v", 0))
+
+ if reflect.DeepEqual(oid, asn1.ObjectIdentifier(gssapi.OIDSPNEGO.OID())) {
+ result = append(result, SPNEGO_NAME)
+ }
+
+ }
+
+ if err != nil {
+ fmt.Println(err)
+ }
+ }
+
+ return result, err
+}
diff --git a/cliv2/internal/httpauth/spnego_generated_mock.go b/cliv2/internal/httpauth/spnego_generated_mock.go
new file mode 100644
index 0000000000..d098b36d67
--- /dev/null
+++ b/cliv2/internal/httpauth/spnego_generated_mock.go
@@ -0,0 +1,78 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: spnego.go
+
+// Package httpauth is a generated GoMock package.
+package httpauth
+
+import (
+ log "log"
+ url "net/url"
+ reflect "reflect"
+
+ gomock "github.com/golang/mock/gomock"
+)
+
+// MockSpnegoProvider is a mock of SpnegoProvider interface.
+type MockSpnegoProvider struct {
+ ctrl *gomock.Controller
+ recorder *MockSpnegoProviderMockRecorder
+}
+
+// MockSpnegoProviderMockRecorder is the mock recorder for MockSpnegoProvider.
+type MockSpnegoProviderMockRecorder struct {
+ mock *MockSpnegoProvider
+}
+
+// NewMockSpnegoProvider creates a new mock instance.
+func NewMockSpnegoProvider(ctrl *gomock.Controller) *MockSpnegoProvider {
+ mock := &MockSpnegoProvider{ctrl: ctrl}
+ mock.recorder = &MockSpnegoProviderMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockSpnegoProvider) EXPECT() *MockSpnegoProviderMockRecorder {
+ return m.recorder
+}
+
+// Close mocks base method.
+func (m *MockSpnegoProvider) Close() error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Close")
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// Close indicates an expected call of Close.
+func (mr *MockSpnegoProviderMockRecorder) Close() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockSpnegoProvider)(nil).Close))
+}
+
+// GetToken mocks base method.
+func (m *MockSpnegoProvider) GetToken(url *url.URL, responseToken string) (string, bool, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "GetToken", url, responseToken)
+ ret0, _ := ret[0].(string)
+ ret1, _ := ret[1].(bool)
+ ret2, _ := ret[2].(error)
+ return ret0, ret1, ret2
+}
+
+// GetToken indicates an expected call of GetToken.
+func (mr *MockSpnegoProviderMockRecorder) GetToken(url, responseToken interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetToken", reflect.TypeOf((*MockSpnegoProvider)(nil).GetToken), url, responseToken)
+}
+
+// SetLogger mocks base method.
+func (m *MockSpnegoProvider) SetLogger(logger *log.Logger) {
+ m.ctrl.T.Helper()
+ m.ctrl.Call(m, "SetLogger", logger)
+}
+
+// SetLogger indicates an expected call of SetLogger.
+func (mr *MockSpnegoProviderMockRecorder) SetLogger(logger interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLogger", reflect.TypeOf((*MockSpnegoProvider)(nil).SetLogger), logger)
+}
diff --git a/cliv2/internal/httpauth/spnego_nonwindows.go b/cliv2/internal/httpauth/spnego_nonwindows.go
new file mode 100644
index 0000000000..406986c43a
--- /dev/null
+++ b/cliv2/internal/httpauth/spnego_nonwindows.go
@@ -0,0 +1,121 @@
+//go:build linux || darwin
+// +build linux darwin
+
+package httpauth
+
+import (
+ "encoding/base64"
+ "fmt"
+ "io"
+ "log"
+ "net/url"
+ "os"
+ "os/user"
+ "strings"
+
+ "github.com/jcmturner/gokrb5/v8/client"
+ "github.com/jcmturner/gokrb5/v8/config"
+ "github.com/jcmturner/gokrb5/v8/credentials"
+ "github.com/jcmturner/gokrb5/v8/krberror"
+ "github.com/jcmturner/gokrb5/v8/spnego"
+)
+
+type NonwindowsSpnegoProvider struct {
+ logger *log.Logger
+ configPath string
+ cachePath string
+ config *config.Config
+ client *spnego.SPNEGO
+}
+
+func SpnegoProviderInstance() SpnegoProvider {
+ s := &NonwindowsSpnegoProvider{}
+ s.initDefaultConfiguration()
+ return s
+}
+
+func (s *NonwindowsSpnegoProvider) initDefaultConfiguration() {
+ // logger
+ s.logger = log.New(io.Discard, "", 0)
+
+ // configuration location
+ s.configPath = os.Getenv("KRB5_CONFIG")
+ if _, err := os.Stat(s.configPath); os.IsNotExist(err) {
+ s.configPath = "/etc/krb5.conf"
+ }
+
+ // cache location
+ user, err := user.Current()
+ if err == nil {
+ s.cachePath = "/tmp/krb5cc_" + user.Uid
+ cacheName := os.Getenv("KRB5CCNAME")
+ if strings.HasPrefix(cacheName, "FILE:") {
+ s.cachePath = strings.SplitN(cacheName, ":", 2)[1]
+ }
+ } else {
+ s.logger.Println("Failed to get current user! ", err)
+ }
+
+}
+
+func (s *NonwindowsSpnegoProvider) Close() error {
+ return nil
+}
+
+func (s *NonwindowsSpnegoProvider) SetLogger(logger *log.Logger) {
+ s.logger = logger
+}
+
+func (s *NonwindowsSpnegoProvider) init(url *url.URL) ([]byte, error) {
+ hostname := url.Hostname()
+ spn := "HTTP/" + hostname
+ token := []byte{}
+ var err error
+
+ s.logger.Printf("krb5 configuration file: %s", s.configPath)
+ s.config, err = config.Load(s.configPath)
+ if err != nil {
+ return token, err
+ }
+
+ s.logger.Printf("krb5 cache file: %s", s.cachePath)
+ cache, err := credentials.LoadCCache(s.cachePath)
+ if err != nil {
+ return token, err
+ }
+
+ krb5client, err := client.NewFromCCache(cache, s.config, client.DisablePAFXFAST(true))
+
+ s.client = spnego.SPNEGOClient(krb5client, spn)
+
+ tokenObject, err := s.client.InitSecContext()
+ if err != nil {
+ return token, fmt.Errorf("could not initialize context: %v", err)
+ }
+
+ token, err = tokenObject.Marshal()
+ if err != nil {
+ return token, krberror.Errorf(err, krberror.EncodingError, "could not marshal SPNEGO")
+ }
+
+ return token, err
+}
+
+func (s *NonwindowsSpnegoProvider) GetToken(url *url.URL, responseToken string) (string, bool, error) {
+ var err error
+ var token []byte
+ var encodedToken string
+ done := false
+
+ if s.client == nil {
+ token, err = s.init(url)
+ } else {
+ err = fmt.Errorf("NonwindowsSpnegoProvider.update() is not yet implemented. Only preemptive authentication is supported!")
+ }
+
+ if len(token) > 0 {
+ encodedToken = base64.StdEncoding.EncodeToString(token)
+ }
+
+ return encodedToken, done, err
+}
diff --git a/cliv2/internal/httpauth/spnego_test.go b/cliv2/internal/httpauth/spnego_test.go
new file mode 100644
index 0000000000..0a42c077cf
--- /dev/null
+++ b/cliv2/internal/httpauth/spnego_test.go
@@ -0,0 +1,169 @@
+package httpauth
+
+import (
+ "fmt"
+ "net/url"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+//go:generate $GOPATH/bin/mockgen -source=spnego.go -destination ./spnego_generated_mock.go -package httpauth -self_package github.com/snyk/cli/cliv2/internal/httpauth
+
+func fixturePath() string {
+ path, _ := filepath.Abs(filepath.Join("..", "..", "internal", "httpauth", "test", "fixtures"))
+ return path
+}
+
+func dockerComposeFile() string {
+ path := filepath.Join(fixturePath(), "squid_environment", "docker-compose.yml")
+ return path
+}
+
+func scriptsPath() string {
+ path := filepath.Join(fixturePath(), "squid_environment", "scripts")
+ return path
+}
+
+func hasDockerInstalled() bool {
+ result := false
+ cmd := exec.Command("docker-compose", "--version")
+ err := cmd.Run()
+ if err == nil {
+ result = true
+ }
+ return result
+}
+
+func runTestUsingExternalAuthLibrary() bool {
+ result := false
+ os := runtime.GOOS
+ if os == "windows" {
+ result = true
+ } else {
+ result = hasDockerInstalled()
+ }
+ return result
+}
+
+func runDockerCompose(arg ...string) {
+ cmd := exec.Command("docker-compose", arg...)
+ cmd.Env = os.Environ()
+ cmd.Env = append(cmd.Env, "HTTP_PROXY_PORT=3128")
+ cmd.Env = append(cmd.Env, "PROXY_HOSTNAME=proxy.snyk.local")
+ cmd.Env = append(cmd.Env, "CONTAINER_NAME=spnego_test")
+ cmd.Env = append(cmd.Env, "SCRIPTS_PATH="+scriptsPath())
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ run := func() {
+ err := cmd.Run()
+ if err != nil {
+ fmt.Println(err)
+ }
+ }
+ run()
+}
+
+func startProxyEnvironment() {
+ stopProxyEnvironment()
+ runDockerCompose("--file", dockerComposeFile(), "up", "--build", "--detach")
+}
+
+func stopProxyEnvironment() {
+ runDockerCompose("--file", dockerComposeFile(), "down")
+}
+
+func waitForFile(filename string, timeout time.Duration) {
+ start := time.Now()
+ for {
+ _, err := os.Stat(filename)
+ if !os.IsNotExist(err) {
+ break
+ }
+
+ if time.Since(start) >= timeout {
+ fmt.Println("waitForFile() - timeout", filename)
+ break
+ }
+
+ time.Sleep(time.Second)
+
+ }
+}
+
+func Test_IsNTLMToken(t *testing.T) {
+ ntlmToken := "TlRMTVNTUAACAAAADgAOADgAAAAVgoni/Fy06M1ZvVQAAAAAAAAAAKQApABGAAAABgEAAAAAAA9IAEEATQBNAEUAUgAyAAIADgBIAEEATQBNAEUAUgAyAAEAHABFAEMAMgBBAE0AQQBaAC0AVgBVAEgATABOAFEABAAeAGgAYQBtAG0AZQByADIALgBzAG4AeQBrAC4AaQBvAAMAPABlAGMAMgBhAG0AYQB6AC0AdgB1AGgAbABuAHEALgBoAGEAbQBtAGUAcgAyAC4AcwBuAHkAawAuAGkAbwAHAAgAli6vQTKY2AEAAAAA"
+ assert.True(t, IsNTLMToken(ntlmToken))
+
+ nonNtlmToken := "YIIIOgYGKwYBBQUCoIIILjCCCCqgMDAuBgkqhkiC9xIBAgIGCSqGSIb3EgECAgYKKwYBBAGCNwICHgYKKwYBBAGCNwICCqKCB/QEggfwYIIH7AYJKoZIhvcSAQICAQBuggfbMIIH16ADAgEFoQMCAQ6iBwMFACAAAACjggXwYYIF7DCCBeigAwIBBaERGw9IQU1NRVIyLlNOWUsuSU+iMTAvoAMCAQKhKDAmGwRIVFRQGx5FQzJBTUFaLXZ1SGxOUS5oYW1tZXIyLnNueWsuaW+jggWZMIIFlaADAgESoQMCARGiggWHBIIFg+D23RW9siubTtOIG40J97PP44yP8iJ77Sl7k/5D4m+DMENFD1yfxY6JbJYHA34EF0TbE4/Dz+OTSWOS8vPACYiunkZrEbtHa6gx2K+Lp4ogo0UVmPfRjkokvFZNgeRfDYhCg3WPXffuQOpcT209gz/StKRQtaE7O5TyPIbWpzXjtxyuIWisLM5G5OgR4nOPJ+8VGwA6DEP/xOfzOheqmGELv+/14uY1SeI2EiqZDpQVl/ZP0p6MkxyR3I0o1xn7u2jgQfMnlN1hFcTwi6lfh1XoPT+3ZeIOc8J/DS74vX2jElpl6gqBC4vB5FrcB7do4L2XI6bUfgiNT9uhpgcb0kOu4D2sPQaI3/TDPByknJCPK6YkVlO/l1cXJ158uebfLWWUgbK4XJjLU9K7tk2wqtNvdkmEXkJOcbHFvo+4Bda1OhcgFvpjuCSscSLjnK2PwZv51qlA3ozK93v/1wjAWJrgGTu9KB5MraZzWZF8XZz+yrWP710WZ35ohHoR2tFK8GdukoH1ZO0jts0VT/W0zk+SyatJ3TAev/HU6R7sTKF/x8pSTlzOa9La7gyuHKOUxUlkt23YJvZKQgdEu/585iGy0o0w3MwVWb3CsLxr2URjc9Z4r693YNBdQ2xPjDdL2pzvaMnswHr97mOR1fnemuIDkL26wKKjKzPdhNqB0aed1z/saYou/5nkDAfGjMzI4Pw58zUHgOiOzwuAZF5bjKUMn5kQcarRNGVuZFJ6jB89r1JuHt/HUQ1Ie5ZsYIq1oPGyeCZy40EwOHpuy9Ks88BnzNfi7kMgb8FKgZL1nGzR4rqucQn6OGjdwFXk2624b/JYztPlsmRrzsHXp6lampUxxDQN5KqYPzAawC6/G5SdqFw3Wf8rDW9b+claN3dzEj8kAHHlqrJx4By5X+HTwspHtZNCCOwrJp7rcXw1+5sLdx/XAYI3rSFaIAO9VlZe5E1uml/uToawlWZ5phtO2H5GwxadtG0vIw8MdFpy2UiEocYZuE1aCHYCLjXZokl4tsqIeXndsEgCWq5IpZ2PLQr14XUQHjOu8y2tPpw3sklxBIgXxt1sSpjOWMLROPXkJRI2QriVuhoOoGgIyi4wKbTC0f0tBDjv2AGKYmOZpaHW8hGyJ4YzTkxq+eGL5aLy457Ag6JtAIkUW9RUiFIMQoIKdq0TP/875qcE3xFjoRrS46e4P38IfXEmpRmJih1f4/R0nPZ3sgisgnK2LthxzUuewzBTxghadmLtj0QopOv/uCaFhwObxuE7hzVjriQnIl1rw/rpL+4aNZpinexGXtW7lawi/M4ANJPAusDxJ1A2AeoWtcf7AzLACHK/3RUAXRfrhdzoEh1Lt+tC2LM0qUdASxJ+UgZoABqg49feQ1TfByHxE/H0aJOEL81TDkqfVr59LMTtxPiYsMWm2uKXQIxiyydGYLKQhPm3GoCW0JZfgjm/tmsKG0dNPgruVjJz52zvgy5+5rYqmTBUeJRnT3zigT7ZVbmiHqeQJndMcMvLcrl3Z7o7OsMuynCyNMccfhtLlMQgwPhMGuRDZsNDwnduJk4jzg/qgP1fLvCTS22kegaF/+tIM3A4cyh7SJugkP7jN/P2OzFZV11XG8LO0GCvLVq3XYGU3v20lSpIEGJfcY6utJ4e+ButmDj7tw4IxFJxkoM4ciee/rLzzQD8BaOuqvofcTwQgWBEuHYPs5m4rHFE7xwLLiIIwlOD6NF7LSSztC/y9eV+Jdd9CuyuqqqOGLLw+E0jW9eXm1RukOqwue7r3109+USkF+ebA3SiTwGZwwXVLb3KptA3KCygZZAlfAsvK3UO96tXCYxEk6M/QIMtyt5xXwrrKtNW3HF3okqGAWOlhyUPq1W3wHS1zeMow+GkggHMMIIByKADAgESooIBvwSCAbuire4ijXcXCqxnXhwOxHBHAML3kvrdUnrAm2egVIRFxo22U5/yE4fCU8nsDty14GyXtDyZmabSXFU3WMSruXs7vkldQiCj0n7+jwFRKbtgBh6+bk0JP+sMK8vIHe1/TCNjiSpyJY2Xj69qyG4y3uYmr77DR17CEFnDbnlPFwrVC2zTWUDfDx/p98/d3Z7cQ3JnfdX8SRmM2zJv9pJ1hWNe73moYY1940TJmsYRq3eFr+BxfvvQabDoc1BC7n+1weNUxhZf9SVUl8WFElICazqhwDic//wfuc9qV3oaypIKhykmThXJddsqlckMbcKQjEO8CC1nnUnbU4WeQO0+c8sQrzcHCZQqdGv+5N4XBQ+AAv4p0K+o35OLqg+P+rsxC01ug4m2Q5AwL65dz3Wpa4j8MBTFTIo6RI3Hr/i2Li3MsPkxJ78eFWnoHgaDz+ULQ2HxkvQDTaAlQs4EN86FxlLJ1CYyN+aYtbiiBCO7O7AgwzLN3YPoF9kkszeKFqamsy2wZ6j5Lqte/04piZkY18EZ6z5XkrT+enntabIyi9qkgvHh+VDmV7NXnzjrJ/6GLwNQuNx+Vq7FJwR6lw=="
+ assert.False(t, IsNTLMToken(nonNtlmToken))
+}
+
+func Test_GetMechanismsFromHttpFieldValue_NTLM(t *testing.T) {
+ ntlmToken := "TlRMTVNTUAACAAAADgAOADgAAAAVgoni/Fy06M1ZvVQAAAAAAAAAAKQApABGAAAABgEAAAAAAA9IAEEATQBNAEUAUgAyAAIADgBIAEEATQBNAEUAUgAyAAEAHABFAEMAMgBBAE0AQQBaAC0AVgBVAEgATABOAFEABAAeAGgAYQBtAG0AZQByADIALgBzAG4AeQBrAC4AaQBvAAMAPABlAGMAMgBhAG0AYQB6AC0AdgB1AGgAbABuAHEALgBoAGEAbQBtAGUAcgAyAC4AcwBuAHkAawAuAGkAbwAHAAgAli6vQTKY2AEAAAAA"
+ expected := NTLMSSP_NAME
+ actual, err := GetMechanismsFromHttpFieldValue(ntlmToken)
+ assert.Nil(t, err)
+ assert.Equal(t, actual[0], expected)
+}
+
+func Test_GetMechanismsFromHttpFieldValue_NTLM_Negotiate(t *testing.T) {
+ ntlmToken := "Negotiate TlRMTVNTUAACAAAADgAOADgAAAAVgoni/Fy06M1ZvVQAAAAAAAAAAKQApABGAAAABgEAAAAAAA9IAEEATQBNAEUAUgAyAAIADgBIAEEATQBNAEUAUgAyAAEAHABFAEMAMgBBAE0AQQBaAC0AVgBVAEgATABOAFEABAAeAGgAYQBtAG0AZQByADIALgBzAG4AeQBrAC4AaQBvAAMAPABlAGMAMgBhAG0AYQB6AC0AdgB1AGgAbABuAHEALgBoAGEAbQBtAGUAcgAyAC4AcwBuAHkAawAuAGkAbwAHAAgAli6vQTKY2AEAAAAA"
+ expected := NTLMSSP_NAME
+ actual, err := GetMechanismsFromHttpFieldValue(ntlmToken)
+ assert.Nil(t, err)
+ assert.Contains(t, actual[0], expected)
+}
+
+func Test_GetMechanismsFromHttpFieldValue_Multiple(t *testing.T) {
+ ntlmToken := "YIIIOgYGKwYBBQUCoIIILjCCCCqgMDAuBgkqhkiC9xIBAgIGCSqGSIb3EgECAgYKKwYBBAGCNwICHgYKKwYBBAGCNwICCqKCB/QEggfwYIIH7AYJKoZIhvcSAQICAQBuggfbMIIH16ADAgEFoQMCAQ6iBwMFACAAAACjggXwYYIF7DCCBeigAwIBBaERGw9IQU1NRVIyLlNOWUsuSU+iMTAvoAMCAQKhKDAmGwRIVFRQGx5FQzJBTUFaLXZ1SGxOUS5oYW1tZXIyLnNueWsuaW+jggWZMIIFlaADAgESoQMCARGiggWHBIIFg+D23RW9siubTtOIG40J97PP44yP8iJ77Sl7k/5D4m+DMENFD1yfxY6JbJYHA34EF0TbE4/Dz+OTSWOS8vPACYiunkZrEbtHa6gx2K+Lp4ogo0UVmPfRjkokvFZNgeRfDYhCg3WPXffuQOpcT209gz/StKRQtaE7O5TyPIbWpzXjtxyuIWisLM5G5OgR4nOPJ+8VGwA6DEP/xOfzOheqmGELv+/14uY1SeI2EiqZDpQVl/ZP0p6MkxyR3I0o1xn7u2jgQfMnlN1hFcTwi6lfh1XoPT+3ZeIOc8J/DS74vX2jElpl6gqBC4vB5FrcB7do4L2XI6bUfgiNT9uhpgcb0kOu4D2sPQaI3/TDPByknJCPK6YkVlO/l1cXJ158uebfLWWUgbK4XJjLU9K7tk2wqtNvdkmEXkJOcbHFvo+4Bda1OhcgFvpjuCSscSLjnK2PwZv51qlA3ozK93v/1wjAWJrgGTu9KB5MraZzWZF8XZz+yrWP710WZ35ohHoR2tFK8GdukoH1ZO0jts0VT/W0zk+SyatJ3TAev/HU6R7sTKF/x8pSTlzOa9La7gyuHKOUxUlkt23YJvZKQgdEu/585iGy0o0w3MwVWb3CsLxr2URjc9Z4r693YNBdQ2xPjDdL2pzvaMnswHr97mOR1fnemuIDkL26wKKjKzPdhNqB0aed1z/saYou/5nkDAfGjMzI4Pw58zUHgOiOzwuAZF5bjKUMn5kQcarRNGVuZFJ6jB89r1JuHt/HUQ1Ie5ZsYIq1oPGyeCZy40EwOHpuy9Ks88BnzNfi7kMgb8FKgZL1nGzR4rqucQn6OGjdwFXk2624b/JYztPlsmRrzsHXp6lampUxxDQN5KqYPzAawC6/G5SdqFw3Wf8rDW9b+claN3dzEj8kAHHlqrJx4By5X+HTwspHtZNCCOwrJp7rcXw1+5sLdx/XAYI3rSFaIAO9VlZe5E1uml/uToawlWZ5phtO2H5GwxadtG0vIw8MdFpy2UiEocYZuE1aCHYCLjXZokl4tsqIeXndsEgCWq5IpZ2PLQr14XUQHjOu8y2tPpw3sklxBIgXxt1sSpjOWMLROPXkJRI2QriVuhoOoGgIyi4wKbTC0f0tBDjv2AGKYmOZpaHW8hGyJ4YzTkxq+eGL5aLy457Ag6JtAIkUW9RUiFIMQoIKdq0TP/875qcE3xFjoRrS46e4P38IfXEmpRmJih1f4/R0nPZ3sgisgnK2LthxzUuewzBTxghadmLtj0QopOv/uCaFhwObxuE7hzVjriQnIl1rw/rpL+4aNZpinexGXtW7lawi/M4ANJPAusDxJ1A2AeoWtcf7AzLACHK/3RUAXRfrhdzoEh1Lt+tC2LM0qUdASxJ+UgZoABqg49feQ1TfByHxE/H0aJOEL81TDkqfVr59LMTtxPiYsMWm2uKXQIxiyydGYLKQhPm3GoCW0JZfgjm/tmsKG0dNPgruVjJz52zvgy5+5rYqmTBUeJRnT3zigT7ZVbmiHqeQJndMcMvLcrl3Z7o7OsMuynCyNMccfhtLlMQgwPhMGuRDZsNDwnduJk4jzg/qgP1fLvCTS22kegaF/+tIM3A4cyh7SJugkP7jN/P2OzFZV11XG8LO0GCvLVq3XYGU3v20lSpIEGJfcY6utJ4e+ButmDj7tw4IxFJxkoM4ciee/rLzzQD8BaOuqvofcTwQgWBEuHYPs5m4rHFE7xwLLiIIwlOD6NF7LSSztC/y9eV+Jdd9CuyuqqqOGLLw+E0jW9eXm1RukOqwue7r3109+USkF+ebA3SiTwGZwwXVLb3KptA3KCygZZAlfAsvK3UO96tXCYxEk6M/QIMtyt5xXwrrKtNW3HF3okqGAWOlhyUPq1W3wHS1zeMow+GkggHMMIIByKADAgESooIBvwSCAbuire4ijXcXCqxnXhwOxHBHAML3kvrdUnrAm2egVIRFxo22U5/yE4fCU8nsDty14GyXtDyZmabSXFU3WMSruXs7vkldQiCj0n7+jwFRKbtgBh6+bk0JP+sMK8vIHe1/TCNjiSpyJY2Xj69qyG4y3uYmr77DR17CEFnDbnlPFwrVC2zTWUDfDx/p98/d3Z7cQ3JnfdX8SRmM2zJv9pJ1hWNe73moYY1940TJmsYRq3eFr+BxfvvQabDoc1BC7n+1weNUxhZf9SVUl8WFElICazqhwDic//wfuc9qV3oaypIKhykmThXJddsqlckMbcKQjEO8CC1nnUnbU4WeQO0+c8sQrzcHCZQqdGv+5N4XBQ+AAv4p0K+o35OLqg+P+rsxC01ug4m2Q5AwL65dz3Wpa4j8MBTFTIo6RI3Hr/i2Li3MsPkxJ78eFWnoHgaDz+ULQ2HxkvQDTaAlQs4EN86FxlLJ1CYyN+aYtbiiBCO7O7AgwzLN3YPoF9kkszeKFqamsy2wZ6j5Lqte/04piZkY18EZ6z5XkrT+enntabIyi9qkgvHh+VDmV7NXnzjrJ/6GLwNQuNx+Vq7FJwR6lw=="
+ expected := SPNEGO_NAME
+ actual, err := GetMechanismsFromHttpFieldValue(ntlmToken)
+ assert.Nil(t, err)
+ assert.Contains(t, actual, expected)
+}
+
+// This is a very simplistic test for basic coverage. It is not supposed to recreate a functional setup
+func Test_GetToken(t *testing.T) {
+
+ if !runTestUsingExternalAuthLibrary() {
+ t.Skip("This test can't be run in this environment.")
+ }
+
+ dockerInstalled := hasDockerInstalled()
+ if dockerInstalled {
+ startProxyEnvironment()
+ }
+
+ config := filepath.Join(fixturePath(), "squid_environment", "scripts", "krb5.conf")
+ cache := filepath.Join(fixturePath(), "squid_environment", "scripts", "krb5_cache")
+ waitForFile(config, time.Second*30)
+
+ os.Setenv("KRB5CCNAME", "FILE:"+cache)
+ os.Setenv("KRB5_CONFIG", config)
+
+ url, _ := url.Parse("https://localhost:3128")
+ initialToken := ""
+ expectedDone := false
+
+ provider := SpnegoProviderInstance()
+ actualToken, done, err := provider.GetToken(url, initialToken)
+
+ assert.NotEmpty(t, actualToken)
+ assert.Equal(t, expectedDone, done)
+ assert.Nil(t, err)
+
+ fmt.Println(actualToken)
+
+ if dockerInstalled {
+ stopProxyEnvironment()
+ }
+
+ os.Remove(config)
+ os.Remove(cache)
+}
diff --git a/cliv2/internal/httpauth/spnego_windows.go b/cliv2/internal/httpauth/spnego_windows.go
new file mode 100644
index 0000000000..a938a021b9
--- /dev/null
+++ b/cliv2/internal/httpauth/spnego_windows.go
@@ -0,0 +1,76 @@
+package httpauth
+
+import (
+ "encoding/base64"
+ "github.com/alexbrainman/sspi/negotiate"
+ "log"
+ "net/url"
+)
+
+type WindowsSpnegoProvider struct {
+ clientContext *negotiate.ClientContext
+}
+
+func SpnegoProviderInstance() SpnegoProvider {
+ return &WindowsSpnegoProvider{}
+}
+
+func (s *WindowsSpnegoProvider) init(url *url.URL) ([]byte, error) {
+ hostname := url.Hostname()
+ token := []byte{}
+
+ spn := "HTTP/" + hostname
+
+ cred, err := negotiate.AcquireCurrentUserCredentials()
+ if err != nil {
+ return token, err
+ }
+ defer cred.Release()
+
+ secctx, token, err := negotiate.NewClientContext(cred, spn)
+ if err != nil {
+ return token, err
+ }
+
+ s.clientContext = secctx
+ return token, nil
+}
+
+func (s *WindowsSpnegoProvider) update(responseToken string) ([]byte, bool, error) {
+ var decodedToken []byte
+ var newRequesToken []byte
+ var err error
+ done := false
+
+ decodedToken, err = base64.StdEncoding.DecodeString(responseToken)
+ if err != nil {
+ return newRequesToken, done, err
+ }
+
+ done, newRequesToken, err = s.clientContext.Update(decodedToken)
+
+ return newRequesToken, done, err
+}
+
+func (s *WindowsSpnegoProvider) GetToken(url *url.URL, responseToken string) (string, bool, error) {
+ var err error
+ var token []byte
+ done := false
+
+ if s.clientContext == nil {
+ token, err = s.init(url)
+ } else {
+ token, done, err = s.update(responseToken)
+ }
+
+ encodedToken := base64.StdEncoding.EncodeToString(token)
+ return encodedToken, done, err
+}
+
+func (s *WindowsSpnegoProvider) Close() error {
+ return s.clientContext.Release()
+}
+
+func (s *WindowsSpnegoProvider) SetLogger(logger *log.Logger) {
+ //this implementation currently doesn't require a logger
+}
diff --git a/cliv2/internal/httpauth/test/fixtures/squid_environment/Dockerfile b/cliv2/internal/httpauth/test/fixtures/squid_environment/Dockerfile
new file mode 100644
index 0000000000..6c4baa8bae
--- /dev/null
+++ b/cliv2/internal/httpauth/test/fixtures/squid_environment/Dockerfile
@@ -0,0 +1,11 @@
+FROM debian:bullseye-slim
+
+RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
+ && apt-get -y install --no-install-recommends \
+ build-essential \
+ krb5-user krb5-kdc krb5-admin-server krb5-multidev libkrb5-dev \
+ curl openssl ca-certificates \
+ squid \
+ && mkdir -p /etc/cliv2/bin && touch /etc/cliv2/bin/snyk
+
+ENTRYPOINT [ "/etc/cliv2/scripts/setup.sh" ]
diff --git a/cliv2/internal/httpauth/test/fixtures/squid_environment/docker-compose.yml b/cliv2/internal/httpauth/test/fixtures/squid_environment/docker-compose.yml
new file mode 100644
index 0000000000..2f1961bbb4
--- /dev/null
+++ b/cliv2/internal/httpauth/test/fixtures/squid_environment/docker-compose.yml
@@ -0,0 +1,19 @@
+services:
+ cliv2_kerberos:
+ container_name: ${CONTAINER_NAME}
+ build: .
+ environment:
+ - KERBEROS_USERNAME=administrator
+ - KERBEROS_PASSWORD=password123
+ - HTTP_PROXY_PORT=${HTTP_PROXY_PORT}
+ ports:
+ - "${HTTP_PROXY_PORT}:${HTTP_PROXY_PORT}"
+ - "88:88"
+ expose:
+ - "${HTTP_PROXY_PORT}"
+ - "88/udp"
+ hostname: "${PROXY_HOSTNAME}"
+ extra_hosts:
+ - "host.docker.internal:host-gateway"
+ volumes:
+ - ${SCRIPTS_PATH}:/etc/cliv2/scripts
diff --git a/cliv2/internal/httpauth/test/fixtures/squid_environment/scripts/setup.sh b/cliv2/internal/httpauth/test/fixtures/squid_environment/scripts/setup.sh
new file mode 100755
index 0000000000..e74b52709a
--- /dev/null
+++ b/cliv2/internal/httpauth/test/fixtures/squid_environment/scripts/setup.sh
@@ -0,0 +1,87 @@
+#!/bin/bash
+set -exuo pipefail
+# Inspired by: https://github.com/requests/requests-kerberos/blob/cf45bbac9df696c5dd2d1a2360b6fbee7096207c/ci/setup-kerb.sh
+# ${VAR^^} uppercases all characters.
+
+export KERBEROS_HOSTNAME="$(cat /etc/hostname)"
+export KERBEROS_REALM="$(echo ${KERBEROS_HOSTNAME} | cut -d'.' -f2,3)"
+export KRB5_KTNAME='/etc/krb5.keytab'
+
+echo "Setting up Kerberos config file at /etc/krb5.conf"
+cat > /etc/krb5.conf << EOL
+[libdefaults]
+ default_realm = ${KERBEROS_REALM^^}
+ dns_lookup_realm = false
+ dns_lookup_kdc = false
+[realms]
+ ${KERBEROS_REALM^^} = {
+ kdc = localhost
+ admin_server = localhost
+ }
+[domain_realm]
+ .${KERBEROS_REALM} = ${KERBEROS_REALM^^}
+[logging]
+ kdc = FILE:/var/log/krb5kdc.log
+ admin_server = FILE:/var/log/kadmin.log
+ default = FILE:/var/log/krb5lib.log
+EOL
+cat /etc/krb5.conf
+
+echo "Setting up kerberos ACL configuration at /etc/krb5kdc/kadm5.acl"
+echo -e "*/*@${KERBEROS_REALM^^}\t*" > /etc/krb5kdc/kadm5.acl
+
+echo "Creating KDC database"
+printf "${KERBEROS_PASSWORD}\n${KERBEROS_PASSWORD}" | krb5_newrealm
+
+echo "Creating principals for tests"
+kadmin.local -q "addprinc -pw ${KERBEROS_PASSWORD} ${KERBEROS_USERNAME}"
+
+echo "Adding HTTP principal for Kerberos and create keytab"
+kadmin.local -q "addprinc -randkey HTTP/localhost"
+kadmin.local -q "addprinc -randkey HTTP/${KERBEROS_HOSTNAME}"
+kadmin.local -q "ktadd -k ${KRB5_KTNAME} HTTP/localhost"
+kadmin.local -q "ktadd -k ${KRB5_KTNAME} HTTP/${KERBEROS_HOSTNAME}"
+chmod 777 "${KRB5_KTNAME}"
+
+echo "Restarting Kerberos KDS service"
+service krb5-kdc restart
+
+echo "Configuring Squid HTTP proxy"
+cat > /etc/squid/squid.conf << EOL
+auth_param negotiate program /usr/lib/squid/negotiate_wrapper_auth --kerberos /usr/lib/squid/negotiate_kerberos_auth -d -s HTTP/localhost --ntlm /usr/lib/squid/ntlm_fake_auth
+auth_param negotiate children 10
+auth_param negotiate keep_alive on
+
+auth_param ntlm program /usr/lib/squid/ntlm_fake_auth
+auth_param ntlm children 10
+auth_param ntlm keep_alive off
+
+auth_param basic program /usr/lib/squid/basic_fake_auth
+
+acl auth proxy_auth REQUIRED
+http_port 0.0.0.0:${HTTP_PROXY_PORT}
+http_access deny !auth
+http_access allow auth
+http_access deny all
+EOL
+cat /etc/squid/squid.conf
+service squid restart
+
+sleep 1
+
+echo "Getting ticket for Kerberos user"
+echo -n "${KERBEROS_PASSWORD}" | kinit "${KERBEROS_USERNAME}@${KERBEROS_REALM^^}"
+
+echo "Checking HTTP proxy"
+curl --verbose --head --retry 5 \
+ --proxy "http://localhost:${HTTP_PROXY_PORT}" --proxy-negotiate -u ":" \
+ "https://snyk.io"
+
+cp /tmp/krb5cc_0 /etc/cliv2/scripts/krb5_cache
+cp /etc/krb5.conf /etc/cliv2/scripts/krb5.conf
+chmod a+r /etc/cliv2/scripts/krb5_cache
+chmod a+r /etc/cliv2/scripts/krb5.conf
+
+echo "Kerberos setup complete."
+echo "Keeping container running... Press CTRL+C to stop."
+tail -F /var/log/squid/access.log
diff --git a/cliv2/internal/httpauth/test/helper/mock_http_server.go b/cliv2/internal/httpauth/test/helper/mock_http_server.go
new file mode 100644
index 0000000000..8370dfb6b2
--- /dev/null
+++ b/cliv2/internal/httpauth/test/helper/mock_http_server.go
@@ -0,0 +1,54 @@
+package helper
+
+import (
+ "fmt"
+ "net"
+ "net/http"
+)
+
+type MockServer struct {
+ Port int
+ ResponseList []*http.Response
+ responseIndex int
+ listener net.Listener
+}
+
+func NewMockServer() *MockServer {
+ listener, err := net.Listen("tcp", ":0")
+ if err != nil {
+ panic(err)
+ }
+
+ server := MockServer{ResponseList: []*http.Response{}}
+ server.Port = listener.Addr().(*net.TCPAddr).Port
+ server.listener = listener
+ fmt.Println("Using port:", listener.Addr().(*net.TCPAddr).Port)
+
+ return &server
+}
+
+func (m *MockServer) Listen() {
+ http.Serve(m.listener, m)
+}
+
+func (m *MockServer) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
+ var response *http.Response
+
+ if m.responseIndex < len(m.ResponseList) {
+ response = m.ResponseList[m.responseIndex]
+ m.responseIndex++
+ } else {
+ response = &http.Response{StatusCode: 200}
+ }
+
+ fmt.Printf("%4d. Request: %v\n", m.responseIndex, request)
+ fmt.Printf("%4d. Response: %v\n", m.responseIndex, response)
+
+ for k, v := range response.Header {
+ for i := range v {
+ writer.Header().Add(k, v[i])
+ }
+ }
+
+ writer.WriteHeader(response.StatusCode)
+}
diff --git a/cliv2/internal/proxy/proxy.go b/cliv2/internal/proxy/proxy.go
new file mode 100644
index 0000000000..3c0f98fc9e
--- /dev/null
+++ b/cliv2/internal/proxy/proxy.go
@@ -0,0 +1,207 @@
+package proxy
+
+import (
+ "context"
+ "crypto/tls"
+ "crypto/x509"
+ "fmt"
+ "log"
+ "net"
+ "net/http"
+ "net/url"
+ "os"
+
+ "github.com/snyk/cli/cliv2/internal/certs"
+ "github.com/snyk/cli/cliv2/internal/httpauth"
+ "github.com/snyk/cli/cliv2/internal/utils"
+
+ "github.com/elazarl/goproxy"
+)
+
+type WrapperProxy struct {
+ httpServer *http.Server
+ DebugLogger *log.Logger
+ CertificateLocation string
+ upstreamProxy func(*http.Request) (*url.URL, error)
+ transport *http.Transport
+ authenticator *httpauth.ProxyAuthenticator
+ port int
+ authMechanism httpauth.AuthenticationMechanism
+ cliVersion string
+}
+
+func NewWrapperProxy(insecureSkipVerify bool, cacheDirectory string, cliVersion string, debugLogger *log.Logger) (*WrapperProxy, error) {
+ var p WrapperProxy
+ p.DebugLogger = debugLogger
+ p.cliVersion = cliVersion
+
+ certName := "snyk-embedded-proxy"
+ certPEMBlock, keyPEMBlock, err := certs.MakeSelfSignedCert(certName, []string{}, p.DebugLogger)
+ if err != nil {
+ return nil, err
+ }
+
+ tempDir, err := utils.SnykTempDirectory(p.DebugLogger)
+ if err != nil {
+ p.DebugLogger.Println("failed to create system temp directory:", tempDir)
+ return nil, err
+ }
+
+ certFile, err := os.CreateTemp(tempDir, "snyk-cli-cert-*.crt")
+ if err != nil {
+ fmt.Println("failed to create temp cert file")
+ return nil, err
+ }
+ defer certFile.Close()
+
+ p.CertificateLocation = certFile.Name() // gives full path, not just the name
+ p.DebugLogger.Println("p.CertificateLocation:", p.CertificateLocation)
+
+ certPEMString := string(certPEMBlock)
+ err = utils.WriteToFile(p.CertificateLocation, certPEMString)
+ if err != nil {
+ fmt.Println("failed to write cert to file")
+ return nil, err
+ }
+
+ err = setCAFromBytes(certPEMBlock, keyPEMBlock)
+ if err != nil {
+ return nil, err
+ }
+
+ p.transport = &http.Transport{
+ TLSClientConfig: &tls.Config{
+ InsecureSkipVerify: insecureSkipVerify, // goproxy defaults to true
+ },
+ }
+
+ p.SetUpstreamProxy(http.ProxyFromEnvironment)
+
+ return &p, nil
+}
+
+func (p *WrapperProxy) replaceVersionHandler(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
+ // Manipulate Header (replace x-snyk-cli-version)
+ existingValue := r.Header.Get("x-snyk-cli-version")
+ if existingValue != "" {
+ p.DebugLogger.Printf("Replacing value of existing x-snyk-cli-version header (%s) with %s\n", existingValue, p.cliVersion)
+ r.Header.Set("x-snyk-cli-version", p.cliVersion)
+ }
+ return r, nil
+}
+
+func (p *WrapperProxy) Start() (int, error) {
+ proxy := goproxy.NewProxyHttpServer()
+ proxy.Tr = p.transport
+ proxy.Logger = p.DebugLogger
+ proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)
+ proxy.OnRequest().DoFunc(p.replaceVersionHandler)
+
+ proxy.Verbose = true
+ proxyServer := &http.Server{
+ Handler: proxy,
+ }
+
+ p.httpServer = proxyServer
+
+ p.DebugLogger.Println("starting proxy")
+ address := "127.0.0.1:0"
+ l, err := net.Listen("tcp", address)
+ if err != nil {
+ return 0, err
+ }
+
+ p.port = l.Addr().(*net.TCPAddr).Port
+ p.DebugLogger.Println("Wrapper proxy is listening on port: ", p.port)
+
+ go func() {
+ _ = p.httpServer.Serve(l) // this blocks until the server stops and gives you an error which can be ignored
+ }()
+
+ return p.port, nil
+}
+
+func (p *WrapperProxy) Stop() {
+ err := p.httpServer.Shutdown(context.Background())
+ if err == nil {
+ p.DebugLogger.Printf("Proxy successfully shut down")
+ } else {
+ // Error from closing listeners, or context timeout:
+ p.DebugLogger.Printf("HTTP server Shutdown error: %v", err)
+ }
+}
+
+func (p *WrapperProxy) Close() {
+ p.Stop()
+
+ p.DebugLogger.Println("deleting temp cert file:", p.CertificateLocation)
+ err := os.Remove(p.CertificateLocation)
+ if err != nil {
+ p.DebugLogger.Println("failed to delete cert file")
+ p.DebugLogger.Println(err)
+ } else {
+ p.DebugLogger.Println("deleted temp cert file:", p.CertificateLocation)
+ }
+}
+
+func setCAFromBytes(certPEMBlock []byte, keyPEMBlock []byte) error {
+ goproxyCa, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock)
+ if err != nil {
+ return err
+ }
+ if goproxyCa.Leaf, err = x509.ParseCertificate(goproxyCa.Certificate[0]); err != nil {
+ return err
+ }
+ goproxy.GoproxyCa = goproxyCa
+ goproxy.OkConnect = &goproxy.ConnectAction{Action: goproxy.ConnectAccept, TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)}
+ goproxy.MitmConnect = &goproxy.ConnectAction{Action: goproxy.ConnectMitm, TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)}
+ goproxy.HTTPMitmConnect = &goproxy.ConnectAction{Action: goproxy.ConnectHTTPMitm, TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)}
+ goproxy.RejectConnect = &goproxy.ConnectAction{Action: goproxy.ConnectReject, TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)}
+ return nil
+}
+
+func (p *WrapperProxy) SetUpstreamProxyAuthentication(mechanism httpauth.AuthenticationMechanism) {
+ if mechanism != p.authMechanism {
+ p.authMechanism = mechanism
+ p.DebugLogger.Printf("Enabled Proxy Authentication Mechanism: %s\n", httpauth.StringFromAuthenticationMechanism(p.authMechanism))
+ }
+
+ if httpauth.IsSupportedMechanism(p.authMechanism) { // since Negotiate is not covered by the go http stack, we skip its proxy handling and inject a custom Handling via the DialContext
+ p.authenticator = httpauth.NewProxyAuthenticator(p.authMechanism, p.upstreamProxy, p.DebugLogger)
+ p.transport.DialContext = p.authenticator.DialContext
+ p.transport.Proxy = nil
+ } else { // for other mechanisms like basic we switch back to go default behavior
+ p.transport.DialContext = nil
+ p.transport.Proxy = p.upstreamProxy
+ p.authenticator = nil
+ }
+}
+
+func (p *WrapperProxy) SetUpstreamProxyFromUrl(proxyAddr string) {
+ if len(proxyAddr) > 0 {
+ if proxyUrl, err := url.Parse(proxyAddr); err == nil {
+ p.SetUpstreamProxy(func(req *http.Request) (*url.URL, error) {
+ return proxyUrl, nil
+ })
+ } else {
+ fmt.Println("Failed to set proxy! ", err)
+ }
+ }
+}
+
+func (p *WrapperProxy) SetUpstreamProxy(proxyFunc func(req *http.Request) (*url.URL, error)) {
+ p.upstreamProxy = proxyFunc
+ p.SetUpstreamProxyAuthentication(p.authMechanism)
+}
+
+func (p *WrapperProxy) UpstreamProxy() func(req *http.Request) (*url.URL, error) {
+ return p.upstreamProxy
+}
+
+func (p *WrapperProxy) Port() int {
+ return p.port
+}
+
+func (p *WrapperProxy) Transport() *http.Transport {
+ return p.transport
+}
diff --git a/cliv2/internal/proxy/proxy_test.go b/cliv2/internal/proxy/proxy_test.go
new file mode 100644
index 0000000000..4a8ee905ec
--- /dev/null
+++ b/cliv2/internal/proxy/proxy_test.go
@@ -0,0 +1,178 @@
+package proxy_test
+
+import (
+ "crypto/tls"
+ "crypto/x509"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "os"
+ "testing"
+
+ "github.com/snyk/cli/cliv2/internal/httpauth"
+ "github.com/snyk/cli/cliv2/internal/proxy"
+
+ "github.com/stretchr/testify/assert"
+)
+
+var debugLogger *log.Logger = log.New(os.Stderr, "", log.Ldate|log.Ltime|log.Lmicroseconds|log.Lshortfile)
+
+func helper_getHttpClient(gateway *proxy.WrapperProxy) (*http.Client, error) {
+ rootCAs, _ := x509.SystemCertPool()
+ if rootCAs == nil {
+ rootCAs = x509.NewCertPool()
+ }
+
+ proxyCertBytes, err := ioutil.ReadFile(gateway.CertificateLocation)
+ if err != nil {
+ return nil, err
+ }
+
+ ok := rootCAs.AppendCertsFromPEM(proxyCertBytes)
+ if ok == false {
+ return nil, fmt.Errorf("failed to append proxy cert")
+ }
+
+ config := &tls.Config{
+ InsecureSkipVerify: false,
+ RootCAs: rootCAs,
+ }
+
+ proxyUrl, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", gateway.Port()))
+ if err != nil {
+ return nil, err
+ }
+
+ proxiedClient := &http.Client{Transport: &http.Transport{
+ Proxy: http.ProxyURL(proxyUrl),
+ TLSClientConfig: config,
+ }}
+
+ return proxiedClient, nil
+}
+
+func Test_closingProxyDeletesTempCert(t *testing.T) {
+ wp, err := proxy.NewWrapperProxy(false, "", "", debugLogger)
+ assert.Nil(t, err)
+
+ port, err := wp.Start()
+ t.Log("proxy port:", port)
+ assert.Nil(t, err)
+
+ wp.Close()
+
+ // assert cert file is deleted
+ _, err = os.Stat(wp.CertificateLocation)
+ assert.NotNil(t, err) // this means the file is gone
+}
+
+func Test_canGoThroughProxy(t *testing.T) {
+ wp, err := proxy.NewWrapperProxy(false, "", "", debugLogger)
+ assert.Nil(t, err)
+
+ _, err = wp.Start()
+ assert.Nil(t, err)
+
+ proxiedClient, err := helper_getHttpClient(wp)
+ assert.Nil(t, err)
+
+ res, err := proxiedClient.Get("https://static.snyk.io/cli/latest/version")
+ if err != nil {
+ t.Fatal(err)
+ }
+ assert.Equal(t, 200, res.StatusCode)
+
+ wp.Close()
+
+ // assert cert file is deleted on Close
+ _, err = os.Stat(wp.CertificateLocation)
+ assert.NotNil(t, err) // this means the file is gone
+}
+
+func Test_xSnykCliVersionHeaderIsReplaced(t *testing.T) {
+ expectedVersion := "the-cli-version"
+ wp, err := proxy.NewWrapperProxy(false, "", expectedVersion, debugLogger)
+ assert.Nil(t, err)
+
+ _, err = wp.Start()
+ assert.Nil(t, err)
+
+ proxiedClient, err := helper_getHttpClient(wp)
+ assert.Nil(t, err)
+
+ var capturedVersion string
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ capturedVersion = r.Header.Get("x-snyk-cli-version")
+ }))
+ defer ts.Close()
+
+ // request without the header set
+ res, err := proxiedClient.Get(ts.URL)
+ if err != nil {
+ t.Fatal(err)
+ }
+ assert.Equal(t, 200, res.StatusCode)
+ assert.Equal(t, "", capturedVersion)
+
+ // request with the header set
+ req, _ := http.NewRequest("GET", ts.URL, nil)
+ req.Header.Add("x-snyk-cli-version", "1.0.0")
+ res, err = proxiedClient.Do(req)
+ if err != nil {
+ t.Fatal(err)
+ }
+ assert.Equal(t, 200, res.StatusCode)
+ assert.Equal(t, expectedVersion, capturedVersion)
+
+ wp.Close()
+}
+
+func Test_SetUpstreamProxy(t *testing.T) {
+ var err error
+ var objectUnderTest *proxy.WrapperProxy
+
+ testUrl, _ := url.Parse("http://www.snyk.io")
+ testRequest := http.Request{URL: testUrl}
+
+ upstreanProxyUrlAsString := "http://localhost:3128"
+ expectedUpstreamProxyUrl, _ := url.Parse(upstreanProxyUrlAsString)
+
+ // using different cases to determine whether the proxy actually switches the mode authentication mode
+ testCaseList := []httpauth.AuthenticationMechanism{
+ httpauth.Negotiate,
+ httpauth.AnyAuth,
+ httpauth.NoAuth,
+ httpauth.UnknownMechanism,
+ }
+
+ objectUnderTest, err = proxy.NewWrapperProxy(false, "", "", debugLogger)
+ assert.Nil(t, err)
+
+ // running different cases
+ for i := range testCaseList {
+ currentMechanism := testCaseList[i]
+ t.Logf(" - using %s", httpauth.StringFromAuthenticationMechanism(currentMechanism))
+
+ objectUnderTest.SetUpstreamProxyAuthentication(currentMechanism)
+ objectUnderTest.SetUpstreamProxyFromUrl(upstreanProxyUrlAsString)
+ transport := objectUnderTest.Transport()
+ proxyFunc := objectUnderTest.UpstreamProxy()
+
+ assert.NotNil(t, proxyFunc)
+ actualUrl, err := proxyFunc(&testRequest)
+ assert.Nil(t, err)
+ assert.Equal(t, expectedUpstreamProxyUrl, actualUrl)
+
+ // check transport and thereby authenticator configuration
+ if httpauth.IsSupportedMechanism(currentMechanism) {
+ assert.NotNil(t, transport.DialContext)
+ assert.Nil(t, transport.Proxy)
+ } else {
+ assert.Nil(t, transport.DialContext)
+ assert.NotNil(t, transport.Proxy)
+ }
+ }
+}
diff --git a/cliv2/internal/utils/array.go b/cliv2/internal/utils/array.go
new file mode 100644
index 0000000000..03c8b73368
--- /dev/null
+++ b/cliv2/internal/utils/array.go
@@ -0,0 +1,87 @@
+package utils
+
+import (
+ "fmt"
+ "strings"
+)
+
+func Contains(list []string, element string) bool {
+ for _, a := range list {
+ if a == element {
+ return true
+ }
+ }
+ return false
+}
+
+func RemoveSimilar(list []string, element string) []string {
+ filteredArgs := []string{}
+
+ for _, a := range list {
+ if !strings.Contains(a, element) {
+ filteredArgs = append(filteredArgs, a)
+ }
+ }
+
+ return filteredArgs
+}
+
+func ToKeyValueMap(input []string, splitBy string) map[string]string {
+ result := make(map[string]string)
+
+ for _, a := range input {
+ splittedString := strings.SplitN(a, splitBy, 2)
+ if len(splittedString) == 2 {
+ key := splittedString[0]
+ value := splittedString[1]
+ result[key] = value
+ }
+ }
+
+ return result
+}
+
+func ToSlice(input map[string]string, combineBy string) []string {
+ result := []string{}
+
+ for key, value := range input {
+ entry := fmt.Sprintf("%s%s%s", key, combineBy, value)
+ result = append(result, entry)
+ }
+
+ return result
+}
+
+// Removes a given key from the input map and uses FindKeyCaseInsensitive() for this. The resulting map is being returned.
+func Remove(input map[string]string, key string) map[string]string {
+ found := false
+ key, found = FindKeyCaseInsensitive(input, key)
+ if found {
+ delete(input, key)
+ }
+ return input
+}
+
+// This method determines whether the given key is in the input map, it therefore looks for the exact match and the key in all capital or lower case letters.
+// If the key in any of these versions was found, it'll be returned alongside with a boolean indicating whether or not it was found.
+func FindKeyCaseInsensitive(input map[string]string, key string) (string, bool) {
+
+ found := false
+
+ // look for exact match
+ _, found = input[key]
+
+ // look for lower case match
+ if !found {
+ key = strings.ToLower(key)
+ _, found = input[key]
+ }
+
+ // look for upper case match
+ if !found {
+ key = strings.ToUpper(key)
+ _, found = input[key]
+ }
+
+ return key, found
+}
diff --git a/cliv2/internal/utils/cache-dir.go b/cliv2/internal/utils/cache-dir.go
new file mode 100644
index 0000000000..dc35a0040a
--- /dev/null
+++ b/cliv2/internal/utils/cache-dir.go
@@ -0,0 +1,25 @@
+package utils
+
+import (
+ "os"
+ "path"
+)
+
+func SnykCacheDir() (string, error) {
+ baseDirectory, err := os.UserCacheDir()
+ if err != nil {
+ return "", err
+ }
+
+ snykCacheDir := path.Join(baseDirectory, "snyk")
+ err = os.MkdirAll(snykCacheDir, 0755)
+ if err != nil {
+ return "", err
+ }
+
+ return snykCacheDir, nil
+}
+
+func FullPathInSnykCacheDir(cacheDir string, filename string) (string, error) {
+ return path.Join(cacheDir, filename), nil
+}
diff --git a/cliv2/internal/utils/file.go b/cliv2/internal/utils/file.go
new file mode 100644
index 0000000000..3556ce353a
--- /dev/null
+++ b/cliv2/internal/utils/file.go
@@ -0,0 +1,23 @@
+package utils
+
+import (
+ "bufio"
+ "os"
+)
+
+func WriteToFile(filePath string, data string) error {
+ file, err := os.Create(filePath)
+ if err != nil {
+ return err
+ }
+
+ defer file.Close()
+
+ w := bufio.NewWriter(file)
+ _, err = w.WriteString(data)
+ if err != nil {
+ return err
+ }
+
+ return w.Flush()
+}
diff --git a/cliv2/internal/utils/temp-dir.go b/cliv2/internal/utils/temp-dir.go
new file mode 100644
index 0000000000..5db47005ac
--- /dev/null
+++ b/cliv2/internal/utils/temp-dir.go
@@ -0,0 +1,47 @@
+package utils
+
+import (
+ "log"
+ "os"
+ "path"
+)
+
+// Gets the system temp directory and, if it doesn't exist, attempts to create it.
+func systemTempDirectory(debugLogger *log.Logger) (string, error) {
+ tempDir := os.TempDir()
+ // make sure this directory exists
+ debugLogger.Println("system temp directory:", tempDir)
+ _, err := os.Stat(tempDir)
+ if err != nil {
+ debugLogger.Println("system temp directory does not exist... attempting to create it:", tempDir)
+ err = os.MkdirAll(tempDir, 0755)
+ if err != nil {
+ debugLogger.Println("failed to create system temp directory:", tempDir)
+ return "", err
+ }
+ }
+
+ return tempDir, nil
+}
+
+func SnykTempDirectory(debugLogger *log.Logger) (string, error) {
+ tempDir, err := systemTempDirectory(debugLogger)
+ if err != nil {
+ return "", err
+ }
+
+ snykTempDir := path.Join(tempDir, "snyk")
+
+ // make sure it exists
+ _, err = os.Stat(snykTempDir)
+ if err != nil {
+ debugLogger.Println("snyk temp directory does not exist... attempting to create it:", snykTempDir)
+ err = os.MkdirAll(snykTempDir, 0755)
+ if err != nil {
+ debugLogger.Println("failed to create snyk temp directory:", snykTempDir)
+ return "", err
+ }
+ }
+
+ return snykTempDir, nil
+}
diff --git a/cliv2/jest.config.ts b/cliv2/jest.config.ts
new file mode 100644
index 0000000000..fadc043225
--- /dev/null
+++ b/cliv2/jest.config.ts
@@ -0,0 +1,3 @@
+import { createJestConfig } from '../test/createJestConfig';
+
+export default createJestConfig({ displayName: 'cliv2' });
diff --git a/cliv2/main_integration_test.go b/cliv2/main_integration_test.go
new file mode 100644
index 0000000000..ed838c8aeb
--- /dev/null
+++ b/cliv2/main_integration_test.go
@@ -0,0 +1,132 @@
+//go:build integration
+// +build integration
+
+package main
+
+import (
+ "fmt"
+ "github.com/snyk/cli/cliv2/internal/embedded"
+ "github.com/snyk/cli/cliv2/internal/embedded/cliv1"
+ "github.com/snyk/cli/cliv2/test"
+ "github.com/stretchr/testify/assert"
+ "os"
+ "strings"
+ "testing"
+)
+
+func Test_init(t *testing.T) {
+ args := []string{"version"}
+ res := test.SetupTestProject(t).LaunchCLI(t, args)
+ t.Log("ExitCode", res.ExitCode)
+ t.Log("Stdout", res.Stdout)
+ t.Log("Stderr", res.Stderr)
+
+ versionString := strings.TrimSpace(res.Stdout)
+
+ // from https://semver.org/
+ semverRegexp := `^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`
+
+ assert.Equal(t, res.ExitCode, 0)
+ assert.Regexp(t, semverRegexp, versionString)
+}
+
+func Test_debug_mode(t *testing.T) {
+ type TestCase struct {
+ args []string
+ expectedExitCode int
+ expectedStderrContains string
+ }
+
+ cases := []TestCase{
+ {[]string{""}, 0, ""},
+ {[]string{"--debug"}, 0, "debug: true"},
+ {[]string{"-d"}, 0, "debug: true"},
+ }
+
+ for _, c := range cases {
+ res := test.SetupTestProject(t).LaunchCLI(t, c.args)
+ assert.Equal(t, res.ExitCode, c.expectedExitCode)
+ assert.Contains(t, res.Stderr, c.expectedStderrContains)
+ }
+}
+
+func Test_passesExitCode(t *testing.T) {
+ args := []string{"test"}
+ testProject := test.SetupTestProjectWithFixture(t, "test/fixtures/npm-test-proj-with-vulns")
+ res := testProject.LaunchCLI(t, args)
+ assert.Equal(t, res.ExitCode, 1)
+ assert.Contains(t, res.Stdout, "found 4 issues")
+
+ args = []string{"test"}
+ testProject = test.SetupTestProjectWithFixture(t, "test/fixtures/npm-test-proj-no-vulns")
+ res = testProject.LaunchCLI(t, args)
+ assert.Equal(t, res.ExitCode, 0)
+ assert.Contains(t, res.Stdout, "no vulnerable paths found")
+}
+
+func Test_canPassThroughArgs(t *testing.T) {
+ args := []string{"test", "--print-deps"}
+ testProject := test.SetupTestProjectWithFixture(t, "test/fixtures/npm-test-proj-with-vulns")
+ res := testProject.LaunchCLI(t, args)
+ assert.Equal(t, res.ExitCode, 1)
+ assert.Contains(t, res.Stdout, "npm-test-proj-with-vulns @ 1.0.0")
+ assert.Contains(t, res.Stdout, "└─ lodash @ 4.17.15")
+ assert.Contains(t, res.Stdout, "found 4 issues")
+}
+
+func Test_cliv1AlreadyExistsAndIsValid(t *testing.T) {
+ testProject := test.SetupTestProject(t)
+
+ // get target extraction path
+ cliv1TargetExtractionPath, err := cliv1.GetFullCLIV1TargetPath(testProject.CacheDirPath)
+ if err != nil {
+ t.Errorf("failed to get cliv1 target extraction path: %s", err)
+ }
+
+ // extract the real cliv1 to the path
+ cliv1.ExtractTo(cliv1TargetExtractionPath)
+
+ cliv1FileInfoBefore, err := os.Stat(cliv1TargetExtractionPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ res := testProject.LaunchCLI(t, []string{"version", "--debug"})
+
+ assert.Equal(t, res.ExitCode, 0)
+ assert.Contains(t, res.Stderr, fmt.Sprintf("cliv1 already exists and is valid at %s", cliv1TargetExtractionPath))
+ cliv1FileInfoAfter, err := os.Stat(cliv1TargetExtractionPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ assert.Equal(t, cliv1FileInfoBefore.Size(), cliv1FileInfoAfter.Size())
+ assert.Equal(t, cliv1FileInfoBefore.ModTime(), cliv1FileInfoAfter.ModTime())
+}
+
+func Test_cliv1AlreadyExistsAndIsInvalid(t *testing.T) {
+ testProject := test.SetupTestProject(t)
+
+ // get target extraction path
+ cliv1TargetExtractionPath, err := cliv1.GetFullCLIV1TargetPath(testProject.CacheDirPath)
+ if err != nil {
+ t.Errorf("failed to get cliv1 target extraction path: %s", err)
+ }
+
+ // write a bogus file to cliv1TargetExtractionPath
+ embedded.ExtractBytesToTarget([]byte(""), cliv1TargetExtractionPath)
+ cliv1FileInfoBefore, err := os.Stat(cliv1TargetExtractionPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ res := testProject.LaunchCLI(t, []string{"--debug"})
+
+ assert.Equal(t, res.ExitCode, 0)
+ assert.Contains(t, res.Stderr, "cliv1 is not valid, start extracting")
+ assert.Contains(t, res.Stderr, "cliv1 is valid after extracting")
+ cliv1FileInfoAfter, err := os.Stat(cliv1TargetExtractionPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ assert.NotEqual(t, cliv1FileInfoBefore.Size(), cliv1FileInfoAfter.Size())
+}
diff --git a/cliv2/scripts/issigned_darwin.sh b/cliv2/scripts/issigned_darwin.sh
new file mode 100755
index 0000000000..34ec53e276
--- /dev/null
+++ b/cliv2/scripts/issigned_darwin.sh
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+set -uo pipefail
+
+LOG_PREFIX="--- $(basename "$0"):"
+
+if [[ "$OSTYPE" != *"darwin"* ]]; then
+ echo "$LOG_PREFIX ERROR! This script needs to be run on macOS!"
+ exit 1
+fi
+
+if ! codesign --verify --deep --strict "$1"; then
+ echo "$LOG_PREFIX NOT signed!"
+ exit 1
+else
+ echo "$LOG_PREFIX is signed!"
+fi
diff --git a/cliv2/scripts/issigned_windows.sh b/cliv2/scripts/issigned_windows.sh
new file mode 100755
index 0000000000..519e54e7e1
--- /dev/null
+++ b/cliv2/scripts/issigned_windows.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+set -uo pipefail
+
+LOG_PREFIX="--- $(basename "$0"):"
+
+if ! osslsigncode verify -in "$1"; then
+ echo "$LOG_PREFIX NOT signed!"
+ exit 1
+else
+ echo "$LOG_PREFIX is signed!"
+fi
diff --git a/cliv2/scripts/lint.sh b/cliv2/scripts/lint.sh
new file mode 100755
index 0000000000..c45d5cb672
--- /dev/null
+++ b/cliv2/scripts/lint.sh
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+echo "Running gofmt"
+
+# gofmt does not fail if issues are found so check if unformatted files are listed
+GOFMT_RESULT="$(gofmt -l -e .)"
+if test -n "${GOFMT_RESULT}"; then
+ echo "${GOFMT_RESULT}"
+ echo "Formatting issues found. Run 'make format' to fix them.";
+ exit 1;
+fi
+echo "No formatting issues found."
diff --git a/cliv2/scripts/sign_darwin.sh b/cliv2/scripts/sign_darwin.sh
new file mode 100755
index 0000000000..47ed19706d
--- /dev/null
+++ b/cliv2/scripts/sign_darwin.sh
@@ -0,0 +1,80 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# expected environment variables
+#APPLE_ID=AAA
+#APPLE_APP_PASSWORD=BBB
+#APPLE_TEAM_ID=CCC
+#APPLE_SIGNING_IDENTITY="DDD"
+#APPLE_SIGNING_SECRETS_BINARY=EEE....
+#APPLE_SIGNING_SECRETS_PASSWORD=FFF
+
+EXPORT_PATH=${1:-./bin}
+PRODUCT_NAME=${2:-snyk_darwin_amd64}
+KEYCHAIN_PROFILE=AC_PASSWORD
+APP_PATH="$EXPORT_PATH/$PRODUCT_NAME"
+ZIP_PATH="$EXPORT_PATH/$PRODUCT_NAME.zip"
+#DMG_PATH="$EXPORT_PATH/$PRODUCT_NAME.dmg"
+APPLE_SIGNING_SECRETS="AppleCodeSigningSecrets.p12"
+KEYCHAIN_NAME=CodeSigningChain
+KEYCHAIN_PASSWORD=123456
+KEYCHAIN_FILE="$HOME/Library/Keychains/$KEYCHAIN_NAME-db"
+OLD_KEYCHAIN_NAMES=$(security list-keychains | sed -E -e ':a' -e 'N' -e '$!ba' -e 's/\n//g' -e 's/ //g' -e 's/""/" "/g')
+
+LOG_PREFIX="--- $(basename "$0"):"
+
+echo "$LOG_PREFIX Signing & notarizing \"$APP_PATH\""
+
+if [[ "$OSTYPE" != *"darwin"* ]]; then
+ echo "$LOG_PREFIX ERROR! This script needs to be run on macOS!"
+ exit 1
+fi
+
+#
+# signing
+#
+echo "$LOG_PREFIX Creating p12 file"
+echo "$APPLE_SIGNING_SECRETS_BINARY" | base64 --decode > "$APPLE_SIGNING_SECRETS"
+
+echo "$LOG_PREFIX Adding temporary keychain"
+security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME"
+security list-keychains -s "$KEYCHAIN_NAME"
+security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME"
+
+# import signing secrets into key chain
+echo "$LOG_PREFIX Importing p12 file into temporary keychain"
+security import "$APPLE_SIGNING_SECRETS" -P "$APPLE_SIGNING_SECRETS_PASSWORD" -k "$KEYCHAIN_NAME" -T /usr/bin/codesign
+rm $APPLE_SIGNING_SECRETS
+security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME"
+
+echo "$LOG_PREFIX Signing binary $APP_PATH"
+codesign -s "$APPLE_SIGNING_IDENTITY" -v "$APP_PATH" --options runtime
+
+#
+# notarization
+#
+
+# create a zip file
+echo "$LOG_PREFIX Creating zip file $ZIP_PATH"
+/usr/bin/ditto -c -k --keepParent "$APP_PATH" "$ZIP_PATH"
+
+# add notarization credentials to keychain for later usage
+echo "$LOG_PREFIX Preparing notarization"
+xcrun notarytool store-credentials "$KEYCHAIN_PROFILE" --apple-id "$APPLE_ID" --team-id "$APPLE_TEAM_ID" --password "$APPLE_APP_PASSWORD" --keychain "$KEYCHAIN_FILE"
+
+# notarize & wait
+echo "$LOG_PREFIX Running notarization"
+xcrun notarytool submit "$ZIP_PATH" --keychain-profile "$KEYCHAIN_PROFILE" --wait
+
+# note: currently creating a DMG is disabled, since we experienced issues running it n the CircleCi VM
+# create dmg
+#echo "$LOG_PREFIX Creating DMG file $DMG_PATH"
+#hdiutil create -volname "$PRODUCT_NAME" -srcfolder "$APP_PATH" -ov -format UDZO "$DMG_PATH"
+#xcrun notarytool submit "$DMG_PATH" --keychain-profile "$KEYCHAIN_PROFILE" --wait
+# ONLY for .dmg - Staple
+#xcrun stapler staple "$DMG_PATH"
+
+# cleanup
+echo "$LOG_PREFIX Cleaning up"
+security list-keychains -s "$OLD_KEYCHAIN_NAMES"
+security delete-keychain "$KEYCHAIN_NAME"
diff --git a/cliv2/scripts/sign_windows.sh b/cliv2/scripts/sign_windows.sh
new file mode 100755
index 0000000000..415a68a2b3
--- /dev/null
+++ b/cliv2/scripts/sign_windows.sh
@@ -0,0 +1,35 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# expected environment variables
+#SIGNING_SECRETS_BINARY=EEE....
+#SIGNING_SECRETS_PASSWORD=FFF
+
+EXPORT_PATH=${1:-./bin}
+PRODUCT_NAME=${2:-snyk_windows_amd64.exe}
+APP_PATH="$EXPORT_PATH/$PRODUCT_NAME"
+APP_PATH_UNSIGNED="$APP_PATH.unsigned"
+SIGNING_SECRETS=secrets.p12
+
+LOG_PREFIX="--- $(basename "$0"):"
+echo "$LOG_PREFIX Signing \"$APP_PATH\""
+
+# create files as needed
+echo "$LOG_PREFIX Creating p12 file"
+echo "$SIGNING_SECRETS_BINARY" | base64 --decode > "$SIGNING_SECRETS"
+
+echo "$LOG_PREFIX Signing binary $APP_PATH_UNSIGNED"
+mv "$APP_PATH" "$APP_PATH_UNSIGNED"
+
+osslsigncode sign -h sha512 \
+ -pkcs12 "$SIGNING_SECRETS" \
+ -pass "$SIGNING_SECRETS_PASSWORD" \
+ -n "Snyk CLI" \
+ -i "https://snyk.io" \
+ -t "http://timestamp.comodoca.com/authenticode" \
+ -in "$APP_PATH_UNSIGNED" \
+ -out "$APP_PATH"
+
+echo "$LOG_PREFIX Cleaning up"
+rm -f "$APP_PATH_UNSIGNED"
+rm -f "$SIGNING_SECRETS"
diff --git a/cliv2/test/acceptance/proxy_authentication.spec.ts b/cliv2/test/acceptance/proxy_authentication.spec.ts
new file mode 100644
index 0000000000..49d031c8c4
--- /dev/null
+++ b/cliv2/test/acceptance/proxy_authentication.spec.ts
@@ -0,0 +1,277 @@
+import * as path from 'path';
+import { fakeServer, FakeServer } from '../../../test/acceptance/fake-server';
+import {
+ createProjectFromWorkspace,
+ TestProject,
+} from '../../../test/jest/util/createProject';
+import {
+ startCommand,
+ TestCLI,
+ startSnykCLI,
+} from '../../../test/jest/util/startSnykCLI';
+import { isCLIV2 } from '../../../test/jest/util/isCLIV2';
+import { unlink } from 'fs';
+import { execSync } from 'child_process';
+import * as os from 'os';
+
+jest.setTimeout(1000 * 60);
+
+// Global test configuration
+const rootDir = path.resolve(path.join(__dirname, '..', '..'));
+const squidEnvironmentPath = path.resolve(
+ path.join(
+ rootDir,
+ 'internal',
+ 'httpauth',
+ 'test',
+ 'fixtures',
+ 'squid_environment',
+ ),
+);
+const dockerComposeFile = path.resolve(
+ path.join(squidEnvironmentPath, 'docker-compose.yml'),
+);
+const scriptsPath = path.resolve(path.join(squidEnvironmentPath, 'scripts'));
+const containerName = 'proxy_authentication_container';
+const hostnameFakeServer = 'host.docker.internal';
+const hostnameProxy = 'proxy.snyk.local';
+const proxyPort = '3128';
+const port = process.env.PORT || process.env.SNYK_PORT || '12345';
+const baseApi = '/api/v1';
+const SNYK_API = 'http://' + hostnameFakeServer + ':' + port + baseApi;
+const HTTP_PROXY = 'http://localhost:' + proxyPort;
+const KRB5_CACHE_FILE = 'krb5_cache';
+const KRB5_CONFIG_FILE = 'krb5.conf';
+
+function getDockerOptions() {
+ const dockerOptions = {
+ env: {
+ ...process.env,
+ HTTP_PROXY_PORT: proxyPort,
+ PROXY_HOSTNAME: hostnameProxy,
+ SNYK_API: SNYK_API,
+ CONTAINER_NAME: containerName,
+ SCRIPTS_PATH: scriptsPath,
+ },
+ };
+ return dockerOptions;
+}
+
+function isDockerAvailable(): boolean {
+ let result = false;
+
+ try {
+ execSync('docker --version');
+ execSync('docker-compose --version');
+ result = true;
+ } catch (error) {
+ result = false;
+ console.debug(error);
+ }
+
+ return result;
+}
+
+async function startProxyEnvironment(): Promise {
+ // Stop any orphaned containers from previous runs.
+ await stopProxyEnvironment();
+
+ const dockerUp = await startCommand(
+ 'docker-compose',
+ ['--file', dockerComposeFile, 'up', '--build'],
+ getDockerOptions(),
+ );
+ await expect(dockerUp).toDisplay('Kerberos setup complete.', {
+ timeout: 60_000,
+ });
+}
+
+async function stopProxyEnvironment(): Promise {
+ const dockerDown = await startCommand(
+ 'docker-compose',
+ ['--file', dockerComposeFile, 'down'],
+ getDockerOptions(),
+ );
+ await expect(dockerDown).toExitWith(0, { timeout: 30_000 });
+}
+
+async function getProxyAccessLog(): Promise {
+ const check = await startCommand('docker', [
+ 'exec',
+ containerName,
+ 'cat',
+ '/var/log/squid/access.log',
+ ]);
+ await expect(check).toExitWith(0);
+ return check.stdout.get();
+}
+
+async function runCliWithProxy(
+ env: Record,
+ args: string[] = [],
+ cmd = 'test',
+): Promise {
+ let temp: string[] = [cmd, '--debug'];
+ temp = temp.concat(args);
+
+ if (env['KRB5CCNAME'] == undefined) {
+ env['KRB5CCNAME'] = 'FILE:' + path.join(scriptsPath, KRB5_CACHE_FILE);
+ env['KRB5_CONFIG'] = path.join(scriptsPath, KRB5_CONFIG_FILE);
+ }
+
+ const cli = await startSnykCLI(temp.join(' '), {
+ env: {
+ ...env,
+ SNYK_HTTP_PROTOCOL_UPGRADE: '0',
+ },
+ });
+ return cli;
+}
+
+function canTestRun(): boolean {
+ if (!isCLIV2() || !isDockerAvailable()) {
+ // eslint-disable-next-line jest/no-focused-tests
+ it.only('These tests are currently limited to certain environments.', () => {
+ console.warn(
+ 'Skipping CLIv2 test. These tests are limited to environments that have docker and docker-compose installed.',
+ );
+ });
+ return false;
+ }
+ return true;
+}
+
+describe('Proxy Authentication (all platforms)', () => {
+ if (canTestRun()) {
+ let server: FakeServer;
+ let env: Record;
+ let project: TestProject;
+
+ beforeAll(async () => {
+ project = await createProjectFromWorkspace('npm-package');
+ await startProxyEnvironment();
+
+ env = {
+ ...process.env,
+ SNYK_API: SNYK_API,
+ SNYK_TOKEN: '123456789',
+ HTTP_PROXY: HTTP_PROXY,
+ HTTPS_PROXY: HTTP_PROXY,
+ };
+ server = fakeServer(baseApi, env.SNYK_TOKEN);
+ await server.listenPromise(port);
+ });
+
+ afterEach(() => {
+ server.restore();
+ });
+
+ afterAll(async () => {
+ await server.closePromise();
+ await stopProxyEnvironment();
+ unlink(path.join(scriptsPath, KRB5_CACHE_FILE), () => {});
+ unlink(path.join(scriptsPath, KRB5_CONFIG_FILE), () => {});
+ });
+
+ it('fails to run snyk test due to disabled proxy authentication', async () => {
+ const logOnEntry = await getProxyAccessLog();
+
+ // run snyk test
+ const args: string[] = ['--proxy-noauth', project.path()];
+ const cli = await runCliWithProxy(env, args);
+ await expect(cli).toExitWith(2);
+
+ const logOnExit = await getProxyAccessLog();
+ const additionalLogEntries = logOnExit.substring(logOnEntry.length);
+ expect(additionalLogEntries.includes('TCP_DENIED/407')).toBeTruthy();
+ expect(
+ additionalLogEntries.includes(
+ 'CONNECT ' + hostnameFakeServer + ':' + port,
+ ),
+ ).toBeFalsy();
+ });
+
+ it('successfully runs snyk test with proxy', async () => {
+ const logOnEntry = await getProxyAccessLog();
+
+ // run snyk test
+ const args: string[] = [project.path()];
+ const cli = await runCliWithProxy(env, args);
+ await expect(cli).toExitWith(0);
+
+ const logOnExit = await getProxyAccessLog();
+ const additionalLogEntries = logOnExit.substring(logOnEntry.length);
+ expect(additionalLogEntries.includes('TCP_TUNNEL/200')).toBeTruthy();
+ expect(
+ additionalLogEntries.includes(
+ 'CONNECT ' + hostnameFakeServer + ':' + port,
+ ),
+ ).toBeTruthy();
+ });
+ }
+});
+
+describe('Proxy Authentication (Non-Windows)', () => {
+ if (canTestRun() && !os.platform().includes('win32')) {
+ let server: FakeServer;
+ let env: Record;
+ let project: TestProject;
+
+ beforeAll(async () => {
+ project = await createProjectFromWorkspace('npm-package');
+ await startProxyEnvironment();
+
+ env = {
+ ...process.env,
+ SNYK_API: SNYK_API,
+ SNYK_TOKEN: '123456789',
+ HTTP_PROXY: HTTP_PROXY,
+ HTTPS_PROXY: HTTP_PROXY,
+ };
+ server = fakeServer(baseApi, env.SNYK_TOKEN);
+ await server.listenPromise(port);
+ });
+
+ afterEach(() => {
+ server.restore();
+ });
+
+ afterAll(async () => {
+ await server.closePromise();
+ await stopProxyEnvironment();
+ unlink(path.join(scriptsPath, KRB5_CACHE_FILE), () => {});
+ unlink(path.join(scriptsPath, KRB5_CONFIG_FILE), () => {});
+ });
+
+ it('fail to run snyk test with proxy due to incorrect cache configuration', async () => {
+ const logOnEntry = await getProxyAccessLog();
+
+ // run snyk test
+ const args: string[] = [project.path()];
+ env['KRB5CCNAME'] = 'MEMORY:' + path.join(scriptsPath, KRB5_CACHE_FILE); // specifying incorrect cache type memory
+ env['KRB5_CONFIG'] = path.join(scriptsPath, KRB5_CONFIG_FILE);
+ const cli = await runCliWithProxy(env, args);
+ await expect(cli).toExitWith(2);
+
+ const logOnExit = await getProxyAccessLog();
+ const additionalLogEntries = logOnExit.substring(logOnEntry.length);
+ expect(additionalLogEntries.includes('TCP_DENIED/407')).toBeTruthy();
+ });
+
+ it('fail to run snyk test with proxy due to incorrect config file', async () => {
+ const logOnEntry = await getProxyAccessLog();
+
+ // run snyk test
+ const args: string[] = [project.path()];
+ env['KRB5CCNAME'] = 'FILE:' + path.join(scriptsPath, KRB5_CACHE_FILE);
+ env['KRB5_CONFIG'] =
+ path.join(scriptsPath, KRB5_CONFIG_FILE) + '_not_existing'; // specifying incorrect config location
+ const cli = await runCliWithProxy(env, args);
+ await expect(cli).toExitWith(2);
+
+ const logOnExit = await getProxyAccessLog();
+ const additionalLogEntries = logOnExit.substring(logOnEntry.length);
+ expect(additionalLogEntries.includes('TCP_DENIED/407')).toBeTruthy();
+ });
+ }
+});
diff --git a/cliv2/test/fixtures/npm-test-proj-no-vulns/package.json b/cliv2/test/fixtures/npm-test-proj-no-vulns/package.json
new file mode 100644
index 0000000000..25d68b4d1a
--- /dev/null
+++ b/cliv2/test/fixtures/npm-test-proj-no-vulns/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "npm-test-proj-no-vulns",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ }
+}
diff --git a/cliv2/test/fixtures/npm-test-proj-with-vulns/package-lock.json b/cliv2/test/fixtures/npm-test-proj-with-vulns/package-lock.json
new file mode 100644
index 0000000000..bf402535e9
--- /dev/null
+++ b/cliv2/test/fixtures/npm-test-proj-with-vulns/package-lock.json
@@ -0,0 +1,28 @@
+{
+ "name": "npm-test-proj-with-vulns",
+ "version": "1.0.0",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "npm-test-proj-with-vulns",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "lodash": "^4.17.15"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.15",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
+ }
+ },
+ "dependencies": {
+ "lodash": {
+ "version": "4.17.15",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
+ }
+ }
+}
\ No newline at end of file
diff --git a/cliv2/test/fixtures/npm-test-proj-with-vulns/package.json b/cliv2/test/fixtures/npm-test-proj-with-vulns/package.json
new file mode 100644
index 0000000000..9490a50ecd
--- /dev/null
+++ b/cliv2/test/fixtures/npm-test-proj-with-vulns/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "npm-test-proj-with-vulns",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "dependencies": {
+ "lodash": "^4.17.15"
+ }
+}
\ No newline at end of file
diff --git a/cliv2/test/testlib.go b/cliv2/test/testlib.go
new file mode 100644
index 0000000000..fb5948de40
--- /dev/null
+++ b/cliv2/test/testlib.go
@@ -0,0 +1,213 @@
+package test
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path"
+ "testing"
+)
+
+type ProcessOutput struct {
+ ExitCode int
+ Stdout string
+ Stderr string
+}
+
+func getBinPath(t *testing.T) string {
+ cliBinPath := os.Getenv("TEST_SNYK_EXECUTABLE_PATH")
+
+ _, err := os.Stat(cliBinPath)
+ if err != nil {
+ fmt.Println("error checking binPath")
+ t.Fatal(err)
+ }
+
+ return cliBinPath
+}
+
+func LaunchAsProccess(t *testing.T, args []string) *ProcessOutput {
+ snykCLIPath := getBinPath(t)
+ t.Log("snykCLIPath:", snykCLIPath)
+
+ if _, err := os.Stat(snykCLIPath); err != nil {
+ t.Fatal("snyk CLI binary not found")
+ }
+
+ cmd := exec.Command(snykCLIPath, args...)
+ var stderrBuf bytes.Buffer
+ cmd.Stderr = &stderrBuf
+ cmdOutput, err := cmd.Output()
+
+ exitCode := 0
+ if err != nil {
+ if exitError, ok := err.(*exec.ExitError); ok {
+ exitCode = exitError.ExitCode()
+ } else {
+ // got an error but it's not an ExitError
+ t.Fatal(err)
+ }
+ }
+
+ output := ProcessOutput{
+ ExitCode: exitCode,
+ Stdout: string(cmdOutput),
+ Stderr: stderrBuf.String(),
+ }
+
+ return &output
+}
+
+type TestProject struct {
+ TestDirectoryPath string
+ SnykCliPath string
+ CacheDirPath string
+}
+
+func SetupTestProject(t *testing.T) *TestProject {
+ snykCLIPath := getBinPath(t)
+ t.Log("snykCLIPath:", snykCLIPath)
+
+ snykCLIFilename := path.Base(snykCLIPath)
+ tempDirForTest := t.TempDir()
+
+ targetSnykCLIPath := path.Join(tempDirForTest, snykCLIFilename)
+ t.Log("targetSnykCLIPath:", targetSnykCLIPath)
+ err := copyFile(snykCLIPath, targetSnykCLIPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = os.Chmod(targetSnykCLIPath, 0755)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ cacheDirPath := path.Join(tempDirForTest, "cache")
+ err = os.MkdirAll(cacheDirPath, 0755)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ testProject := TestProject{
+ TestDirectoryPath: tempDirForTest,
+ SnykCliPath: targetSnykCLIPath,
+ CacheDirPath: cacheDirPath,
+ }
+
+ return &testProject
+}
+
+func (tp *TestProject) CopyFixture(t *testing.T, fixturePath string) error {
+ err := copyDir(fixturePath, tp.TestDirectoryPath)
+ return err
+}
+
+func SetupTestProjectWithFixture(t *testing.T, fixturePath string) *TestProject {
+ testProject := SetupTestProject(t)
+ err := testProject.CopyFixture(t, fixturePath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return testProject
+}
+
+func (tp *TestProject) LaunchCLI(t *testing.T, args []string) *ProcessOutput {
+ t.Log("TestDirectoryPath:", tp.TestDirectoryPath)
+ t.Log("SnykCliPath:", tp.SnykCliPath)
+
+ cmd := exec.Command(tp.SnykCliPath, args...)
+ cmd.Dir = tp.TestDirectoryPath
+ cmd.Env = append(
+ os.Environ(),
+ fmt.Sprintf("SNYK_CACHE_PATH=%s", tp.CacheDirPath),
+ )
+
+ var stderrBuf bytes.Buffer
+ cmd.Stderr = &stderrBuf
+ cmdOutput, err := cmd.Output()
+
+ exitCode := 0
+ if err != nil {
+ if exitError, ok := err.(*exec.ExitError); ok {
+ exitCode = exitError.ExitCode()
+ } else {
+ // got an error but it's not an ExitError
+ t.Fatal(err)
+ }
+ }
+
+ output := ProcessOutput{
+ ExitCode: exitCode,
+ Stdout: string(cmdOutput),
+ Stderr: stderrBuf.String(),
+ }
+
+ return &output
+}
+
+func copyFile(sourcePath, destinationPath string) error {
+ source, err := os.Open(sourcePath)
+ if err != nil {
+ return err
+ }
+ defer source.Close()
+
+ destination, err := os.Create(destinationPath)
+ if err != nil {
+ return err
+ }
+ defer destination.Close()
+
+ _, err = io.Copy(destination, source)
+ return err
+}
+
+func copyDir(sourceDir, destinationDir string) error {
+ sourceStat, err := os.Stat(sourceDir)
+ if err != nil {
+ return err
+ }
+
+ if !sourceStat.IsDir() {
+ return fmt.Errorf("%s is not a directory", sourceDir)
+ }
+
+ destStat, err := os.Stat(destinationDir)
+ if err != nil {
+ // destination path does not exist, create it
+ err = os.MkdirAll(destinationDir, sourceStat.Mode())
+ if err != nil {
+ return err
+ }
+ }
+
+ if !destStat.IsDir() {
+ return fmt.Errorf("%s is not a directory", destinationDir)
+ }
+
+ files, err := ioutil.ReadDir(sourceDir)
+ if err != nil {
+ return err
+ }
+
+ for _, fileInfo := range files {
+ sourceFilePath := path.Join(sourceDir, fileInfo.Name())
+ destFilePath := path.Join(destinationDir, fileInfo.Name())
+
+ if fileInfo.IsDir() {
+ err = copyDir(sourceFilePath, destFilePath)
+ } else {
+ err = copyFile(sourceFilePath, destFilePath)
+ }
+
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/config.default.json b/config.default.json
index 38dc52758b..c3db8a5337 100644
--- a/config.default.json
+++ b/config.default.json
@@ -1,5 +1,7 @@
{
- "API": "https://snyk.io/api/v1",
"devDeps": false,
- "PRUNE_DEPS_THRESHOLD": 40000
+ "PRUNE_DEPS_THRESHOLD": 40000,
+ "MAX_PATH_COUNT": 1500000,
+ "NPM_TREE_SIZE_LIMIT": 6.0e6,
+ "YARN_TREE_SIZE_LIMIT": 6.0e6
}
diff --git a/dangerfile.js b/dangerfile.js
index 5845efceea..e2a5d904c8 100644
--- a/dangerfile.js
+++ b/dangerfile.js
@@ -1,28 +1,111 @@
-const {danger, warn, fail, message} = require('danger');
+const { danger, warn, fail, message } = require('danger');
+const fs = require('fs');
+
+const MAX_COMMIT_MESSAGE_LENGTH = 72;
+const SMOKE_TEST_BRANCH = 'smoke/';
+const SMOKE_TEST_WORKFLOW_FILE_PATH = '.github/workflows/smoke-tests.yml';
if (danger.github && danger.github.pr) {
- const commitizenRegex = /^(feat|fix|chore|test|docs|perf|refactor|revert)(\(.*\))?:(.+)$/;
const ghCommits = danger.github.commits;
- let willTriggerRelease = false;
- for (const {commit} of ghCommits) {
- const {message, url} = commit;
- const firstLine = message.split('\n')[0];
+ for (const { commit } of ghCommits) {
+ const { message, url } = commit;
+ const [firstLine] = message.split('\n', 1);
- if (message.startsWith('feat') || message.startsWith('fix')) {
- willTriggerRelease = true;
+ const firstLineRegex = /^(feat|fix|chore|test|docs|refactor|revert)(\(.*\))?:(.+)$/;
+ if (!firstLineRegex.test(firstLine)) {
+ fail(
+ `"[${firstLine}](${url})" is not using a valid commit message format. For commit guidelines, see: [CONTRIBUTING](https://github.com/snyk/snyk/blob/master/CONTRIBUTING.md#creating-commits).`,
+ );
}
- const regexMatch = commitizenRegex.exec(firstLine);
- if (!regexMatch) {
- fail(`Commit ["${firstLine}"](${url}) is not a valid commitizen message. See [Contributing page](https://github.com/snyk/snyk/blob/master/.github/CONTRIBUTING.md#commit-types) with required commit message format.`);
+ if (firstLine.length >= MAX_COMMIT_MESSAGE_LENGTH) {
+ warn(
+ `"[${firstLine}](${url})" is too long. Keep the first line of your commit message under ${MAX_COMMIT_MESSAGE_LENGTH} characters.`,
+ );
}
+ }
- if (firstLine.length >= 72) {
- warn(`Your commit message ["${firstLine}"](${url}) is too long. Keep first line of your commit under 72 characters.`);
- }
+ // Forgotten tests check
+ const modifiedTest =
+ danger.git.modified_files.some((f) => f.startsWith('test/')) ||
+ danger.git.created_files.some((f) => f.startsWith('test/'));
+ const modifiedSrc =
+ danger.git.modified_files.some((f) => f.startsWith('src/')) ||
+ danger.git.created_files.some((f) => f.startsWith('src/'));
+
+ if (modifiedSrc && !modifiedTest) {
+ // TODO: let's be careful about wording here. Maybe including Contributing guidelines and project goals document here
+ warn(
+ "You've modified files in `src/` directory, but haven't updated anything in test folder. Is there something that could be tested?",
+ );
}
- if (!willTriggerRelease) {
- message('This PR will not trigger a new version. It doesn\'t include any commit message with `feat` or `fix`.');
+ // `.spec.ts` is always used for Jest tests
+ // `.test.ts` is normally used for Tap tests and but there are also `.spec.ts` files which are used be Tap tests in test/acceptance.
+ // either way, we should warn about new `.test.ts` or `.spec.ts` files being created outside the `/test/jest` folder
+ const newTestFiles = danger.git.created_files.filter((f) => {
+ const inTestFolder = f.startsWith('test/');
+ const isATestFile = f.includes('.test.ts') || f.includes('.spec.ts');
+ const inJestFolder = f.startsWith('test/jest/');
+ const inFixturesFolder = f.startsWith('test/fixtures/');
+ return inTestFolder && isATestFile && !inJestFolder && !inFixturesFolder;
+ });
+
+ if (newTestFiles.length) {
+ const joinedFileList = newTestFiles.map((f) => '- `' + f + '`').join('\n');
+ const msg = `Looks like you added a new Tap test. Consider making it a Jest test instead. See files in \`test/jest/(unit|acceptance)\` for examples. Files found:\n${joinedFileList}`;
+ warn(msg);
+ }
+
+ // Smoke test modification check
+ const modifiedSmokeTest =
+ danger.git.modified_files.some((f) => f.startsWith('test/smoke/')) ||
+ danger.git.created_files.some((f) => f.startsWith('test/smoke/')) ||
+ danger.git.modified_files.includes(SMOKE_TEST_WORKFLOW_FILE_PATH);
+
+ const isOnSmokeTestBranch = danger.github.pr.head.ref.startsWith(
+ SMOKE_TEST_BRANCH,
+ );
+
+ if (modifiedSmokeTest && !isOnSmokeTestBranch) {
+ message(
+ `You are modifying something in \`test/smoke\` directory, yet you are not on the branch starting with ${SMOKE_TEST_BRANCH}. You can prefix your branch with ${SMOKE_TEST_BRANCH} and Smoke tests will trigger for this PR.`,
+ );
+ }
+
+ // Enforce usage of ES6 modules
+ const filesUsingNodeJSImportExport = danger.git.modified_files
+ .filter((filePath) => {
+ if (filePath.endsWith('.js')) {
+ return false;
+ }
+ const fileContent = fs.readFileSync(filePath, 'utf8');
+ return (
+ fileContent.includes('module.exports') ||
+ fileContent.includes('= require(')
+ );
+ })
+ .map((filePath) => `- \`${filePath}\``)
+ .join('\n');
+
+ if (filesUsingNodeJSImportExport) {
+ const message =
+ "Since the CLI is unifying on a standard and improved tooling, we're starting to migrate old-style `import`s and `export`s to ES6 ones.\nA file you've modified is using either `module.exports` or `require()`. If you can, please update them to ES6 [import syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) and [export syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export).\n Files found:\n" +
+ filesUsingNodeJSImportExport;
+ warn(message);
+ }
+
+ // Warn if changes to help files are created in snyk/snyk repo instead of snyk/user-docs
+ const modifiedHelp = danger.git.modified_files.some((f) =>
+ f.startsWith('help/'),
+ );
+ const createdHelp = danger.git.created_files.some((f) =>
+ f.startsWith('help/'),
+ );
+
+ if (modifiedHelp || createdHelp) {
+ warn(
+ 'Please make changes to `snyk help` text in [Gitbook](https://docs.snyk.io/snyk-cli/commands). Changes will be automatically synchronised to Snyk CLI as a [scheduled PR](https://github.com/snyk/snyk/actions/workflows/sync-cli-help-to-user-docs.yml).\nFor more information, see: [`help/README.md`](https://github.com/snyk/snyk/tree/master/help/README.md).',
+ );
}
}
diff --git a/docker-desktop/README.md b/docker-desktop/README.md
new file mode 100644
index 0000000000..011d757d32
--- /dev/null
+++ b/docker-desktop/README.md
@@ -0,0 +1,36 @@
+# Snyk CLI for Docker Desktop on macOS
+
+This distribution is not for customers! Releases are included with
+[Docker Desktop on macOS](https://www.docker.com/products/docker-desktop).
+
+If you are looking for Snyk CLI Docker Images, see
+[Docker Hub](https://hub.docker.com/r/snyk/snyk-cli).
+
+## How it works
+
+Unlike the `snyk-mac` binary build, the NodeJS release included with this
+distribution is a signed executable, which allows Docker Desktop to use it to
+execute Snyk CLI's underlying JavaScript build.
+
+## Building
+
+You must be at the root of the workspace. That is, one directory up from this
+repository. Then you can run:
+
+```sh
+./docker-desktop/build.sh darwin x64
+```
+
+This will create a tarball at:
+
+```sh
+./binary-releases/snyk-for-docker-desktop-darwin-x64.tar.gz
+```
+
+To test it, you can do the following:
+
+```sh
+cd ./binary-releases
+tar xzf snyk-for-docker-desktop-darwin-x64.tar.gz
+./docker/snyk-mac.sh woof
+```
diff --git a/docker-desktop/build.sh b/docker-desktop/build.sh
new file mode 100755
index 0000000000..f6a20ef435
--- /dev/null
+++ b/docker-desktop/build.sh
@@ -0,0 +1,56 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+platform="${1}"
+arch="${2}"
+node_version="v16.16.0"
+node_url="https://nodejs.org/dist/${node_version}/node-${node_version}-${platform}-${arch}.tar.gz"
+build_name="snyk-for-docker-desktop-${platform}-${arch}"
+build_filename="${build_name}.tar.gz"
+build_sha_filename="${build_filename}.sha256"
+build_root="./docker-desktop/dist/${build_name}"
+build_dir_name="docker"
+build_dir="${build_root}/${build_dir_name}"
+output_dir="./binary-releases"
+
+if [[ -d "${build_dir}" ]]; then
+ echo "ERROR: Build directory already exists."
+ echo " - ${build_dir}"
+ exit 1
+fi
+
+mkdir -p "${build_dir}"
+mkdir -p "${output_dir}"
+
+# Include entrypoint.
+cp ./docker-desktop/src/snyk-mac.sh "${build_dir}"
+
+# Include Snyk CLI build.
+cp ./package.json "${build_dir}"
+cp ./config.default.json "${build_dir}"
+cp -r ./dist "${build_dir}"
+cp -r ./bin "${build_dir}"
+cp -r ./pysrc "${build_dir}"
+cp -r --parents ./help/cli-commands "${build_dir}"
+
+# Include NodeJS.
+#
+# --strip-components=1 removes the versioned NodeJS directory so that we can
+# refer to the contents without needing to know the exact release name it came
+# from.
+mkdir "${build_dir}/node-release"
+pushd "${build_dir}/node-release"
+curl "${node_url}" | tar -xz --strip-components=1
+popd
+
+# Create Snyk CLI for Docker Desktop build
+#
+# We build from build_root so that build_name is the top-level directory in the
+# tarball. We want a top-level directory to avoid tarbombs.
+pushd "${build_root}"
+tar czfh "${build_filename}" "${build_dir_name}"
+shasum -a 256 "${build_filename}" > "${build_sha_filename}"
+popd
+
+mv "${build_root}/${build_filename}" "${output_dir}"
+mv "${build_root}/${build_sha_filename}" "${output_dir}"
diff --git a/docker-desktop/src/snyk-mac.sh b/docker-desktop/src/snyk-mac.sh
new file mode 100755
index 0000000000..706082f78e
--- /dev/null
+++ b/docker-desktop/src/snyk-mac.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+script_dir="$(dirname "$0")"
+"${script_dir}/node-release/bin/node" "${script_dir}/bin/snyk" "$@"
diff --git a/docker/Dockerfile.docker b/docker/Dockerfile.docker
index 9294893088..522e4fb821 100644
--- a/docker/Dockerfile.docker
+++ b/docker/Dockerfile.docker
@@ -1,4 +1,4 @@
-FROM node:10-slim
+FROM node:14-slim
MAINTAINER Snyk Ltd
@@ -7,7 +7,7 @@ RUN apt-get update && \
apt-get install -y apt-transport-https ca-certificates curl gnupg2 software-properties-common git && \
curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - && \
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable" && \
- apt-get update && apt-get install -y docker-ce jq && \
+ apt-get update && apt-get install -y docker-ce && \
npm install --global snyk snyk-to-html && \
apt-get autoremove -y && \
apt-get clean && \
@@ -20,7 +20,9 @@ ENV HOME /home/node
ENV PROJECT_PATH /project
COPY docker-entrypoint.sh .
-COPY snyk_report.css .
+
+ENV SNYK_INTEGRATION_NAME DOCKER_SNYK_CLI
+ENV SNYK_INTEGRATION_VERSION docker
ENTRYPOINT ["./docker-entrypoint.sh"]
diff --git a/docker/Dockerfile.gradle-2.8 b/docker/Dockerfile.gradle-2.8
index 6982c98a38..1d8d697bd0 100644
--- a/docker/Dockerfile.gradle-2.8
+++ b/docker/Dockerfile.gradle-2.8
@@ -10,8 +10,8 @@ RUN apt-get update && \
apt-get install -y curl unzip git && \
curl -L https://services.gradle.org/distributions/gradle-2.8-bin.zip -o gradle-2.8-bin.zip && \
unzip gradle-2.8-bin.zip -d /home/node/ &&\
- curl -sL https://deb.nodesource.com/setup_10.x | bash - && \
- apt-get install -y nodejs jq && \
+ curl -sL https://deb.nodesource.com/setup_14.x | bash - && \
+ apt-get install -y nodejs && \
npm install --global snyk snyk-to-html && \
apt-get autoremove -y && \
apt-get clean && \
@@ -26,7 +26,9 @@ ENV PATH=$PATH:$GRADLE_HOME/bin
ENV PROJECT_PATH /project
COPY docker-entrypoint.sh .
-COPY snyk_report.css .
+
+ENV SNYK_INTEGRATION_NAME DOCKER_SNYK_CLI
+ENV SNYK_INTEGRATION_VERSION gradle-2.8
ENTRYPOINT ["./docker-entrypoint.sh"]
diff --git a/docker/Dockerfile.gradle-4.4 b/docker/Dockerfile.gradle-4.4
index 0fe0db8261..2bdce208e5 100644
--- a/docker/Dockerfile.gradle-4.4
+++ b/docker/Dockerfile.gradle-4.4
@@ -10,8 +10,8 @@ RUN apt-get update && \
apt-get install -y curl unzip git && \
curl -L https://services.gradle.org/distributions/gradle-4.4-bin.zip -o gradle-4.4-bin.zip && \
unzip gradle-4.4-bin.zip -d /home/node/ && \
- curl -sL https://deb.nodesource.com/setup_10.x | bash - && \
- apt-get install -y nodejs jq && \
+ curl -sL https://deb.nodesource.com/setup_14.x | bash - && \
+ apt-get install -y nodejs && \
npm install --global snyk snyk-to-html && \
apt-get autoremove -y && \
apt-get clean && \
@@ -26,7 +26,9 @@ ENV PATH=$PATH:$GRADLE_HOME/bin
ENV PROJECT_PATH /project
COPY docker-entrypoint.sh .
-COPY snyk_report.css .
+
+ENV SNYK_INTEGRATION_NAME DOCKER_SNYK_CLI
+ENV SNYK_INTEGRATION_VERSION gradle-4.4
ENTRYPOINT ["./docker-entrypoint.sh"]
diff --git a/docker/Dockerfile.gradle-5.4 b/docker/Dockerfile.gradle-5.4
index 496cbb97c7..51e787de35 100644
--- a/docker/Dockerfile.gradle-5.4
+++ b/docker/Dockerfile.gradle-5.4
@@ -10,8 +10,8 @@ RUN apt-get update && \
apt-get install -y curl unzip git && \
curl -L https://services.gradle.org/distributions/gradle-5.4-bin.zip -o gradle-5.4-bin.zip && \
unzip gradle-5.4-bin.zip -d /home/node/ && \
- curl -sL https://deb.nodesource.com/setup_10.x | bash - && \
- apt-get install -y nodejs jq && \
+ curl -sL https://deb.nodesource.com/setup_14.x | bash - && \
+ apt-get install -y nodejs && \
npm install --global snyk snyk-to-html && \
apt-get autoremove -y && \
apt-get clean && \
@@ -26,7 +26,9 @@ ENV PATH=$PATH:$GRADLE_HOME/bin
ENV PROJECT_PATH /project
COPY docker-entrypoint.sh .
-COPY snyk_report.css .
+
+ENV SNYK_INTEGRATION_NAME DOCKER_SNYK_CLI
+ENV SNYK_INTEGRATION_VERSION gradle-5.4
ENTRYPOINT ["./docker-entrypoint.sh"]
diff --git a/docker/Dockerfile.gradle-5.4_java11 b/docker/Dockerfile.gradle-5.4_java11
index b7539fab3e..33d4d93da2 100644
--- a/docker/Dockerfile.gradle-5.4_java11
+++ b/docker/Dockerfile.gradle-5.4_java11
@@ -10,9 +10,10 @@ RUN apt-get update && \
apt-get install -y curl unzip && \
curl -L https://services.gradle.org/distributions/gradle-5.4-bin.zip -o gradle-5.4-bin.zip && \
unzip gradle-5.4-bin.zip -d /home/node/ && \
- curl -sL https://deb.nodesource.com/setup_8.x | bash - && \
- apt-get install -y nodejs jq npm && \
+ curl -sL https://deb.nodesource.com/setup_14.x | bash - && \
+ apt-get install -y nodejs && \
node -v && \
+ npm -v && \
npm install --global snyk snyk-to-html && \
apt-get autoremove -y && \
apt-get clean && \
@@ -27,7 +28,6 @@ ENV PATH=$PATH:$GRADLE_HOME/bin
ENV PROJECT_PATH /project
COPY docker-entrypoint.sh .
-COPY snyk_report.css .
ENTRYPOINT ["./docker-entrypoint.sh"]
diff --git a/docker/Dockerfile.maven-3.5.4 b/docker/Dockerfile.maven-3.5.4
index 6ef99042e7..3caa37ef6d 100644
--- a/docker/Dockerfile.maven-3.5.4
+++ b/docker/Dockerfile.maven-3.5.4
@@ -11,8 +11,8 @@ RUN apt-get update && \
curl -L -o apache-maven-3.5.4-bin.tar.gz https://www-eu.apache.org/dist/maven/maven-3/3.5.4/binaries/apache-maven-3.5.4-bin.tar.gz && \
tar -xvzf apache-maven-3.5.4-bin.tar.gz && \
rm -f apache-maven-3.5.4-bin.tar.gz && \
- curl -sL https://deb.nodesource.com/setup_10.x | bash - && \
- apt-get install -y nodejs jq && \
+ curl -sL https://deb.nodesource.com/setup_14.x | bash - && \
+ apt-get install -y nodejs && \
npm install --global snyk snyk-to-html && \
apt-get autoremove -y && \
apt-get clean && \
@@ -26,7 +26,9 @@ ENV PATH /home/node/apache-maven-3.5.4/bin:$PATH
ENV PROJECT_PATH /project
ADD docker-entrypoint.sh .
-ADD snyk_report.css .
+
+ENV SNYK_INTEGRATION_NAME DOCKER_SNYK_CLI
+ENV SNYK_INTEGRATION_VERSION maven-3.5.4
ENTRYPOINT ["./docker-entrypoint.sh"]
diff --git a/docker/Dockerfile.maven-3.6.1 b/docker/Dockerfile.maven-3.6.1
deleted file mode 100644
index 4d9ad61cb0..0000000000
--- a/docker/Dockerfile.maven-3.6.1
+++ /dev/null
@@ -1,35 +0,0 @@
-FROM openjdk:8-jdk-slim
-
-MAINTAINER Snyk Ltd
-
-RUN mkdir /home/node
-WORKDIR /home/node
-
-# Install maven, node, cli
-RUN apt-get update && \
- apt-get install -y curl git && \
- curl -L -o apache-maven-3.6.1-bin.tar.gz https://www-eu.apache.org/dist/maven/maven-3/3.6.1/binaries/apache-maven-3.6.1-bin.tar.gz && \
- tar -xvzf apache-maven-3.6.1-bin.tar.gz && \
- rm -f apache-maven-3.6.1-bin.tar.gz && \
- curl -sL https://deb.nodesource.com/setup_10.x | bash - && \
- apt-get install -y nodejs jq && \
- npm install --global snyk snyk-to-html && \
- apt-get autoremove -y && \
- apt-get clean && \
- chmod -R a+wrx /home/node
-
-ENV HOME /home/node
-ENV M2 /home/node/.m2
-ENV PATH /home/node/apache-maven-3.6.1/bin:$PATH
-
-# The path at which the project is mounted (-v runtime arg)
-ENV PROJECT_PATH /project
-
-ADD docker-entrypoint.sh .
-ADD snyk_report.css .
-
-ENTRYPOINT ["./docker-entrypoint.sh"]
-
-# Default command is `snyk test`
-# Override with `docker run ... snyk/snyk-cli `
-CMD ["test"]
diff --git a/docker/Dockerfile.maven-3.6.1_java11 b/docker/Dockerfile.maven-3.6.1_java11
deleted file mode 100644
index f056a821ee..0000000000
--- a/docker/Dockerfile.maven-3.6.1_java11
+++ /dev/null
@@ -1,35 +0,0 @@
-FROM openjdk:11-jdk-slim
-
-MAINTAINER Snyk Ltd
-
-RUN mkdir /home/node
-WORKDIR /home/node
-
-# Install maven, node, cli
-RUN apt-get update && \
- apt-get install -y curl git && \
- curl -L -o apache-maven-3.6.1-bin.tar.gz https://www-eu.apache.org/dist/maven/maven-3/3.6.1/binaries/apache-maven-3.6.1-bin.tar.gz && \
- tar -xvzf apache-maven-3.6.1-bin.tar.gz && \
- rm -f apache-maven-3.6.1-bin.tar.gz && \
- curl -sL https://deb.nodesource.com/setup_10.x | bash - && \
- apt-get install -y nodejs jq && \
- npm install --global snyk snyk-to-html && \
- apt-get autoremove -y && \
- apt-get clean && \
- chmod -R a+wrx /home/node
-
-ENV HOME /home/node
-ENV M2 /home/node/.m2
-ENV PATH /home/node/apache-maven-3.6.1/bin:$PATH
-
-# The path at which the project is mounted (-v runtime arg)
-ENV PROJECT_PATH /project
-
-ADD docker-entrypoint.sh .
-ADD snyk_report.css .
-
-ENTRYPOINT ["./docker-entrypoint.sh"]
-
-# Default command is `snyk test`
-# Override with `docker run ... snyk/snyk-cli `
-CMD ["test"]
diff --git a/docker/Dockerfile.maven-3.6.3 b/docker/Dockerfile.maven-3.6.3
new file mode 100644
index 0000000000..523c75b72a
--- /dev/null
+++ b/docker/Dockerfile.maven-3.6.3
@@ -0,0 +1,37 @@
+FROM openjdk:8-jdk-slim
+
+MAINTAINER Snyk Ltd
+
+RUN mkdir /home/node
+WORKDIR /home/node
+
+# Install maven, node, cli
+RUN apt-get update && \
+ apt-get install -y curl git && \
+ curl -L -o apache-maven-3.6.3-bin.tar.gz https://www-eu.apache.org/dist/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz && \
+ tar -xvzf apache-maven-3.6.3-bin.tar.gz && \
+ rm -f apache-maven-3.6.3-bin.tar.gz && \
+ curl -sL https://deb.nodesource.com/setup_14.x | bash - && \
+ apt-get install -y nodejs && \
+ npm install --global snyk snyk-to-html && \
+ apt-get autoremove -y && \
+ apt-get clean && \
+ chmod -R a+wrx /home/node
+
+ENV HOME /home/node
+ENV M2 /home/node/.m2
+ENV PATH /home/node/apache-maven-3.6.3/bin:$PATH
+
+# The path at which the project is mounted (-v runtime arg)
+ENV PROJECT_PATH /project
+
+ADD docker-entrypoint.sh .
+
+ENV SNYK_INTEGRATION_NAME DOCKER_SNYK_CLI
+ENV SNYK_INTEGRATION_VERSION maven-3.6.3
+
+ENTRYPOINT ["./docker-entrypoint.sh"]
+
+# Default command is `snyk test`
+# Override with `docker run ... snyk/snyk-cli `
+CMD ["test"]
diff --git a/docker/Dockerfile.maven-3.6.3_java11 b/docker/Dockerfile.maven-3.6.3_java11
new file mode 100644
index 0000000000..2feb14e02c
--- /dev/null
+++ b/docker/Dockerfile.maven-3.6.3_java11
@@ -0,0 +1,37 @@
+FROM openjdk:11-jdk-slim
+
+MAINTAINER Snyk Ltd
+
+RUN mkdir /home/node
+WORKDIR /home/node
+
+# Install maven, node, cli
+RUN apt-get update && \
+ apt-get install -y curl git && \
+ curl -L -o apache-maven-3.6.3-bin.tar.gz https://www-eu.apache.org/dist/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz && \
+ tar -xvzf apache-maven-3.6.3-bin.tar.gz && \
+ rm -f apache-maven-3.6.3-bin.tar.gz && \
+ curl -sL https://deb.nodesource.com/setup_14.x | bash - && \
+ apt-get install -y nodejs && \
+ npm install --global snyk snyk-to-html && \
+ apt-get autoremove -y && \
+ apt-get clean && \
+ chmod -R a+wrx /home/node
+
+ENV HOME /home/node
+ENV M2 /home/node/.m2
+ENV PATH /home/node/apache-maven-3.6.3/bin:$PATH
+
+# The path at which the project is mounted (-v runtime arg)
+ENV PROJECT_PATH /project
+
+ADD docker-entrypoint.sh .
+
+ENV SNYK_INTEGRATION_NAME DOCKER_SNYK_CLI
+ENV SNYK_INTEGRATION_VERSION maven-3.6.3_java11
+
+ENTRYPOINT ["./docker-entrypoint.sh"]
+
+# Default command is `snyk test`
+# Override with `docker run ... snyk/snyk-cli `
+CMD ["test"]
diff --git a/docker/Dockerfile.npm_ruby b/docker/Dockerfile.npm_ruby
index 85f53b40c4..4d5e92044b 100644
--- a/docker/Dockerfile.npm_ruby
+++ b/docker/Dockerfile.npm_ruby
@@ -1,4 +1,4 @@
-FROM node:10-slim
+FROM node:14-slim
MAINTAINER Snyk Ltd
@@ -7,7 +7,7 @@ ENV HOME /home/node
# Install snyk cli and clean up
RUN apt-get update && \
- apt-get install -y jq git && \
+ apt-get install -y git && \
npm install --global snyk snyk-to-html && \
apt-get autoremove -y && \
apt-get clean && \
@@ -17,11 +17,14 @@ RUN apt-get update && \
ENV PROJECT_PATH /project
COPY docker-entrypoint.sh .
-COPY snyk_report.css .
+
+ENV SNYK_INTEGRATION_NAME DOCKER_SNYK_CLI
+# This image is currently being used for nuget, composer and ruby
+# If we see a tons of usage, we can split the analytics
+ENV SNYK_INTEGRATION_VERSION npm
ENTRYPOINT ["./docker-entrypoint.sh"]
# Default command is `snyk test`
# Override with `docker run ... snyk/snyk-cli `
CMD ["test"]
-
diff --git a/docker/Dockerfile.python-2 b/docker/Dockerfile.python-2
index ed050585ea..4425e470e2 100644
--- a/docker/Dockerfile.python-2
+++ b/docker/Dockerfile.python-2
@@ -6,11 +6,11 @@ RUN mkdir /home/node
WORKDIR /home/node
# Install Python utilities, node, Snyk CLI
-RUN pip install pip pipenv virtualenv -U && \
+RUN pip install pip pipenv==2021.5.29 virtualenv -U && \
apt-get update && \
apt-get install -y build-essential curl git && \
- curl -sL https://deb.nodesource.com/setup_10.x | bash - && \
- apt-get install -y nodejs jq && \
+ curl -sL https://deb.nodesource.com/setup_14.x | bash - && \
+ apt-get install -y nodejs && \
npm install --global snyk snyk-to-html && \
apt-get autoremove -y && \
apt-get clean && \
@@ -23,11 +23,12 @@ ENV PROJECT_PATH /project
COPY docker-python-entrypoint.sh .
COPY docker-entrypoint.sh .
-COPY snyk_report.css .
+
+ENV SNYK_INTEGRATION_NAME DOCKER_SNYK_CLI
+ENV SNYK_INTEGRATION_VERSION python-2
ENTRYPOINT ["./docker-python-entrypoint.sh"]
# Default command is `snyk test`
# Override with `docker run ... snyk/snyk-cli `
CMD ["test"]
-
diff --git a/docker/Dockerfile.python-3 b/docker/Dockerfile.python-3
index a3c9e4e566..6fe53c64b6 100644
--- a/docker/Dockerfile.python-3
+++ b/docker/Dockerfile.python-3
@@ -6,11 +6,11 @@ RUN mkdir /home/node
WORKDIR /home/node
# Install Python utilities, node, Snyk CLI
-RUN pip install pip pipenv virtualenv -U && \
+RUN pip install pip pipenv==2021.5.29 virtualenv -U && \
apt-get update && \
apt-get install -y build-essential curl git && \
- curl -sL https://deb.nodesource.com/setup_10.x | bash - && \
- apt-get install -y nodejs jq && \
+ curl -sL https://deb.nodesource.com/setup_14.x | bash - && \
+ apt-get install -y nodejs && \
npm install --global snyk snyk-to-html && \
apt-get autoremove -y && \
apt-get clean && \
@@ -23,11 +23,12 @@ ENV PROJECT_PATH /project
COPY docker-python-entrypoint.sh .
COPY docker-entrypoint.sh .
-COPY snyk_report.css .
+
+ENV SNYK_INTEGRATION_NAME DOCKER_SNYK_CLI
+ENV SNYK_INTEGRATION_VERSION python-3
ENTRYPOINT ["./docker-python-entrypoint.sh"]
# Default command is `snyk test`
# Override with `docker run ... snyk/snyk-cli `
CMD ["test"]
-
diff --git a/docker/Dockerfile.python-3.6 b/docker/Dockerfile.python-3.6
new file mode 100644
index 0000000000..3f312857ff
--- /dev/null
+++ b/docker/Dockerfile.python-3.6
@@ -0,0 +1,34 @@
+FROM python:3.6-slim
+
+MAINTAINER Snyk Ltd
+
+RUN mkdir /home/node
+WORKDIR /home/node
+
+# Install Python utilities, node, Snyk CLI
+RUN pip install pip pipenv==2021.5.29 virtualenv -U && \
+ apt-get update && \
+ apt-get install -y build-essential curl git && \
+ curl -sL https://deb.nodesource.com/setup_14.x | bash - && \
+ apt-get install -y nodejs && \
+ npm install --global snyk snyk-to-html && \
+ apt-get autoremove -y && \
+ apt-get clean && \
+ chmod -R a+wrx /home/node
+
+ENV HOME /home/node
+
+# The path at which the project is mounted (-v runtime arg)
+ENV PROJECT_PATH /project
+
+COPY docker-python-entrypoint.sh .
+COPY docker-entrypoint.sh .
+
+ENV SNYK_INTEGRATION_NAME DOCKER_SNYK_CLI
+ENV SNYK_INTEGRATION_VERSION python-3.6
+
+ENTRYPOINT ["./docker-python-entrypoint.sh"]
+
+# Default command is `snyk test`
+# Override with `docker run ... snyk/snyk-cli `
+CMD ["test"]
diff --git a/docker/Dockerfile.python-3.8 b/docker/Dockerfile.python-3.8
new file mode 100644
index 0000000000..1797397e72
--- /dev/null
+++ b/docker/Dockerfile.python-3.8
@@ -0,0 +1,34 @@
+FROM python:3.8-slim
+
+MAINTAINER Snyk Ltd
+
+RUN mkdir /home/node
+WORKDIR /home/node
+
+# Install Python utilities, node, Snyk CLI
+RUN pip install pip pipenv==2021.5.29 virtualenv -U && \
+ apt-get update && \
+ apt-get install -y build-essential curl git && \
+ curl -sL https://deb.nodesource.com/setup_14.x | bash - && \
+ apt-get install -y nodejs && \
+ npm install --global snyk snyk-to-html && \
+ apt-get autoremove -y && \
+ apt-get clean && \
+ chmod -R a+wrx /home/node
+
+ENV HOME /home/node
+
+# The path at which the project is mounted (-v runtime arg)
+ENV PROJECT_PATH /project
+
+COPY docker-python-entrypoint.sh .
+COPY docker-entrypoint.sh .
+
+ENV SNYK_INTEGRATION_NAME DOCKER_SNYK_CLI
+ENV SNYK_INTEGRATION_VERSION python-3.8
+
+ENTRYPOINT ["./docker-python-entrypoint.sh"]
+
+# Default command is `snyk test`
+# Override with `docker run ... snyk/snyk-cli `
+CMD ["test"]
diff --git a/docker/Dockerfile.python-3.9 b/docker/Dockerfile.python-3.9
new file mode 100644
index 0000000000..bef2974289
--- /dev/null
+++ b/docker/Dockerfile.python-3.9
@@ -0,0 +1,34 @@
+FROM python:3.9-slim
+
+MAINTAINER Snyk Ltd
+
+RUN mkdir /home/node
+WORKDIR /home/node
+
+# Install Python utilities, node, Snyk CLI
+RUN pip install pip pipenv==2021.5.29 virtualenv -U && \
+ apt-get update && \
+ apt-get install -y build-essential curl git && \
+ curl -sL https://deb.nodesource.com/setup_14.x | bash - && \
+ apt-get install -y nodejs && \
+ npm install --global snyk snyk-to-html && \
+ apt-get autoremove -y && \
+ apt-get clean && \
+ chmod -R a+wrx /home/node
+
+ENV HOME /home/node
+
+# The path at which the project is mounted (-v runtime arg)
+ENV PROJECT_PATH /project
+
+COPY docker-python-entrypoint.sh .
+COPY docker-entrypoint.sh .
+
+ENV SNYK_INTEGRATION_NAME DOCKER_SNYK_CLI
+ENV SNYK_INTEGRATION_VERSION python-3.9
+
+ENTRYPOINT ["./docker-python-entrypoint.sh"]
+
+# Default command is `snyk test`
+# Override with `docker run ... snyk/snyk-cli `
+CMD ["test"]
diff --git a/docker/Dockerfile.sbt-0.13.16 b/docker/Dockerfile.sbt-0.13.16
index 69d1b54c4b..98c1d53081 100644
--- a/docker/Dockerfile.sbt-0.13.16
+++ b/docker/Dockerfile.sbt-0.13.16
@@ -22,8 +22,8 @@ RUN apt-get update && \
echo "net.virtualvoid.sbt.graph.DependencyGraphSettings.graphSettings" >> /root/.sbt/0.13/user.sbt && \
echo "net.virtualvoid.sbt.graph.DependencyGraphSettings.graphSettings" >> /home/node/.sbt/0.13/user.sbt && \
echo "-sbt-version 0.13.16" >> /etc/sbt/sbtopts && \
- curl -sL https://deb.nodesource.com/setup_10.x | bash - && \
- apt-get install -y nodejs jq && \
+ curl -sL https://deb.nodesource.com/setup_14.x | bash - && \
+ apt-get install -y nodejs && \
npm install --global snyk snyk-to-html && \
apt-get autoremove -y && \
apt-get clean && \
@@ -36,7 +36,9 @@ ENV M2 /home/node/.m2
ENV PROJECT_PATH /project
COPY docker-entrypoint.sh .
-COPY snyk_report.css .
+
+ENV SNYK_INTEGRATION_NAME DOCKER_SNYK_CLI
+ENV SNYK_INTEGRATION_VERSION sbt-0.13.16
ENTRYPOINT ["./docker-entrypoint.sh"]
diff --git a/docker/Dockerfile.sbt-1.0.4 b/docker/Dockerfile.sbt-1.0.4
index ca7bb1a8f9..993d6e1757 100644
--- a/docker/Dockerfile.sbt-1.0.4
+++ b/docker/Dockerfile.sbt-1.0.4
@@ -21,8 +21,8 @@ RUN apt-get update && \
echo "addSbtPlugin(\"net.virtual-void\" % \"sbt-dependency-graph\" % \"0.9.0\")" >> /home/node/.sbt/1.0/plugins/plugins.sbt && \
echo "addCommandAlias(\"dependency-tree\", \"dependencyTree\")" >> /root/.sbt/1.0/user.sbt && \
echo "addCommandAlias(\"dependency-tree\", \"dependencyTree\")" >> /home/node/.sbt/1.0/user.sbt && \
- curl -sL https://deb.nodesource.com/setup_10.x | bash - && \
- apt-get install -y nodejs jq && \
+ curl -sL https://deb.nodesource.com/setup_14.x | bash - && \
+ apt-get install -y nodejs && \
npm install --global snyk snyk-to-html && \
apt-get autoremove -y && \
apt-get clean && \
@@ -35,7 +35,9 @@ ENV M2 /home/node/.m2
ENV PROJECT_PATH /project
COPY docker-entrypoint.sh .
-COPY snyk_report.css .
+
+ENV SNYK_INTEGRATION_NAME DOCKER_SNYK_CLI
+ENV SNYK_INTEGRATION_VERSION sbt-1.0.4
ENTRYPOINT ["./docker-entrypoint.sh"]
diff --git a/docker/README.md b/docker/README.md
new file mode 100644
index 0000000000..87919246d3
--- /dev/null
+++ b/docker/README.md
@@ -0,0 +1,5 @@
+# Deprecation Notice
+
+Please note, these Docker images are deprecated and may be removed in the future. They remain available only to provide continuity for legacy Snyk integrations. **Snyk does not recommend their use.**
+
+Instead, please use the [`snyk/snyk`](https://hub.docker.com/r/snyk/snyk) Docker images. These images wrap the Snyk CLI and depending on the Tag come with a relevant tooling for different projects. [See the snyk/images on GitHub for more details and examples](https://github.com/snyk/snyk-images).
diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh
index cac7878d79..6e9e258b70 100755
--- a/docker/docker-entrypoint.sh
+++ b/docker/docker-entrypoint.sh
@@ -20,13 +20,13 @@ fi
useradd -o -m -u "${USER_ID}" -d /home/node docker-user 2>/dev/null
-runCmdAsDockerUser () {
+runCmdAsDockerUser() {
su docker-user -m -c "$1"
return $?
}
-exitWithMsg () {
+exitWithMsg() {
echo "Failed to run the process ..."
if [ -f "$1" ]; then
@@ -38,20 +38,12 @@ exitWithMsg () {
exit "$2"
}
-##
-## Start of backward compatability code.
-## Should be phased out when we phase out the current version of the jenkins
-## plugin.
-## These parameters should only be used with the Jenkins plugin! Please see
-## README.md for more info.
-##
-
-TEST_SETTINGS="";
+TEST_SETTINGS=""
PROJECT_SUBDIR=""
if [ -n "${TARGET_FILE}" ]; then
if [ ! -f "${PROJECT_PATH}/${PROJECT_FOLDER}/${TARGET_FILE}" ]; then
- exitWithMsg "\"${PROJECT_PATH}/${PROJECT_FOLDER}/${TARGET_FILE}\" does not exist" 1
+ exitWithMsg "\"${PROJECT_PATH}/${PROJECT_FOLDER}/${TARGET_FILE}\" does not exist" 2
fi
PROJECT_SUBDIR=$(dirname "${TARGET_FILE}")
@@ -65,22 +57,18 @@ fi
SNYK_PARAMS="${SNYK_PARAMS} ${TEST_SETTINGS}"
-##
-## End of backward compatability code
-##
-
if [ -z "${SNYK_TOKEN}" ]; then
- exitWithMsg "Missing \${SNYK_TOKEN}" 1
+ exitWithMsg "Missing \${SNYK_TOKEN}" 2
fi
if [ -n "${ENV_FLAGS}" ]; then
ADDITIONAL_ENV="-- ${ENV_FLAGS}"
fi
-cd "${PROJECT_PATH}/${PROJECT_FOLDER}/${PROJECT_SUBDIR}" || \
-exitWithMsg "Can't cd to ${PROJECT_PATH}/${PROJECT_FOLDER}/${PROJECT_SUBDIR}" 1
+cd "${PROJECT_PATH}/${PROJECT_FOLDER}/${PROJECT_SUBDIR}" ||
+ exitWithMsg "Can't cd to ${PROJECT_PATH}/${PROJECT_FOLDER}/${PROJECT_SUBDIR}" 2
-runCmdAsDockerUser "PATH=${PATH} snyk ${SNYK_COMMAND} ${SNYK_PARAMS} \
+runCmdAsDockerUser "PATH=${PATH} snyk ${SNYK_COMMAND} --json ${SNYK_PARAMS} \
${ADDITIONAL_ENV} > \"${OUTPUT_FILE}\" 2>\"${ERROR_FILE}\""
RC=$?
@@ -89,33 +77,14 @@ if [ "$RC" -ne "0" ] && [ "$RC" -ne "1" ]; then
exitWithMsg "${OUTPUT_FILE}" "$RC"
fi
-#
-# Commented out the condition because we want to always generate the html
-# file until we phase out the old version of the Jenkins plugin.
-# TODO: Re-add this option to documentation once back
-#
-# - `GENERATE_REPORT` - [OPTIONAL] if set, this will generate the HTML report
-# with a summary of the vulnerabilities detected by snyk.
-#
-# if [ -n $GENERATE_REPORT ]; then
runCmdAsDockerUser "touch \"${PROJECT_PATH}/${PROJECT_FOLDER}/${HTML_FILE}\""
if [ -n "$MONITOR" ]; then
+ echo "Monitoring & generating report ..."
runCmdAsDockerUser "PATH=$PATH snyk monitor --json ${SNYK_PARAMS} ${ADDITIONAL_ENV} > ${MONITOR_OUTPUT_FILE} 2>$ERROR_FILE"
- runCmdAsDockerUser "cat ${MONITOR_OUTPUT_FILE} | jq -r \".uri\" | awk '{print \"View On Snyk.io \"}' > \"${PROJECT_PATH}/${PROJECT_FOLDER}/${HTML_FILE}\" 2>>\"${ERROR_FILE}\""
fi
-
-runCmdAsDockerUser "cat \"${OUTPUT_FILE}\" | \
-jq 'def sortBySeverity: .vulnerabilities|= map(. + {severity_numeric: (if(.severity) == \"high\" then 1 else (if(.severity) == \"medium\" then 2 else (if(.severity) == \"low\" then 3 else 4 end) end) end)}) |.vulnerabilities |= sort_by(.severity_numeric) | del(.vulnerabilities[].severity_numeric); if (. | type) == \"array\" then map(sortBySeverity) else sortBySeverity end'| \
-snyk-to-html | \
-sed 's/<\/head>/ <\/head>/' \
->> \"${PROJECT_PATH}/${PROJECT_FOLDER}/${HTML_FILE}\""
-
-runCmdAsDockerUser "cat /home/node/snyk_report.css > \
-\"${PROJECT_PATH}/${PROJECT_FOLDER}/snyk_report.css\""
-# fi
-#
+runCmdAsDockerUser "cat \"${OUTPUT_FILE}\" | snyk-to-html >> \"${PROJECT_PATH}/${PROJECT_FOLDER}/${HTML_FILE}\""
if [ $RC -ne "0" ]; then
exitWithMsg "${OUTPUT_FILE}" "$RC"
diff --git a/docker/docker-python-entrypoint.sh b/docker/docker-python-entrypoint.sh
index 7c8881f10a..27e4b76b67 100755
--- a/docker/docker-python-entrypoint.sh
+++ b/docker/docker-python-entrypoint.sh
@@ -1,6 +1,74 @@
#!/bin/bash
-
virtualenv -p python snyk
source snyk/bin/activate
-pip install -U -r "${PROJECT_PATH}/requirements.txt"
+
+exitWithMsg() {
+ echo "Failed to run the process ..."
+
+ if [ -f "$1" ]; then
+ cat "$1"
+ else
+ echo "$1"
+ fi
+
+ exit "$2"
+}
+
+installRequirementsTxtDeps() {
+ echo "Installing dependencies from requirements file"
+ pip install -U -r "$1"
+}
+
+installPipfileDeps() {
+ pushd "${PROJECT_PATH}/"
+ echo "Found Pipfile"
+ pipenv install --system --deploy
+ popd
+}
+
+PROJECT_SUBDIR=""
+echo "Project path = ${PROJECT_PATH}"
+if [ -n "${TARGET_FILE}" ]; then
+ if [ ! -f "${PROJECT_PATH}/${PROJECT_FOLDER}/${TARGET_FILE}" ]; then
+ exitWithMsg "\"${PROJECT_PATH}/${PROJECT_FOLDER}/${TARGET_FILE}\" does not exist" 2
+ fi
+
+ PROJECT_SUBDIR=$(dirname "${TARGET_FILE}")
+ MANIFEST_NAME=$(basename "${TARGET_FILE}")
+ TEST_SETTINGS="--file=${MANIFEST_NAME} "
+
+ echo "Target file = ${TARGET_FILE}"
+
+ case $MANIFEST_NAME in
+ *req*.txt)
+ echo "Installing dependencies from requirements file"
+ installRequirementsTxtDeps "${PROJECT_PATH}/$MANIFEST_NAME"
+ ;;
+ *setup.py)
+ echo "Installing dependencies from setup.py"
+ pip install -U -e "${PROJECT_PATH}"
+ ;;
+ *Pipfile)
+ echo "Installing dependencies from Pipfile"
+ installPipfileDeps
+ ;;
+ *)
+ exitWithMsg "\"${PROJECT_PATH}/${TARGET_FILE}\" is not supported" 3
+ ;;
+ esac
+fi
+
+if [ -z "${TARGET_FILE}" ]; then
+ if [ -f "${PROJECT_PATH}/requirements.txt" ]; then
+ echo "Found requirement.txt"
+ installRequirementsTxtDeps "${PROJECT_PATH}/requirements.txt"
+ elif [ -f "${PROJECT_PATH}/setup.py" ]; then
+ echo "Found setup.py"
+ pip install -U -e "${PROJECT_PATH}"
+ elif [ -f "${PROJECT_PATH}/Pipfile" ]; then
+ echo "Found Pipfile"
+ installPipfileDeps
+ fi
+fi
+
bash docker-entrypoint.sh "$@"
diff --git a/docker/snyk_report.css b/docker/snyk_report.css
deleted file mode 100644
index 86202f197f..0000000000
--- a/docker/snyk_report.css
+++ /dev/null
@@ -1,263 +0,0 @@
-body {
- -moz-font-feature-settings: "pnum";
- -webkit-font-feature-settings: "pnum";
- font-variant-numeric: proportional-nums;
- display: flex;
- flex-direction: column;
- font-feature-settings: "pnum";
- font-size: 100%;
- line-height: 1.5;
- min-height: 100vh;
- -webkit-text-size-adjust: 100%;
- margin: 0;
- padding: 0;
- background-color: #F5F5F5;
- font-family: 'Arial', 'Helvetica', Calibri, sans-serif;
-}
-
-h1,
-h2,
-h3,
-h4,
-h5,
-h6 {
- font-weight: 500;
-}
-
-a,
-a:link,
-a:visited {
- border-bottom: 1px solid #4b45a9;
- text-decoration: none;
- color: #4b45a9;
-}
-
-a:hover,
-a:focus,
-a:active {
- border-bottom: 2px solid #4b45a9;
-}
-
-hr {
- border: none;
- margin: 1em 0;
- border-top: 1px solid #c5c5c5;
-}
-
-ul {
- padding: 0 1em;
- margin: 1em 0;
-}
-
-code {
- background-color: #EEE;
- color: #333;
- padding: 0.25em 0.5em;
- border-radius: 0.25em;
-}
-
-pre {
- background-color: #333;
- font-family: monospace;
- padding: 0.5em 1em 0.75em;
- border-radius: 0.25em;
- font-size: 14px;
-}
-
-pre code {
- padding: 0;
- background-color: transparent;
- color: #fff;
-}
-
-a code {
- border-radius: .125rem .125rem 0 0;
- padding-bottom: 0;
- color: #4b45a9;
-}
-
-a[href^="http://"]:after,
-a[href^="https://"]:after {
- background-image: linear-gradient(transparent,transparent),url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20112%20109%22%3E%3Cg%20id%3D%22Page-1%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Cg%20id%3D%22link-external%22%3E%3Cg%20id%3D%22arrow%22%3E%3Cpath%20id%3D%22Line%22%20stroke%3D%22%234B45A9%22%20stroke-width%3D%2215%22%20d%3D%22M88.5%2021l-43%2042.5%22%20stroke-linecap%3D%22square%22%2F%3E%3Cpath%20id%3D%22Triangle%22%20fill%3D%22%234B45A9%22%20d%3D%22M111.2%200v50L61%200z%22%2F%3E%3C%2Fg%3E%3Cpath%20id%3D%22square%22%20fill%3D%22%234B45A9%22%20d%3D%22M66%2015H0v94h94V44L79%2059v35H15V30h36z%22%2F%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
- background-repeat: no-repeat;
- background-size: .75rem;
- content: "";
- display: inline-block;
- height: .75rem;
- margin-left: .25rem;
- width: .75rem;
-}
-
-
-/* Layout */
-
-[class*=layout-container] {
- margin: 0 auto;
- max-width: 71.25em;
- padding: 1.9em 1.3em;
- position: relative;
-}
-.layout-container--short {
- padding-top: 0;
- padding-bottom: 0;
-}
-
-.layout-container--short:after {
- display: block;
- content: "";
- clear: both;
-}
-
-/* Header */
-
-.header {
- padding-bottom: 1px;
-}
-
-.project__header {
- background-color: #4C4A73;
- color: #fff;
- margin-bottom: -1px;
- padding-top: 1em;
- padding-bottom: 0.25em;
- border-bottom: 2px solid #BBB;
-}
-
-.project__header__title {
- overflow-wrap: break-word;
- word-wrap: break-word;
- word-break: break-all;
- margin-bottom: .1em;
- margin-top: 0;
- float: left;
-}
-
-.timestamp {
- float: right;
- clear: none;
- margin-bottom: 0;
-}
-
-.meta-counts {
- clear: both;
- display: block;
- flex-wrap: wrap;
- justify-content: space-between;
- margin: 0 0 1.5em;
- color: #fff;
- clear: both;
- font-size: 1.1em;
-}
-
-.meta-count {
- display: block;
- flex-basis: 100%;
- margin: 0 1em 1em 0;
- float: left;
- padding-right: 1em;
- border-right: 2px solid #fff;
-}
-
-.meta-count:last-child {
- border-right: 0;
- padding-right: 0;
- margin-right: 0;
-}
-
-/* Card */
-
-.card {
- background-color: #fff;
- border: 1px solid #c5c5c5;
- border-radius: .25rem;
- margin: 0 0 2em 0;
- position: relative;
- min-height: 40px;
- padding: 1.5em;
-}
-
-.card .label {
- background-color: #767676;
- border: 2px solid #767676;
- color: white;
- padding: 0.25rem 0.75rem;
- font-size: 0.875rem;
- text-transform: uppercase;
- display: inline-block;
- margin: 0;
- border-radius: 0.25rem;
-}
-
-.card .label__text {
- vertical-align: text-top;
-}
-
-.card .label--high {
- background-color: #B51B72;
- border-color: #B51B72;
-}
-
-.card .label--medium {
- background-color: #E29022;
- border-color: #E29022;
-}
-
-.card .label--low {
- background-color: #222049;
- border-color: #222049;
-}
-
-.card .card.severity--low {
- border-color: #222049;
-}
-
-.card .card.severity--medium {
- border-color: #E29022;
-}
-
-.card .card.severity--high {
- border-color: #B51B72;
-}
-
-.card--vuln {
- padding-top: 4em;
- max-width: 48.75em;
-}
-
-.card--vuln .label {
- left: 0;
- position: absolute;
- top: 1.1em;
- padding-left: 1.9em;
- padding-right: 1.9em;
- border-radius: 0 0.25rem 0.25rem 0;
-}
-
-.card--vuln .card__section h2 {
- font-size: 22px;
- margin-bottom: 0.5em;
-}
-
-.card--vuln .card__section p {
- margin: 0 0 0.5em 0;
-}
-
-.card--vuln .card__meta {
- padding: 0 0 0 1em;
- margin: 0;
- font-size: 1.1em;
-}
-
-.card .card__meta__paths {
- font-size: 0.9em;
-}
-
-.card--vuln .card__title {
- font-size: 28px;
- margin-top: 0;
-}
-
-.card--vuln .card__cta p {
- margin: 0;
- text-align: right;
-}
diff --git a/help/README.md b/help/README.md
new file mode 100644
index 0000000000..1a912f4160
--- /dev/null
+++ b/help/README.md
@@ -0,0 +1,9 @@
+# CLI Help files
+
+Snyk CLI Help files are managed by GitBook connected to the [snyk/user-docs](https://github.com/snyk/user-docs) repository.
+
+It's recommended to make all CLI Help changes there. Changes from GitBook are automatically [synced to the Snyk CLI repository with a GitHub Action](https://github.com/snyk/snyk/actions/workflows/sync-cli-help-to-user-docs.yml). The Action creates a PR that needs to be approved and merged. This Action could also be manually triggered.
+
+If you need to make changes tied to a specific PR, you can make them in this repository first, merge the changes and then move them over to the GitBook.
+
+CLI help files are a standard Markdown.
diff --git a/help/_about-this-project/README.md b/help/_about-this-project/README.md
new file mode 100644
index 0000000000..da8fdbb79e
--- /dev/null
+++ b/help/_about-this-project/README.md
@@ -0,0 +1,4 @@
+# About Snyk CLI: Documenting design decisions
+
+- [Why is Snyk CLI bundling its dependencies?](why-we-are-bundling-dependencies.md)
+- [Snyk CLI PGP key](snyk-code-signing-public.pgp)
diff --git a/help/_about-this-project/snyk-code-signing-public.pgp b/help/_about-this-project/snyk-code-signing-public.pgp
new file mode 100644
index 0000000000..6f28702703
--- /dev/null
+++ b/help/_about-this-project/snyk-code-signing-public.pgp
@@ -0,0 +1,51 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBGJmfEwBEADWBKcDoaf8cAnCg1FPcibCzEtKLts9GEpv1ekXi3BP+ZVqdY6/
+vPVznMPgSCDJz4kahDoX18mSZcxJUc6cgu1XPvGHhQE0rcvpUwnTTjnoo4vzvOAM
+SNvaVTmCwO6jYecj5HrBLpy5dhyGUc68GKcOB0CnmsQYFnHrnJOcGdB0AgKsL1Ll
+TZ3qZYe7vS/1i3RoPfoOx+jW8hHPqKltaSf7HRiWcTBpQg8rPjpBRPY/PtXklHy8
+ib76GzEYA7J/X4azBs6I08mSUoUWH0ATwNFSsnR6xN96kEA+d4cL8O9gL9SNpZRX
+X1FPZXfUr8KjS7X2VfDAhG/Ch+aKmTgJF3dIUGHwuegJlVYia32EV3uQH93qnFxA
+oIkP5muhDNNlZ0SDVQLRCTuC6TTKy36VoHNTj3XzUG3+q49RaSdHsp/f90A+caVy
+R6HgYJfcvBhdX7SC+aYpU822j/4SLi2L3K32j+qcIE8iEPR2LlgopEecHQmyI2G4
+0K5HUWaHJPu+EH0s4fsIZfZgESkLQhiBEIon2zTO7Zz8tWxVV0aR4/85Djnn5UUq
+h6efMw2g8WaUkRD3Djsi9CpepyKLakWH3+bBVnKzH8pWdGMa272wn9PVfeCwF8xq
+wICI/PPfN7d7RpULWjphzqeEl/ni0yUIiNPiwBlYH5fbpUlVpndWWXROZwARAQAB
+tCNTbnlrIExpbWl0ZWQgPGNvZGUtc2lnbmluZ0BzbnlrLmlvPokCTgQTAQoAOBYh
+BGi/vM63eU5vwGogRKKcMukfS5VpBQJiZnxMAhsDBQsJCAcDBRUKCQgLBRYCAwEA
+Ah4BAheAAAoJEKKcMukfS5VpeQ0P/j5CgXpwsx3De56+4tibEBqHLfv4wwWxBsoy
+Wv6L1BfgKyI0NSs1IUJSx5GdsXSwtU9Mp5PhBlVVA6U0SwOxt5ghRSf/kCvB1oP1
+Oo8W3dBl4Y4oWFlIM280t2v/PveBdB1nNiPxmAG1jHtoMqbHFkmZDFvroYFSj/rm
+FOe2qXOc/gwj5RUVVu+tTCPwCyCF9tBfqnbYbLjmI2z1om90uFqSOVDZJbUCY9ZK
+OR9Szu0/Jybg9/7VQ2BDRLi4LSRIASHOc4ZbHfX/44NZOODwzCme78MbbS9cFvRX
+SkFzH1I1yEqtCzCF23Nav/Dbemyhe2zi/Qhi/XCufOsg3tK3uSjWWqeIm5knP/IL
+MSdoEOxIkGZ+FHJ6yzO5r4bLYrfwWLrFI/k5dCGgzKiX76TwakqkcJ0NX5kSLdle
+O2HBSuuoPCLcX7QfPWh51CF/EZ6IV/Rmc+ZbyW2UbJ4FuYWsb9wMdSiaoi8TGiVt
+1snb9QRbTo3ZJWRN+pE4bW2UadieE8wfyHHVEMo5RQ49heaR0Q5VJkapnPWfKb1v
+3QTJh4L2Jju0cGQD+OtAvTQ005ZjG0ZXR4GiKArCUMebJRzNR3FFR/vyItP2lb31
+GwCObABWUtP4oCMRxTGHpGRNLJCmMvWIs1RejxB9Y7svU7fcF06jHF5UXke7NSOs
+fLaduZz2uQINBGJmfEwBEACpd4neyneH+/2d5KKQyXpPnPpCnxPIcQ9Ql4QyGuJB
+Em/PeVZDMaqpuQwNfFsPCjV1C9uF9VJfTlvQnXPqA/OndVaWbfBFqEtnS6RX4NHf
+FWIMcYibpg+3lHSY5iY4B4R/WIrlO021g3ULpsVL/Sz4Zz4drF2lZXgcGiBmiRVo
+JopU+KPUbjO0rFh927EYEJ80+LT0E4M+dJnEofGd5P0bTeQWNqoVABLAKql+erHX
+kBUrwMk8/ekdqfn5DYM31OR19Ogud/3cxsxOkhgvWWwUhtihu9NiqtO2y0c4ijHG
+WgWXjM3K2zs23f+pfNLfTtE8pkZw7Swmr0GRQ6Ikx9LqhdGPKqoALr8TqVUudZXU
+8clact0OtmHKASAI4bH51b3XSTmj1+v8g4/oEjukniuFm5Fave78Rh/EaO2IXjxc
+moCSCk6bK2YEM2fSjCuTVI9zm+CzyQ9MmvEJcR0vBCU2vHcrwKkvKXQIssePZT12
+B8IO9LT5jeVnFmtI+tLY2E/r7tqrmcgmHciAw0ugFGG7uYQivF21Mlqz72Dx+P0+
+WVch456NHUy26ALhAv8jU7OQprpzuRQOytYKiUK5GwrF91/6WmhYyIzlVFIYoqfQ
+s0WEu2apcPIx9OVtWoot8cynskneJ9s+EtPNF4T/Zh8YTCvGIhRy7Tvt6GbEnDQE
+UQARAQABiQI2BBgBCgAgFiEEaL+8zrd5Tm/AaiBEopwy6R9LlWkFAmJmfEwCGwwA
+CgkQopwy6R9LlWkJpg/+JWhBKpWJ9Kv4I0XWpMOlCzaKPk0TDqXpSPg+MWOGrv3i
+Q2Xeyi4HCy7oBISI1YpF4mJeaJikC1KQIikOmCKyj6kYj+WnHfjBUkplOKMe80ZU
+X5AZWsUfw2p1LJn0fCcdJrHDAn29R0abaoe9ExYPlXgFmBNoIQNvG/fMzwoIYmPO
+WsWPmqCzYQ7oCi4kOHhOo1isDlrunT0egSb6KXKdLPwn1u+rTQno1fBFQB/cY9s5
+F036QfVx0M4NdX8LzNQCObXZdln0PSqSNs7EXzHGv5ivuBZaFVsKv+HPSnaUng/M
+fyDZhOowNy6NEgB042lnOl0wO8JEm8Av1PC7y/1gTijAd1bFAjX7m0FqIfkJnhvY
+yGwSnGq4qXECpivBGA4Y2xUXURaSDNk8h1TjUT4366CGqSaVgTL5zZ2Jiu6rDozk
+9A4ur3GwldHpszUm3zAhDrjTHxDVFohhbBW9kmcQQmsz1PKiWMqCKT2V3J+Pn/HE
+VEu6go9VxdW4bZyb4Zc0ikB5T+FEuHOMK8QSeEfdWgzG87YJZkTCf5o90wl3A8YJ
+dvQcwQJsZ4FGInMk62VVyHrrJmD7W6+35/dLcAbrjMfrkR9FE97q3wRk4KkGPzDi
+r3OBWax7GYllVWd94kYFN4mRQxQISPRpO9yvH58WbsGxlymYr5lfibYI7AX+/B0=
+=vpdV
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/help/_about-this-project/why-we-are-bundling-dependencies.md b/help/_about-this-project/why-we-are-bundling-dependencies.md
new file mode 100644
index 0000000000..312876859c
--- /dev/null
+++ b/help/_about-this-project/why-we-are-bundling-dependencies.md
@@ -0,0 +1,11 @@
+# Why is Snyk CLI bundling its dependencies?
+
+Snyk CLI is a Node.js application that's distributed as a standalone executable or an [npm/yarn package](https://www.npmjs.com/package/snyk). Snyk CLI relies on many other npm packages, that are defined in the `package.json` manifest.
+
+Over the years we've run into multiple issues and limitations in the npm/yarn ecosystem. These caused a number of issues ranging from annoyances to outright security problems. The root cause for most of those was that npm packages installation does not respect lockfiles that are distributed with the package.
+
+Without a working lockfile, we couldn't control versions of dependencies that are installed with the Snyk CLI. When you ran `npm install snyk --global` on your machine, **you could end up with different and sometimes incompatible versions than those we tested the Snyk CLI with**. Things got even more complicated once you were installing Snyk CLI with a different npm version or yarn command or as a direct dependency into your project.
+
+This forced us to work on making Snyk CLI installations more predictable. We chose to use [Webpack](https://webpack.js.org) to bundle all the JavaScript code from the Snyk CLI, including the dependencies. This means Webpack will pick only the code that's required for the CLI and create a JavaScript **bundle**. This bundle is self-contained. This improved our testing, as we can rely on a single artifact that gets built, regardless of installation method and environment. As a result, when publishing the CLI we don't declare any dependencies in `package.json` as they are already included in the published bundle.
+
+Another major win is a much [smaller distribution and faster Snyk CLI installation](https://updates.snyk.io/smaller-and-faster-cli!-206415). E.g., a Windows npm installation took about 2-3 minutes. After bundling was released it takes a few seconds.
diff --git a/help/api-license.txt b/help/api-license.txt
deleted file mode 100644
index 03afde9d27..0000000000
--- a/help/api-license.txt
+++ /dev/null
@@ -1 +0,0 @@
-The use of Snyk's API, whether through the use of the 'snyk' npm package or otherwise, is subject to the terms & conditions specified here: https://snyk.io/policies
diff --git a/help/cli-commands/README.md b/help/cli-commands/README.md
new file mode 100644
index 0000000000..3f6795407d
--- /dev/null
+++ b/help/cli-commands/README.md
@@ -0,0 +1,69 @@
+# CLI help
+
+Snyk CLI scans and monitors your projects for security vulnerabilities and license issues.
+
+For more information visit the [Snyk website](https://snyk.io)
+
+For details see the [CLI documentation](https://docs.snyk.io/features/snyk-cli)
+
+## How to get started
+
+1. Authenticate by running `snyk auth`
+2. Test your local project with `snyk test`
+3. Get alerted for new vulnerabilities with `snyk monitor`
+
+## Available commands
+
+To learn more about each Snyk CLI command, use the `--help` option, for example, `snyk auth --help` or `snyk container --help`
+
+**Note:** The help on the docs site is the same as the `--help` in the CLI.
+
+### [`snyk auth`](auth.md)
+
+Authenticate Snyk CLI with a Snyk account.
+
+### [`snyk test`](test.md)
+
+Test a project for open source vulnerabilities and license issues.
+
+**Note**: Use `snyk test --unmanaged` to scan all files for known open source dependencies (C/C++ only).
+
+### [`snyk monitor`](monitor.md)
+
+Snapshot and continuously monitor a project for open source vulnerabilities and license issues.
+
+### [`snyk container`](container.md)
+
+Test container images for vulnerabilities.
+
+### [`snyk iac`](iac.md)
+
+Commands to find and manage security issues in Infrastructure as Code files.
+
+### [`snyk code`](code.md)
+
+Find security issues using static code analysis.
+
+### [`snyk log4shell`](log4shell.md)
+
+Find Log4Shell vulnerability.
+
+### [`snyk config`](config.md)
+
+Manage Snyk CLI configuration.
+
+### [`snyk policy`](policy.md)
+
+Display the `.snyk` policy for a package.
+
+### [`snyk ignore`](ignore.md)
+
+Modify the `.snyk` policy to ignore stated issues.
+
+## Debug
+
+Use `-d` option to output the debug logs.
+
+## Configure the Snyk CLI
+
+You can use environment variables to configure the Snyk CLI and also set variables to configure the Snyk CLI to connect with the Snyk API. See [Configure the Snyk CLI](https://docs.snyk.io/features/snyk-cli/configure-the-snyk-cli)
diff --git a/help/cli-commands/apps.md b/help/cli-commands/apps.md
new file mode 100644
index 0000000000..f8f6fb19e5
--- /dev/null
+++ b/help/cli-commands/apps.md
@@ -0,0 +1,55 @@
+# snyk apps -- Create and manage your Snyk Apps
+
+# Usage
+
+`snyk apps []`
+
+## Description
+
+Snyk Apps are integrations that extend the functionality of the Snyk platform. They provide you with an opportunity to mould your Snyk experience to suit your specific needs.
+
+[For more information see our user docs](https://docs.snyk.io/features/integrations/snyk-apps)
+
+## Commands
+
+**_Note: All `apps` commands are only accessible behind the `--experimental` flag and the behaviour can change at any time, without prior notice. You are kindly advised to use all the commands with caution_**
+
+### `create`
+
+Create a new Snyk App.
+
+## Options
+
+### `--interactive`
+
+Use the command in interactive mode.
+
+### `--org=`
+
+(Required for the `create` command)
+Specify the `` to create the Snyk App under.
+
+### `--name=`
+
+(Required for the `create` command)
+The name of Snyk App that will be displayed to the user during the authentication flow.
+
+### `--redirect-uris=`
+
+(Required for the `create` command)
+A comma separated list of redirect URIs. This will form a list of allowed redirect URIs to call back after authentication.
+
+### `--scopes=`
+
+(Required for the `create` command)
+A comma separated list of scopes required by your Snyk App. This will form a list of scopes that your app is allowed to request during authorization. You can read more about the allowed scopes in our [docs](https://docs.snyk.io/snyk-apps/getting-started-with-snyk-apps/create-an-app-via-the-api#requesting-scopes).
+
+## Examples
+
+### `Create Snyk App`
+
+\$ snyk apps create --experimental --org=48ebb069-472f-40f4-b5bf-d2d103bc02d4 --name='My Awesome App' --redirect-uris=https://example1.com,https://example2.com --scopes=org.read,org.report.read
+
+### `Create Snyk App Interactive Mode`
+
+\$ snyk apps create --experimental --interactive
diff --git a/help/cli-commands/auth.md b/help/cli-commands/auth.md
new file mode 100644
index 0000000000..ce4dabc189
--- /dev/null
+++ b/help/cli-commands/auth.md
@@ -0,0 +1,27 @@
+# Auth
+
+## Usage
+
+`snyk auth [] []`
+
+## Description
+
+The `snyk auth` command authenticates your machine to associate the Snyk CLI with your Snyk account.
+
+Running `$ snyk auth` opens a browser window with prompts to log in to your Snyk account and authenticate. No repository permissions are needed at this stage, only your email address.
+
+When you have authenticated you can start using the CLI; see [Getting started with the CLI](https://docs.snyk.io/snyk-cli/getting-started-with-the-cli)
+
+## Value
+
+In some environments and configurations you must use the ``; see [Authenticate the CLI with your account](https://docs.snyk.io/features/snyk-cli/authenticate-the-cli-with-your-account)
+
+The value may be a user token or a service account; see [Service accounts](https://docs.snyk.io/features/integrations/managing-integrations/service-accounts)
+
+In a CI/CD environment use the `SNYK_TOKEN` environment variable; see [Configure the Snyk CLI](https://docs.snyk.io/features/snyk-cli/configure-the-snyk-cli)
+
+After setting this environment variable you can use CLI commands.
+
+## Debug
+
+Use the `-d` option to output the debug logs.
diff --git a/help/cli-commands/code.md b/help/cli-commands/code.md
new file mode 100644
index 0000000000..8580643ae0
--- /dev/null
+++ b/help/cli-commands/code.md
@@ -0,0 +1,80 @@
+# Code
+
+## Usage
+
+`snyk code [] [] []`
+
+## Description
+
+The `snyk code` command finds security issues using Static Code Analysis.
+
+For more information see [CLI for Snyk Code](https://docs.snyk.io/snyk-code/cli-for-snyk-code)
+
+## Subcommand: `test`
+
+Test for any known issue.
+
+## Exit codes
+
+Possible exit codes and their meaning:
+
+**0**: success, no vulnerabilities found\
+**1**: action_needed, vulnerabilities found\
+**2**: failure, try to re-run command\
+**3**: failure, no supported projects detected
+
+## Configure the Snyk CLI
+
+You can use environment variables to configure the Snyk CLI and set variables for connecting with the Snyk API; see [Configure the Snyk CLI](https://docs.snyk.io/features/snyk-cli/configure-the-snyk-cli)
+
+## Debug
+
+Use the `-d` option to output the debug logs.
+
+## Options for the code test subcommand
+
+### `--org=`
+
+Specify the ``to run Snyk commands tied to a specific organization. The `` influences private test limits.
+
+If you have multiple organizations, you can set a default from the CLI using:
+
+`$ snyk config set org=`
+
+Set a default to ensure all newly tested projects are tested under your default organization. If you need to override the default, use the `--org=` option.
+
+Default: `` that is the current preferred organization in your [Account settings](https://app.snyk.io/account)
+
+Note that you can also use `--org=`. The `ORG_ID` works in both the CLI and the API. The organization slug name works in the CLI, but not in the API.
+
+For more information see the article [How to select the organization to use in the CLI](https://support.snyk.io/hc/en-us/articles/360000920738-How-to-select-the-organization-to-use-in-the-CLI)
+
+### `--json`
+
+Print results in JSON format.
+
+Example: `$ snyk code test --json`
+
+### `--json-file-output=`
+
+Save test output in JSON format directly to the specified file, regardless of whether or not you use the `--json` option.
+
+This is useful if you want to display the human-readable test output using stdout and at the same time save the JSON format output to a file.
+
+Example: `$ snyk code test --json-file-output=vuln.json`
+
+### `--sarif`
+
+Return results in SARIF format.
+
+Example: `$ snyk code --sarif`
+
+### `--sarif-file-output=`
+
+Save test output in SARIF format directly to the \ file, regardless of whether or not you use the `--sarif` option.
+
+This is especially useful if you want to display the human-readable test output using stdout and at the same time save the SARIF format output to a file.
+
+### `--severity-threshold=`
+
+Report only vulnerabilities at the specified level or higher. Note that the Snyk Code configuration issues do not currently use the `critical` severity level.
diff --git a/help/cli-commands/config.md b/help/cli-commands/config.md
new file mode 100644
index 0000000000..76b33f83cb
--- /dev/null
+++ b/help/cli-commands/config.md
@@ -0,0 +1,61 @@
+# Config
+
+## Usage
+
+`snyk config []`
+
+## Description
+
+The `snyk config` command manages your local Snyk CLI config file, a JSON file located at `$XDG_CONFIG_HOME` or `~/.config` followed by `configstore/snyk.json`
+
+Example: `~/.config/configstore/snyk.json`
+
+This command does not manage the `.snyk` file that is part of your project. See the [`snyk policy`](policy.md) and [`snyk ignore`](ignore.md) commands.
+
+## Debug
+
+Use the `-d` option to output the debug logs.
+
+## Subcommands
+
+### `get `
+
+Print a config value.
+
+### `set =`
+
+Create a new config value.
+
+### `unset `
+
+Remove a config value.
+
+### `clear`
+
+Remove all config values.
+
+## Supported `` values
+
+### `api`
+
+API token to use when calling Snyk API.
+
+### `endpoint`
+
+Define the API endpoint to use.
+
+### `disable-analytics`
+
+Turn off analytics reporting.
+
+### `oci-registry-url`
+
+Configure the OCI registry used in IaC scanning with custom rules.
+
+### `oci-registry-username`
+
+Configure the username for an OCI registry used in IaC scanning with custom rules.
+
+### `oci-registry-password`
+
+Configure the password for an OCI registry used in IaC scanning with custom rules.
diff --git a/help/cli-commands/container-monitor.md b/help/cli-commands/container-monitor.md
new file mode 100644
index 0000000000..d4ab3faff6
--- /dev/null
+++ b/help/cli-commands/container-monitor.md
@@ -0,0 +1,137 @@
+# Container monitor
+
+## Usage
+
+`snyk container monitor [] []`
+
+## Description
+
+The `snyk container monitor` command captures the container image layers and dependencies and monitor for vulnerabilities on [snyk.io](https://snyk.io)
+
+For more information see [Snyk CLI for container security](https://docs.snyk.io/products/snyk-container/snyk-cli-for-container-security)
+
+## Exit codes
+
+Possible exit codes and their meaning:
+
+**0**: success, image layers and dependencies captured\
+**2**: failure, try to re-run command\
+**3**: failure, no supported projects detected
+
+## Configure the Snyk CLI
+
+You can use environment variables to configure the Snyk CLI and set variables for connecting with the Snyk API.
+
+There are environment variables that apply to the container command; see [Configure the Snyk CLI](https://docs.snyk.io/features/snyk-cli/configure-the-snyk-cli)
+
+## Debug
+
+Use the `-d` option to output the debug logs.
+
+## Options
+
+### `--org=`
+
+Specify the `` to run Snyk commands tied to a specific organization. The `` influences some features availability and private test limits.
+
+If you have multiple organizations, you can set a default from the CLI using:
+
+`$ snyk config set org=`
+
+Set a default to ensure all newly tested and monitored projects are tested and monitored under your default organization. If you need to override the default, use the `--org=` option.
+
+Default: `` that is the current preferred organization in your [Account settings](https://app.snyk.io/account)
+
+Note that you can also use `--org=`. The `ORG_ID` works in both the CLI and the API. The organization slug name works in the CLI, but not in the API.
+
+For more information see the article [How to select the organization to use in the CLI](https://docs.snyk.io/snyk-cli/test-for-vulnerabilities/how-to-select-the-organization-to-use-in-the-cli)
+
+### `--file=`
+
+For more detailed advice, include the path to the Dockerfile for the image.
+
+### `--project-name=`
+
+Specify a custom Snyk project name.
+
+### `--policy-path=`
+
+Manually pass a path to a `.snyk` policy file.
+
+### `--json`
+
+Print results in JSON format, useful for integrating with other tools
+
+Example: `$ snyk container test --json`
+
+### `--project-environment=[,]...>`
+
+Set the project environment to one or more values (comma-separated). To clear the project environment set `--project-environment=`
+
+Allowed values: `frontend`, `backend`, `internal`, `external`, `mobile`, `saas`, `onprem`, `hosted`, `distributed`
+
+For more information see [Project attributes](https://docs.snyk.io/getting-started/introduction-to-snyk-projects/view-project-information/project-attributes)
+
+### `--project-lifecycle=[,`
+
+Set the project lifecycle to one or more values (comma-separated). To clear the project lifecycle set `--project-lifecycle=`
+
+Allowed values: `production, development, sandbox`
+
+For more information see [Project attributes](https://docs.snyk.io/getting-started/introduction-to-snyk-projects/view-project-information/project-attributes)
+
+### `--project-business-criticality=[,]...>`
+
+Set the project business criticality to one or more values (comma-separated). To clear the project business criticality set `--project-business-criticality=`
+
+Allowed values: `critical`, `high`, `medium`, `low`
+
+For more information see [Project attributes](https://docs.snyk.io/getting-started/introduction-to-snyk-projects/view-project-information/project-attributes)
+
+### `--project-tags=[,]...>`
+
+Set the project tags to one or more values (comma-separated key values pairs with an "=" separator).
+
+Example: `--project-tags=department=finance,team=alpha`
+
+To clear the project tags set `--project-tags=`
+
+### `--tags=[,]...>`
+
+This is an alias for `--project tags`
+
+### `--app-vulns`
+
+Allow detection of vulnerabilities in your application dependencies from container images, as well as from the operating system, all in one single scan.
+
+In CLI version 1.962.0 and higher, use the `--app-vulns` option with the the `--json` option to see the operating system as well as application vulnerabilities in JSON format in the results.
+
+For more information see [Detecting application vulnerabilities in container images](https://docs.snyk.io/products/snyk-container/getting-around-the-snyk-container-ui/detecting-application-vulnerabilities-in-container-images)
+
+### `--nested-jars-depth`
+
+When using `--app-vulns` use the `--nested-jars-depth` option to set how many levels of nested jars Snyk is to unpack. Depth must be a number.
+
+### `--exclude-base-image-vulns`
+
+Do not show vulnerabilities introduced only by the base image. Available when using `snyk container test` only.
+
+### `--platform=`
+
+For multi-architecture images, specify the platform to test.
+
+Supported platforms are: `linux/amd64`, `linux/arm64`, `linux/riscv64`, `linux/ppc64le`, `linux/s390x`, `linux/386`, `linux/arm/v7`, or `linux/arm/v6`
+
+### `--username=`
+
+Specify a username to use when connecting to a container registry. This is ignored in favor of local Docker binary credentials when Docker is present.
+
+### `--password=`
+
+Specify a password to use when connecting to a container registry. This is ignored in favor of local Docker binary credentials when Docker is present.
+
+## Example for the container monitor command
+
+**Scan and monitor Docker images**
+
+`$ snyk container monitor `
diff --git a/help/cli-commands/container-test.md b/help/cli-commands/container-test.md
new file mode 100644
index 0000000000..75c7e177f4
--- /dev/null
+++ b/help/cli-commands/container-test.md
@@ -0,0 +1,149 @@
+# Container test
+
+## Usage
+
+`snyk container test [] []`
+
+## Description
+
+The `snyk container test` command tests container images for any known vulnerabilities.
+
+For more information see [Snyk CLI for container security](https://docs.snyk.io/products/snyk-container/snyk-cli-for-container-security)
+
+## Exit codes
+
+Possible exit codes and their meaning:
+
+**0**: success, no vulnerabilities found\
+**1**: action_needed, vulnerabilities found\
+**2**: failure, try to re-run command\
+**3**: failure, no supported projects detected
+
+## Configure the Snyk CLI
+
+You can use environment variables to configure the Snyk CLI and set variables for connecting with the Snyk API.
+
+There are environment variables that apply to the container command; see [Configure the Snyk CLI](https://docs.snyk.io/features/snyk-cli/configure-the-snyk-cli)
+
+## Debug
+
+Use the `-d` option to output the debug logs.
+
+## Options
+
+### `--print-deps`
+
+Print the dependency tree before sending it for analysis.
+
+### `--org=`
+
+Specify the `` to run Snyk commands tied to a specific organization. The `` influences some features availability and private test limits.
+
+If you have multiple organizations, you can set a default from the CLI using:
+
+`$ snyk config set org=`
+
+Set a default to ensure all newly tested and monitored projects are tested and monitored under your default organization. If you need to override the default, use the `--org=` option.
+
+Default: `` that is the current preferred organization in your [Account settings](https://app.snyk.io/account)
+
+Note that you can also use `--org=`. The `ORG_ID` works in both the CLI and the API. The organization slug name works in the CLI, but not in the API.
+
+For more information see the article [How to select the organization to use in the CLI](https://docs.snyk.io/snyk-cli/test-for-vulnerabilities/how-to-select-the-organization-to-use-in-the-cli)
+
+### `--file=`
+
+For more detailed advice, include the path to the Dockerfile for the image.
+
+### `--project-name=`
+
+Specify a custom Snyk project name.
+
+### `--policy-path=`
+
+Manually pass a path to a `.snyk` policy file.
+
+### `--json`
+
+Print results in JSON format, useful for integrating with other tools
+
+Example: `$ snyk container test --json`
+
+### `--json-file-output=`
+
+Save test output in JSON format directly to the specified file, regardless of whether or not you use the `--json` option.
+
+This is especially useful if you want to display the human-readable test output using stdout and at the same time save the JSON format output to a file.
+
+Example: `$ snyk container test --json-file-output=vuln.json`
+
+### `--sarif`
+
+Return results in SARIF format. Note this requires the test to be run with `--file` as well.
+
+### `--sarif-file-output=`
+
+Save test output in SARIF format directly to the `` file, regardless of whether or not you use the `--sarif` option.
+
+This is especially useful if you want to display the human-readable test output using stdout and at the same time save the SARIF format output to a file.
+
+### `--severity-threshold=`
+
+Report only vulnerabilities at the specified level or higher.
+
+### `--fail-on=`
+
+Fail only when there are vulnerabilities that can be fixed.
+
+- `all`: fail when there is at least one vulnerability that can be either upgraded or patched.
+- `upgradable`: fail when there is at least one vulnerability that can be upgraded.
+
+To fail on any vulnerability (the default behavior), do not use the `--fail-on` option. If vulnerabilities do not have a fix and this option is being used, tests pass.
+
+### `--app-vulns`
+
+Allow detection of vulnerabilities in your application dependencies from container images, as well as from the operating system, all in one single scan.
+
+In CLI version 1.962.0 and higher, use the `--app-vulns` option with the the `--json` option to see the operating system as well as application vulnerabilities in JSON format in the results.
+
+For more information see [Detecting application vulnerabilities in container images](https://docs.snyk.io/products/snyk-container/getting-around-the-snyk-container-ui/detecting-application-vulnerabilities-in-container-images)
+
+### `--nested-jars-depth`
+
+When using `--app-vulns` use the `--nested-jars-depth` option to set how many levels of nested jars Snyk is to unpack. Depth must be a number.
+
+### `--exclude-base-image-vulns`
+
+Do not show vulnerabilities introduced only by the base image. Available when using `snyk container test` only.
+
+### `--platform=`
+
+For multi-architecture images, specify the platform to test.
+
+Supported platforms are: `linux/amd64`, `linux/arm64`, `linux/riscv64`, `linux/ppc64le`, `linux/s390x`, `linux/386`, `linux/arm/v7`, or `linux/arm/v6`
+
+### `--username=`
+
+Specify a username to use when connecting to a container registry. This is ignored in favor of local Docker binary credentials when Docker is present.
+
+### `--password=`
+
+Specify a password to use when connecting to a container registry. This is ignored in favor of local Docker binary credentials when Docker is present.
+
+## Examples for the container test command
+
+### Scan Docker images
+
+`$ snyk container test `
+
+### Option to get more information including base image remediation
+
+`--file=path/to/Dockerfile`
+
+### Scan a Docker image created using the given Dockerfile and with a specified policy path
+
+`$ snyk container test app:latest --file=Dockerfile`
+
+`$ snyk container test app:latest --file=Dockerfile --policy-path=path/to/.snyk`
+
+For more information and examples see [Advanced Snyk Container CLI usage](https://docs.snyk.io/snyk-container/snyk-cli-for-container-security/advanced-snyk-container-cli-usage)
diff --git a/help/cli-commands/container.md b/help/cli-commands/container.md
new file mode 100644
index 0000000000..f55381b712
--- /dev/null
+++ b/help/cli-commands/container.md
@@ -0,0 +1,18 @@
+# Container
+
+## Usage
+
+`snyk container [] []`
+
+## Description
+
+The `snyk container` commands test and continuously monitor container images for vulnerabilities.
+
+For more information see [Snyk CLI for container security](https://docs.snyk.io/products/snyk-container/snyk-cli-for-container-security)
+
+## `snyk container` commands and the help docs
+
+The `snyk container` commands are listed here with the help options:
+
+- ``[`container test`](container-test.md), `container test --help`: tests for any known vulnerabilities
+- ``[`container monitor`](container-monitor.md), `container monitor --help`: captures the container image layers and dependencies and monitors for vulnerabilities on [snyk.io](https://snyk.io)
diff --git a/help/cli-commands/iac-describe.md b/help/cli-commands/iac-describe.md
new file mode 100644
index 0000000000..87604e69f9
--- /dev/null
+++ b/help/cli-commands/iac-describe.md
@@ -0,0 +1,217 @@
+# IaC describe
+
+## Usage
+
+**Note:** This feature is available in Snyk CLI version v1.876.0 or greater.
+
+`snyk iac describe []`
+
+## Description
+
+The `snyk iac describe` command detects infrastructure drift and unmanaged resources. It compares resources in your Terraform state file against actual resources in your cloud provider and outputs a report.
+
+- Resources in your Terraform state files are **managed resources**.
+- Changes to managed resources not reflected in the Terraform state file are **drifts**.
+- Resources that exist but are not in your Terraform state file are **unmanaged resources**.
+
+For detailed information and examples, see [IaC describe command examples](https://docs.snyk.io/products/snyk-infrastructure-as-code/detect-drift-and-manually-created-resources/iac-describe-command-examples)
+
+For a list of related commands see the snyk [iac help](iac.md); `iac --help`
+
+## Exit codes
+
+Possible exit codes and their meaning:
+
+**0**: success, no drift found\
+**1**: drifts or unmanaged resources found\
+**2**: failure
+
+## Configure the Snyk CLI
+
+You can use environment variables and set variables for connecting with the Snyk API; see [Configure the Snyk CLI](https://docs.snyk.io/snyk-cli/configure-the-snyk-cli)
+
+## Configure the Terraform provider
+
+You can set environment variables to configure the Terraform provider used by the `describe` command; see [Configure cloud providers](https://docs.snyk.io/products/snyk-infrastructure-as-code/detect-drift-and-manually-created-resources/configure-cloud-providers)
+
+## Debug
+
+Use the `-d` option to output the debug logs.
+
+## Required options
+
+**Note:** To use the `describe` command, you **must use one of these options**:
+
+### `--only-unmanaged`
+
+Report resources not found in any Terraform states.
+
+### `--only-managed` or `--drift`
+
+Scan managed resources found in Terraform states for changes.
+
+### `--all`
+
+Scan both managed and unmanaged resources.
+
+## Optional arguments
+
+### `--org=`
+
+Specify the `` to run Snyk commands tied to a specific organization. Overrides the default `` that is the current preferred organization in your [Account settings](https://app.snyk.io/account)
+
+Note that you can also use `--org=`. The `ORG_ID` works in both the CLI and the API. The organization slug name works in the CLI, but not in the API.
+
+For more information see the article [How to select the organization to use in the CLI](https://support.snyk.io/hc/en-us/articles/360000920738-How-to-select-the-organization-to-use-in-the-CLI)
+
+### `--from=[,...]`
+
+Specify multiple Terraform state files to be read. Glob patterns are supported.
+
+For more information including **a list of supported IaC sources** and how to use them, see [IAC Sources usage](https://docs.snyk.io/products/snyk-infrastructure-as-code/detect-drift-and-manually-created-resources/iac-sources-usage)
+
+### `--to=`
+
+Specify the cloud provider to scan (default: AWS with Terraform).
+
+Supported providers:
+
+- `github+tf` (GitHub with Terraform)
+- `aws+tf` (Amazon Web Services with Terraform)
+- `gcp+tf` (Google Cloud Platform with Terraform)
+- `azure+tf` (Azure with Terraform)
+
+### `--tf-provider-version`
+
+Specify a Terraform provider version to use. If none is specified, default versions are used as follows:
+
+- aws@3.19.0
+- github@4.4.0
+- google@3.78.0
+- azurerm@2.71.0
+
+### `--tf-lockfile`
+
+Read the Terraform lock file (`.terraform.lock.hcl`) from a custom path (default: current directory).
+
+If parsing the lockfile fails, errors are logged and scan continues.
+
+**Note**: When you are using both the `--tf-lockfile` and `--tf-provider-version` options together, `--tf-provider-version` takes precedence.
+
+### `--fetch-tfstate-headers`
+
+Use a specific HTTP header or headers for the HTTP backend when fetching Terraform state.
+
+### `--tfc-token`
+
+Specify an API token to authenticate to the Terraform Cloud or Enterprise API.
+
+### `--tfc-endpoint`
+
+Read the current state for a given workspace from Terraform Enterprise by passing the `tfc-endpoint` value that is specific to your org's Terraform Enterprise installation.
+
+### `--config-dir`
+
+Change the directory path used for `iac describe` configuration (default `$HOME`). This can be useful, for example, if you want to invoke this command in an AWS Lambda function where you can only use the `/tmp` folder.
+
+## Options for including and excluding resources
+
+### `--service=[,...]`
+
+Specify the services whose resources are inspected for drift or unmanaged resources.
+
+This option cannot be used with a `.snyk` drift ignore rule; the content in `.snyk` will be ignored.
+
+Supported services: `aws_s3`, `aws_ec2`, `aws_lambda`, `aws_rds`, `aws_route53`, `aws_iam` , `aws_vpc`, `aws_api_gateway`, `aws_apigatewayv2`, `aws_sqs`, `aws_sns`, `aws_ecr`, `aws_cloudfront`, `aws_kms`, `aws_dynamodb`, `azure_base`, `azure_compute`, `azure_storage`, `azure_network`, `azure_container`, `azure_database`, `azure_loadbalancer`, `azure_private_dns`, `google_cloud_platform`, `google_cloud_storage`, `google_compute_engine`, `google_cloud_dns`, `google_cloud_bigtable`, `google_cloud_bigquery`, `google_cloud_functions`, `google_cloud_sql`, `google_cloud_run`
+
+### `--filter`
+
+Use filter rules.
+
+Filter rules allow you to build a JMESPath expression to include or exclude a set of resources from the report.
+
+To filter on resource attributes, deep mode must be enabled. Deep mode is enabled by default for `--all` and `--only-managed`. To enable deep mode while using `--only-unmanaged`, use the `--deep` option.
+
+For more information see [Filter results](https://docs.snyk.io/products/snyk-infrastructure-as-code/detect-drift-and-manually-created-resources/filter-results)
+
+### `--deep`
+
+Enable deep mode. Deep mode enables you to use the `--filter` option to include or exclude resources in the report based on their attributes.
+
+Deep mode is enabled by default for `--all` and `--only-managed`. Use `--deep` if you want to filter on attributes while using `--only-unmanaged`.
+
+For more information see [Filter results](https://docs.snyk.io/products/snyk-infrastructure-as-code/detect-drift-and-manually-created-resources/filter-results)
+
+### `--strict`
+
+Enable strict mode.
+
+The `iac describe` command ignores service-linked resources by default (like service-linked AWS IAM roles, their policies and policy attachments). To include those resources in the report you can enable **strict mode**. Note that this can create noise when used with an AWS account.
+
+## Options for policies
+
+### `--ignore-policy`
+
+Ignore all set policies, the current policy in the `.snyk` file, org level ignores, and the project policy in the Snyk Web UI.
+
+### `--policy-path=`
+
+Manually pass a path to a `.snyk` policy file.
+
+## Options for output
+
+### `--quiet`
+
+Output only the scan result to stdout.
+
+### `--json`
+
+Output the report as JSON to stdout.
+
+### `--html`
+
+Output the report as html to stdout.
+
+### `--html-file-output=`
+
+Output the report as html into a file.
+
+## Examples for snyk iac describe command
+
+For more examples, see [IaC describe command examples](https://docs.snyk.io/products/snyk-infrastructure-as-code/detect-drift-and-manually-created-resources/iac-describe-command-examples)
+
+### Detect drift and unmanaged resources on AWS with a single local Terraform state
+
+```
+$ snyk iac describe --all --from="tfstate://terraform.tfstate"
+```
+
+### Specify AWS credentials
+
+```
+$ AWS_ACCESS_KEY_ID=XXX AWS_SECRET_ACCESS_KEY=XXX snyk iac describe --all
+```
+
+### Use an AWS named profile
+
+```
+$ AWS_PROFILE=profile_name snyk iac describe --all
+```
+
+### Use a single Terraform state stored on an S3 backend
+
+```
+$ snyk iac describe --from="tfstate+s3://my-bucket/path/to/state.tfstate"
+```
+
+### Aggregate multiple Terraform states
+
+```
+$ snyk iac describe --all --from="tfstate://terraform_S3.tfstate,tfstate://terraform_VPC.tfstate"
+```
+
+### Aggregate many Terraform states, using glob pattern
+
+```
+$ snyk iac describe --all --from="tfstate://path/to/**/*.tfstate"
+```
diff --git a/help/cli-commands/iac-report.md b/help/cli-commands/iac-report.md
new file mode 100644
index 0000000000..2cf65c714f
--- /dev/null
+++ b/help/cli-commands/iac-report.md
@@ -0,0 +1,15 @@
+# IAC report
+
+## Usage
+
+`snyk iac report []`
+
+## Description
+
+Test for any known security issue and share results to the Snyk App.
+
+This is an alias for `snyk iac test --report`.
+
+For more information see the [iac test command help](iac-test.md) for the `--report` option; `iac test --help`
+
+For a list of related commands see the [snyk iac](iac.md) help; `iac --help`
diff --git a/help/cli-commands/iac-test.md b/help/cli-commands/iac-test.md
new file mode 100644
index 0000000000..10fc6a6be1
--- /dev/null
+++ b/help/cli-commands/iac-test.md
@@ -0,0 +1,246 @@
+# IaC test
+
+## Usage
+
+`snyk iac test [] []`
+
+## Description
+
+The `snyk iac test` command tests for any known security issue.
+
+For a list of related commands see the [snyk iac](iac.md) help; `iac --help`
+
+For more information see [Snyk CLI for Infrastructure as Code](https://docs.snyk.io/products/snyk-infrastructure-as-code/snyk-cli-for-infrastructure-as-code)
+
+## Exit codes
+
+Possible exit codes and their meaning:
+
+**0**: success, no vulnerabilities found\
+**1**: action_needed, vulnerabilities found\
+**2**: failure, try to re-run command\
+**3**: failure, no supported projects detected
+
+## Configure the Snyk CLI
+
+You can use environment variables to configure the Snyk CLI and set variables for connecting with the Snyk API. See [Configure the Snyk CLI](https://docs.snyk.io/snyk-cli/configure-the-snyk-cli)
+
+## Debug
+
+Use the `-d` option to output the debug logs.
+
+## Options
+
+### `--detection-depth=`
+
+Use to indicate how many subdirectories to search. `DEPTH` must be a number, 1 or greater; zero (0) is the current directory.
+
+Default: no limit.
+
+Example: `--detection-depth=3` limits search to the specified directory (or the current directory if no `` is specified) plus three levels of subdirectories; zero (0) is the current directory.
+
+### `--org=`
+
+Specify the `` to run Snyk commands tied to a specific organization. The `` influences private test limits.
+
+If you have multiple organizations, you can set a default from the CLI using:
+
+`$ snyk config set org=`
+
+Set a default to ensure all newly tested projects are tested under your default organization. If you need to override the default, use the `--org=` option.
+
+Default: `` that is the current preferred organization in your [Account settings](https://app.snyk.io/account)
+
+Note that you can also use `--org=`. The `ORG_ID` works in both the CLI and the API. The organization slug name works in the CLI, but not in the API.
+
+For more information see the article [How to select the organization to use in the CLI](https://support.snyk.io/hc/en-us/articles/360000920738-How-to-select-the-organization-to-use-in-the-CLI)
+
+### `--ignore-policy`
+
+Ignore all set policies, the current policy in the `.snyk` file, org level ignores, and the project policy on snyk.io.
+
+### `--policy-path=`
+
+Manually pass a path to a `.snyk` policy file.
+
+### `--json`
+
+Print results in JSON format.
+
+Example: `$ snyk iac test --json`
+
+### `--json-file-output=`
+
+Save test output in JSON format directly to the specified file, regardless of whether or not you use the `--json` option.
+
+This is especially useful if you want to display the human-readable test output using stdout and at the same time save the JSON format output to a file.
+
+Example: `$ snyk iac test --json-file-output=vuln.json`
+
+### `--sarif`
+
+Return results in SARIF format.
+
+### `--sarif-file-output=`
+
+Save test output in SARIF format directly to the \ file, regardless of whether or not you use the `--sarif` option.
+
+This is especially useful if you want to display the human-readable test output using stdout and at the same time save the SARIF format output to a file.
+
+### `--project-business-criticality=[,]...>`
+
+This can be used in combination with the `--report` option.
+
+Set the project business criticality project attribute to one or more values (comma-separated). To clear the project business criticality set `--project-business-criticality=`
+
+Allowed values: `critical, high, medium, low`
+
+For more information see [Project attributes](https://docs.snyk.io/getting-started/introduction-to-snyk-projects/view-project-information/project-attributes)
+
+### `--project-environment=[,]...>`
+
+This can be used in combination with the `--report` option.
+
+Set the project environment project attribute to one or more values (comma-separated). To clear the project environment set `--project-environment=`
+
+Allowed values: `frontend`, `backend`, `internal`, `external`, `mobile`, `saas`, `onprem`, `hosted`, `distributed`
+
+For more information see [Project attributes](https://docs.snyk.io/getting-started/introduction-to-snyk-projects/view-project-information/project-attributes)
+
+### `--project-lifecycle=[,]...>`
+
+This can be used in combination with the `--report` option.
+
+Set the project lifecycle project attribute to one or more values (comma-separated). To clear the project lifecycle set `--project-lifecycle=`
+
+Allowed values: `production`, `development`, `sandbox`
+
+For more information see [Project attributes](https://docs.snyk.io/getting-started/introduction-to-snyk-projects/view-project-information/project-attributes)
+
+### `--project-tags=[,]...>`
+
+This can be used in combination with the `--report` option.
+
+Set the project tags to one or more values (comma-separated key value pairs with an "=" separator).
+
+Example: `--project-tags=department=finance,team=alpha`
+
+To clear the project tags set `--project-tags=`
+
+### `--remote-repo-url=`
+
+This can be used in combination with the `--report` option.
+
+Set or override the remote URL for the repository.
+
+### `--report`
+
+**NEW** option: Share results with the Snyk Web UI.
+
+This creates a project in your Snyk account with a snapshot of the current configuration issues. After using this option, log in to the Snyk website and view your projects to see the monitor.
+
+Example: `$ snyk iac test --report`
+
+Note: This option cannot be used in combination with the `--rules` option.
+
+### `--rules=`
+
+Use this dedicated option for Custom Rules scanning to enable the IaC scans to use a custom rules bundle generated with the `snyk-iac-rules` SDK. See [`snyk-iac-rules` SDK](https://github.com/snyk/snyk-iac-rules#readme)
+
+This option cannot be used if the custom rules settings were configured with the Snyk UI. Default: If the `--rules` flag is not specified, scan the configuration files using the internal Snyk rules only.
+
+Example: Scan the configuration files using custom rules and internal Snyk rules.
+
+`--rules=bundle.tar.gz`
+
+Note: This option can not be used in combination with the `--report` option.
+
+### `--severity-threshold=`
+
+Report only vulnerabilities at the specified level or higher.
+
+### `--scan=`
+
+Use this dedicated option for Terraform plan scanning modes to control whether the scan analyzes the full final state (for example, `planned-values`), or the proposed changes only (for example, `resource-changes`).
+
+Default: If the `--scan` option is not specified, scan the proposed changes only by default. Example 1: `--scan=planned-values` (full state scan)\
+Example 2: `--scan=resource-changes` (proposed changes scan)
+
+### `--target-name=`
+
+This can be used in combination with the `--report` option.
+
+Set or override the project name for the repository.
+
+Note: This flag will supersede the `--remote-repo-url`, if used together.
+
+### `--target-reference=`
+
+This can be used in combination with the `--report` option.
+
+Specify a reference which differentiates this project, for example, a branch name or version. Projects having the same reference can be grouped based on that reference.
+
+Example, setting to the current Git branch:
+
+`snyk iac test myproject/ --report --target-reference="$(git branch --show-current)"`
+
+\
+Example, setting to the latest Git tag:
+
+`snyk iac test myproject/ --report --target-reference="$(git describe --tags --abbrev=0)"`
+
+### `--var-file=`
+
+Use this option to load a terraform variable definitions file that is located in a different directory from the scanned one.
+
+Example:
+
+`$ snyk iac test myproject/staging/networking --var-file=myproject/vars.tf`
+
+## Examples for snyk iac test command
+
+For more information see [Snyk CLI for Infrastructure as Code](https://docs.snyk.io/products/snyk-infrastructure-as-code/snyk-cli-for-infrastructure-as-code)
+
+### Test a CloudFormation file
+
+```
+$ snyk iac test /path/to/cloudformation_file.yaml
+```
+
+### Test a Kubernetes file
+
+```
+$ snyk iac test /path/to/kubernetes_file.yaml
+```
+
+### Test a Terraform file
+
+```
+$ snyk iac test /path/to/terraform_file.tf
+```
+
+### Test a Terraform plan file
+
+```
+$ terraform plan -out=tfplan.binary
+$ terraform show -json tfplan.binary > tf-plan.json
+$ snyk iac test tf-plan.json
+```
+
+### Test an ARM file
+
+```
+$ snyk iac test /path/to/arm_file.json
+```
+
+### Test matching files in a directory
+
+```
+$ snyk iac test /path/to/directory
+```
+
+### Test matching files in a directory using a local custom rules bundle
+
+```
+$ snyk iac test /path/to/directory --rules=bundle.tar.gz
+```
diff --git a/help/cli-commands/iac-update-exclude-policy.md b/help/cli-commands/iac-update-exclude-policy.md
new file mode 100644
index 0000000000..4d296accec
--- /dev/null
+++ b/help/cli-commands/iac-update-exclude-policy.md
@@ -0,0 +1,48 @@
+# IAC update-exclude-policy
+
+## Usage
+
+`snyk iac update-exclude-policy []`
+
+## Description
+
+The `snyk iac update-exclude-policy` generates exclude policy rules to be used by `snyk iac describe`.
+
+For a list of related commands see the [snyk iac](iac.md) help; `iac --help`
+
+For more information see [Ignore resources](https://docs.snyk.io/products/snyk-infrastructure-as-code/detect-drift-and-manually-created-resources/ignore-resources)
+
+## Exit codes
+
+Possible exit codes and their meaning:
+
+**0**: success, exclude rules generated successfully\
+**1**: error, something wrong happened during exclude rules generation
+
+## Configure the Snyk CLI
+
+You can use environment variables to configure the Snyk CLI and set variables for connecting with the Snyk API. See [Configure the Snyk CLI](https://docs.snyk.io/snyk-cli/configure-the-snyk-cli)
+
+## Debug
+
+Use the `-d` option to output the debug logs.
+
+## Options
+
+### `--exclude-changed`
+
+Exclude resources that changed on cloud provider.
+
+### `--exclude-missing`
+
+Exclude missing resources.
+
+### `--exclude-unmanaged`
+
+Exclude resources not managed by IaC.
+
+## Example
+
+```
+$ snyk iac describe --json --all | snyk iac update-exclude-policy
+```
diff --git a/help/cli-commands/iac.md b/help/cli-commands/iac.md
new file mode 100644
index 0000000000..882275b33c
--- /dev/null
+++ b/help/cli-commands/iac.md
@@ -0,0 +1,21 @@
+# IaC
+
+## Usage
+
+`snyk iac [] []`
+
+## Description
+
+The `snyk iac` commands find and report security issues in Infrastructure as Code files; detect, track, and alert on infrastructure drift and unmanaged resources; and create a .driftigore file.
+
+For more information see [Snyk CLI for Infrastructure as Code](https://docs.snyk.io/products/snyk-infrastructure-as-code/snyk-cli-for-infrastructure-as-code)
+
+## `snyk iac` commands and the help docs
+
+All the `snyk iac` commands are listed here with the help options:
+
+- [iac test](iac-test.md); `iac test --help`: tests for any known security issue
+- [iac update-exclude-policy](iac-update-exclude-policy.md); `iac update-exclude-policy --help`: auto-generates `.snyk` exclusions for cloud resources\
+ Example: `snyk iac describe --json --all | snyk iac update-exclude-policy`
+- [iac describe](iac-describe.md); `iac describe --help`: detects infrastructure drift and unmanaged cloud resources\
+ Example: `snyk iac describe --only-unmanaged`
diff --git a/help/cli-commands/ignore.md b/help/cli-commands/ignore.md
new file mode 100644
index 0000000000..51d73025cb
--- /dev/null
+++ b/help/cli-commands/ignore.md
@@ -0,0 +1,144 @@
+# Ignore
+
+## Usage
+
+`snyk ignore --id= [--expiry=] [--reason=] [--policy-path=] [--path=] [OPTIONS]`
+
+OR
+
+`snyk ignore [--expiry=] [--reason=] [--policy-path=] --file-path=] [OPTIONS]`
+
+## Description
+
+The `snyk ignore` command modifies the `.snyk` policy file to ignore a stated issue according to its snyk ID for all occurrences, its expiry date, a reason, or according to paths in the filesystem.
+
+This updates your local `.snyk` file to contain a block similar to the following:
+
+```yaml
+ignore:
+ '':
+ - '*':
+ reason:
+ expires:
+```
+
+When you use the `--path` option the block is similar to this:
+
+```yaml
+ignore:
+ '':
+ - '':
+ reason:
+ expires:
+```
+
+When you use the `--file-path` option the block is similar to this:
+
+```yaml
+exclude:
+ '':
+ -
+ - :
+ reason:
+ expires:
+ created:
+```
+
+## Debug
+
+Use the `-d` option to output the debug logs.
+
+## Options
+
+### `--id=`
+
+Snyk ID for the issue to ignore, omitted if used with `--file-path`; required by other use cases.
+
+### `--expiry=`
+
+Expiry date in `YYYY-MM-DD` format.
+
+Supported formats:
+
+[ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html)
+
+[RFC 2822](https://tools.ietf.org/html/rfc2822)
+
+Default: 30 days or none if used with `--file-path`
+
+### `--reason=`
+
+Human-readable `` to ignore this issue.
+
+Default: none
+
+### `--policy-path=`
+
+Path to a `.snyk` policy file to pass manually.
+
+Default: none
+
+### `--path=`
+
+Path to resource inside the depgraph for which to ignore the issue.
+
+Use to narrow the scope of the ignore rule. When no resource path is specified, all resources are ignored.
+
+If used, follows the `--policy-path` option.
+
+Default: all
+
+### `--file-path=`
+
+Filesystem for which to ignore the issue. Used by `snyk code` and `snyk test --unmanaged`
+
+Default: none
+
+### `--file-path-group=[global|code|iac-drift]`
+
+Grouping used in combination with `--file-path`, otherwise omitted.
+
+Default: global
+
+## Examples for snyk ignore command
+
+### Ignore a specific vulnerability
+
+```
+$ snyk ignore --id='npm:qs:20170213' --expiry='2021-01-10' --reason='Module not affected by this vulnerability'
+```
+
+### Ignore a specific vulnerability with a resource path specified
+
+```
+$ snyk ignore --id='SNYK-JS-PATHPARSE-1077067' --expiry='2021-01-10' --path='nyc@11.9.0 > istanbul-lib-report@1.1.3 > path-parse@1.0.5' --reason='Module not affected by this vulnerability'
+```
+
+### Ignore a specific vulnerability for 30 days
+
+```
+$ snyk ignore --id=npm:tough-cookie:20160722
+```
+
+### Ignore a specific file until 2031-01-20
+
+Ignore a specific file, used by `snyk test --unmanaged` until 2031-01-20, with a description as a reference for the future.
+
+```
+$ snyk ignore --file-path='./deps/curl-7.58.0/src/tool_msgs.c' --expiry='2031-01-20' --reason='patched file'
+```
+
+### Ignore files or folders using glob expression
+
+Ignore files matching a glob expression by adding them to a specific group. Applies to Snyk Code; does not apply to Snyk Open Source, Container, or Infrastructure as Code.
+
+```
+$ snyk ignore --file-path='./**/vendor/**/*.cpp' --file-path-group='global'
+```
+
+## More information about snyk ignore command
+
+For more information see:
+
+- [Ignore vulnerabilities using Snyk CLI](https://docs.snyk.io/snyk-cli/test-for-vulnerabilities/ignore-vulnerabilities-using-snyk-cli)
+- [IaC ignores using the .snyk policy file](https://docs.snyk.io/products/snyk-infrastructure-as-code/snyk-cli-for-infrastructure-as-code/iac-ignores-using-the-.snyk-policy-file)
diff --git a/help/cli-commands/log4shell.md b/help/cli-commands/log4shell.md
new file mode 100644
index 0000000000..a54e213edb
--- /dev/null
+++ b/help/cli-commands/log4shell.md
@@ -0,0 +1,31 @@
+# Log4shell
+
+## Usage
+
+`snyk log4shell`
+
+## Description
+
+The `snyk log4shell` command finds traces of the Log4J library that are affected by the Log4Shell vulnerability [CVE-2021-44228](https://security.snyk.io/vuln/SNYK-JAVA-ORGAPACHELOGGINGLOG4J-2314720)
+
+The command finds traces of the Log4J library even if it is not declared in the manifest files (such as pom.xml or build.gradle).
+
+## Managed projects
+
+To test for Log4Shell vulnerabilities in Java projects using their package manager manifest files, use the `snyk test` command. See the [test command help](test.md) (`snyk test --help`) and [Snyk for Java and Kotlin](https://docs.snyk.io/products/snyk-open-source/language-and-package-manager-support/snyk-for-java-gradle-maven)
+
+To test unmanaged files, use `snyk test --scan-all-unmanaged`
+
+See the Maven options section of the [test command help](test.md); `snyk test --help`
+
+## Exit codes
+
+Possible exit codes and their meaning:
+
+**0**: success, Log4Shell not found\
+**1**: action_needed, Log4Shell found\
+**2**: failure, try to re-run command
+
+## Debug
+
+Use the `-d` option to output the debug logs.
diff --git a/help/cli-commands/monitor.md b/help/cli-commands/monitor.md
new file mode 100644
index 0000000000..1beacc1dbc
--- /dev/null
+++ b/help/cli-commands/monitor.md
@@ -0,0 +1,341 @@
+# Monitor
+
+## Usage
+
+`snyk monitor []`
+
+## Description
+
+The `snyk monitor` command creates a project in your Snyk account to be continuously monitored for open source vulnerabilities and license issues. After running this command, log in to the Snyk website and view your projects to see the monitor.
+
+For Snyk Container see [`snyk container` help](https://docs.snyk.io/snyk-cli/commands/container)
+
+The `monitor` command is not supported for Snyk Code.
+
+For Snyk Infrastructure as Code follow the instructions in "Regularly testing IaC files" on [Snyk CLI for Infrastructure as Code](https://docs.snyk.io/products/snyk-infrastructure-as-code/snyk-cli-for-infrastructure-as-code)
+
+## Exit codes
+
+Possible exit codes and their meaning:
+
+**0**: success, snapshot created\
+**2**: failure, try to re-run command\
+**3**: failure, no supported projects detected
+
+## Configure the Snyk CLI
+
+You can use environment variables to configure the Snyk CLI and and set variables for connecting with the Snyk API. See [Configure the Snyk CLI](https://docs.snyk.io/features/snyk-cli/configure-the-snyk-cli)
+
+## Debug
+
+Use the `-d` option to output the debug logs.
+
+## Options
+
+See also subsequent sections for options for specific build environments, package managers, languages and `[]` which you specify last.
+
+### `--all-projects`
+
+Auto-detect all projects in the working directory (including Yarn workspaces).
+
+For more information see the article [Does the Snyk CLI support monorepos or multiple manifest files?](https://support.snyk.io/hc/en-us/articles/360000910577-Does-the-Snyk-CLI-support-monorepos-or-multiple-manifest-files-)
+
+### `--fail-fast`
+
+Use with `--all-projects` to cause scans to be interrupted when errors occur and to report these errors back to the user.
+
+The exit code is 2 and the scan ends. No vulnerability information is reported for projects that did not produce errors.
+
+To perform the scan, resolve the error and scan again.
+
+Note: If you do not use `--fail-fast`, Snyk scans all the projects but does not report any vulnerabilities for projects it could not scan due to misconfiguration or another error.
+
+### `--detection-depth=`
+
+Use with `--all-projects` or `--yarn-workspaces` to indicate how many subdirectories to search. `DEPTH` must be a number, `1` or greater; zero (0) is the current directory.
+
+Default: 4, the current working directory (0) and 4 subdirectories.
+
+Example: `--detection-depth=3` limits search to the specified directory (or the current directory if no `` is specified) plus three levels of subdirectories; zero (0) is the current directory.
+
+### `--exclude=[,]...>`
+
+Can be used with `--all-projects` and `--yarn-workspaces` to indicate directory names and file names to exclude. Must be comma separated.
+
+Example: `$ snyk test --all-projects --exclude=dir1,file2`
+
+This will exclude any directories and files named "dir1" and "file2" when scanning for project manifest files. Such as: "./dir1", "./src/dir1", "./file2", "./src/file2", and so on.
+
+### `--prune-repeated-subdependencies`, `-p`
+
+Prune dependency trees, removing duplicate sub-dependencies.
+
+Continues to find all vulnerabilities, but may not find all of the vulnerable paths.
+
+### `--print-deps`
+
+Print the dependency tree before sending it for analysis.
+
+### `--remote-repo-url=`
+
+Set or override the remote URL for the repository that you would like to monitor.
+
+### `--dev`
+
+Include development-only dependencies. Applicable only for some package managers, for example, `devDependencies` in npm or `:development` dependencies in Gemfile.
+
+Default: scan only production dependencies.
+
+### `--org=`
+
+Specify the `` to run Snyk commands tied to a specific organization. The `` influences some features availability and private test limits.
+
+If you have multiple organizations, you can set a default from the CLI using:
+
+`$ snyk config set org=`
+
+Set a default to ensure all newly monitored projects are created under your default organization. If you need to override the default, use the `--org=` option.
+
+Default: `` that is the current preferred organization in your [Account settings](https://app.snyk.io/account)
+
+Note that you can also use `--org=`. The `ORG_ID` works in both the CLI and the API. The organization slug name works in the CLI, but not in the API.
+
+For more information see the article [How to select the organization to use in the CLI](https://support.snyk.io/hc/en-us/articles/360000920738-How-to-select-the-organization-to-use-in-the-CLI)
+
+### `--file=`
+
+Specify a package file.
+
+When testing locally or monitoring a project, you can specify the file that Snyk should inspect for package information. When the file is not specified, Snyk tries to detect the appropriate file for your project.
+
+### `--package-manager=`
+
+Specify the name of the package manager when the filename specified with the `--file=` option is not standard. This allows Snyk to find the file.
+
+Example: `$ snyk monitor --file=req.txt --package-manager=pip`
+
+### `--unmanaged`
+
+For C++ only, scan all files for known open source dependencies.
+
+For options you can use with `--unmanaged` see [Options for scanning using `--unmanaged`](https://docs.snyk.io/snyk-cli/commands/monitor#options-for-scanning-using-unmanaged)
+
+For more information see [Snyk for C/C++](https://docs.snyk.io/products/snyk-open-source/language-and-package-manager-support/snyk-for-c-c++)
+
+### `--ignore-policy`
+
+Ignore all set policies, the current policy in the `.snyk` file, org level ignores, and the project policy on snyk.io.
+
+### `--trust-policies`
+
+Apply and use ignore rules from the Snyk policies in your dependencies; otherwise ignore rules in the dependencies are only shown as a suggestion.
+
+### `--project-name=`
+
+Specify a custom Snyk project name.
+
+Example: `$ snyk monitor --project-name=my-project`
+
+### `--target-reference=`
+
+Specify a reference which differentiates this project, for example, a branch name or version. Projects having the same reference can be grouped based on that reference. Supported for Snyk Open Source and use with `--unmanaged`.
+
+For more information see [Separating projects by branch or version](https://docs.snyk.io/snyk-cli/secure-your-projects-in-the-long-term/grouping-projects-by-branch-or-version)
+
+### `--policy-path=`
+
+Manually pass a path to a `.snyk` policy file.
+
+### `--json`
+
+Print results in JSON format.
+
+### `--project-environment=[,]...>`
+
+Set the project environment project attribute to one or more values (comma-separated). To clear the project environment set `--project-environment=`
+
+Allowed values: `frontend, backend, internal, external, mobile, saas, onprem, hosted, distributed`
+
+For more information see [Project attributes](https://docs.snyk.io/getting-started/introduction-to-snyk-projects/view-project-information/project-attributes)
+
+### `--project-lifecycle=[,]...>`
+
+Set the project lifecycle project attribute to one or more values (comma-separated). To clear the project lifecycle set `--project-lifecycle=`
+
+Allowed values: `production, development, sandbox`
+
+For more information see [Project attributes](https://docs.snyk.io/getting-started/introduction-to-snyk-projects/view-project-information/project-attributes)
+
+### `--project-business-criticality=[,]...>`
+
+Set the project business criticality project attribute to one or more values (comma-separated). To clear the project business criticality set `--project-business-criticality=`
+
+Allowed values: `critical, high, medium, low`
+
+For more information see [Project attributes](https://docs.snyk.io/getting-started/introduction-to-snyk-projects/view-project-information/project-attributes)
+
+### `--project-tags=[,]...>`
+
+Set the project tags to one or more values (comma-separated key value pairs with an "=" separator), for example, `--project-tags=department=finance,team=alpha` To clear the project tags set `--project-tags=`
+
+### `--tags=[,]...>`
+
+This is an alias for `--project-tags`
+
+## Options for Maven projects
+
+For more information about Maven CLI options see [Snyk for Java and Kotlin](https://docs.snyk.io/products/snyk-open-source/language-and-package-manager-support/snyk-for-java-gradle-maven)
+
+### `--maven-aggregate-project`
+
+Use `--maven-aggregate-project` instead of `--all-projects` when scanning Maven aggregate projects, that is, ones that use modules and inheritance.
+
+When scanning these types of projects, Snyk performs a compile to ensure all modules are resolvable by the Maven reactor.
+
+Be sure to run the scan in the same directory as the root pom.xml file.
+
+Snyk reports test results per pom.xml file.
+
+### `--scan-all-unmanaged`
+
+Auto-detect maven jars, aars, and wars in given directory. To monitor individually use `--file=`
+
+**Note**: Custom-built jar files, even with open source dependencies, are out of scope.
+
+## Options for Gradle projects
+
+For more information about Gradle CLI options see [Snyk for Java and Kotlin](https://docs.snyk.io/products/snyk-open-source/language-and-package-manager-support/snyk-for-java-gradle-maven)
+
+### `--sub-project=`, `--gradle-sub-project=`
+
+For Gradle "multi project" configurations, monitor a specific sub-project.
+
+### `--all-sub-projects`
+
+For "multi project" configurations, monitor all sub-projects.
+
+### `--configuration-matching=`
+
+Resolve dependencies using only configuration(s) that match the specified Java regular expression.
+
+Example: `^releaseRuntimeClasspath$`
+
+### `--configuration-attributes=[,]...`
+
+Select certain values of configuration attributes to install dependencies and perform dependency resolution.
+
+Example: `buildtype:release,usage:java-runtime`
+
+### `--init-script=`
+
+When monitoring a .NET project, use this option to add a custom prefix to the name of files inside a project along with any desired separators.
+
+Example: `snyk monitor --file=my-project.sln --project-name-prefix=my-group/`
+
+This is useful when you have multiple projects with the same name in other `.sln` files.
+
+## Option for npm projects
+
+### `--strict-out-of-sync=true|false`
+
+Control monitoring out-of-sync lockfiles.
+
+Default: true
+
+## Options for Yarn projects
+
+### `--strict-out-of-sync=true|false`
+
+Control monitoring out-of-sync lockfiles.
+
+Default: true
+
+### `--yarn-workspaces`
+
+Detect and scan Yarn workspaces. You can specify how many sub-directories to search using `--detection-depth` and exclude directories and files using `--exclude`. Alternatively scan Yarn workspaces with other projects using `--all-projects`
+
+## Option for CocoaPods projects
+
+### `--strict-out-of-sync=true|false`
+
+Control monitoring out-of-sync lockfiles.
+
+Default: false
+
+## Options for Python projects
+
+### `--command=`
+
+Indicate which specific Python commands to use based on Python version. The default is `python` which executes your default python version. Run 'python -V' to find out what version it is. If you are using multiple Python versions, use this parameter to specify the correct Python command for execution.
+
+Default: `python` Example: `--command=python3`
+
+### `--skip-unresolved=true|false`
+
+Allow skipping packages that are not found in the environment.
+
+## Options for scanning using `--unmanaged`
+
+The following `snyk monitor` options can be used with `--unmanaged` as documented in this help.
+
+`--org=`
+
+`--json`
+
+`--remote-repo-url=`
+
+`--target-reference=`
+
+There are also special options.
+
+### `--target-dir`
+
+Scan the path specified in the argument instead of the current directory.
+
+Alternatively, run `snyk test --unmanaged`
+
+### `--max-depth`
+
+Specify the maximum level of archive extraction.
+
+Usage: `--max-depth=1`
+
+Use 0 to disable archive extraction completely.
+
+### `--print-dep-paths`
+
+Display dependencies.
+
+Use use this option to see what files contributed to each dependency identified.
+
+To see how confident Snyk is about the identified dependency and its version, use the `--print-deps` or `--print-dep-paths` option.
+
+For more information on uses of CLI options for C/C++ projects see [Snyk for C / C++](https://docs.snyk.io/products/snyk-open-source/language-and-package-manager-support/snyk-for-c-c++)
+
+### `--project-name=c-project`
+
+Use with the `snyk monitor --unmanaged` command to override the default name Snyk gives your snapshots by entering the desired name.
+
+## Options for build tools
+
+### `-- []`
+
+Use a double dash (`--`) after the complete Snyk command to pass options (arguments, flags) that follow directly to the build tool, for example Gradle or Maven.
+
+The format is `snyk -- []`
+
+Example: `snyk monitor -- --build-cache`
diff --git a/help/cli-commands/policy.md b/help/cli-commands/policy.md
new file mode 100644
index 0000000000..b22cf245e5
--- /dev/null
+++ b/help/cli-commands/policy.md
@@ -0,0 +1,17 @@
+# Policy
+
+## Usage
+
+`snyk policy [] []`
+
+## Description
+
+The `snyk policy` command displays the `.snyk` policy file for a package.
+
+## Argument: ``
+
+Manually pass a path to a `.snyk` policy file.
+
+## Debug
+
+Use `-d` option to output the debug logs.
diff --git a/help/cli-commands/test.md b/help/cli-commands/test.md
new file mode 100644
index 0000000000..6097cd69b4
--- /dev/null
+++ b/help/cli-commands/test.md
@@ -0,0 +1,366 @@
+# Test
+
+## Usage
+
+`snyk test []`
+
+## Description
+
+The `snyk test` command checks projects for open source vulnerabilities and license issues. The test command tries to auto-detect supported manifest files with dependencies and test those.
+
+## Exit codes
+
+Possible exit codes and their meaning:
+
+**0**: success, no vulnerabilities found\
+**1**: action_needed, vulnerabilities found\
+**2**: failure, try to re-run command\
+**3**: failure, no supported projects detected
+
+## Configure the Snyk CLI
+
+You can use environment variables to configure the Snyk CLI and set variables for connecting with the Snyk API. See [Configure the Snyk CLI](https://docs.snyk.io/features/snyk-cli/configure-the-snyk-cli)
+
+## Debug
+
+Use the `-d` option to output the debug logs.
+
+## Options
+
+See also subsequent sections for options for specific build environments, package managers, languages, and `[]` which you specify last.
+
+### `--all-projects`
+
+Auto-detect all projects in the working directory (including Yarn workspaces).
+
+For more information see the article [Does the Snyk CLI support monorepos or multiple manifest files?](https://support.snyk.io/hc/en-us/articles/360000910577-Does-the-Snyk-CLI-support-monorepos-or-multiple-manifest-files-)
+
+### `--fail-fast`
+
+Use with `--all-projects` to cause scans to be interrupted when errors occur and to report these errors back to the user.
+
+The exit code is 2 and the scan ends. No vulnerability information is reported for projects that did not produce errors.
+
+To perform the scan, resolve the error and scan again.
+
+Note: If you do not use `--fail-fast`, Snyk scans all the projects but does not report any vulnerabilities for projects it could not scan due to misconfiguration or another error.
+
+### `--detection-depth=`
+
+Use with `--all-projects` or `--yarn-workspaces` to indicate how many subdirectories to search. `DEPTH` must be a number, 1 or greater; zero (0) is the current directory.
+
+Default: 4 , the current working directory (0) and 4 subdirectories.
+
+Example: `--detection-depth=3` limits search to the specified directory (or the current directory if no `` is specified) plus three levels of subdirectories; zero (0) is the current directory.
+
+### `--exclude=[,]...>`
+
+Can be used with `--all-projects` and `--yarn-workspaces` to indicate directory names and file names to exclude. Must be comma separated.
+
+Example: `$ snyk test --all-projects --exclude=dir1,file2`
+
+This will exclude any directories and files named "dir1" and "file2" when scanning for project manifest files. Such as: "./dir1", "./src/dir1", "./file2", "./src/file2", and so on.
+
+### `--prune-repeated-subdependencies`, `-p`
+
+Prune dependency trees, removing duplicate sub-dependencies.
+
+Continues to find all vulnerabilities, but may not find all of the vulnerable paths.
+
+### `--print-deps`
+
+Print the dependency tree before sending it for analysis.
+
+### `--remote-repo-url=`
+
+Set or override the remote URL for the repository that you would like to monitor.
+
+### `--dev`
+
+Include development-only dependencies. Applicable only for some package managers, for example, `devDependencies` in npm or `:development` dependencies in Gemfile.
+
+Default: scan only production dependencies.
+
+### `--org=`
+
+Specify the `` to run Snyk commands tied to a specific organization. The `` influences some features availability and private test limits.
+
+If you have multiple organizations, you can set a default from the CLI using:
+
+`$ snyk config set org=`
+
+Set a default to ensure all newly tested projects are tested under your default organization. If you need to override the default, use the `--org=` option.
+
+Default: `` that is the current preferred organization in your [Account settings](https://app.snyk.io/account)
+
+Note that you can also use `--org=.` The `ORG_ID` works in both the CLI and the API. The organization slug name works in the CLI, but not in the API.
+
+For more information see the article [How to select the organization to use in the CLI](https://support.snyk.io/hc/en-us/articles/360000920738-How-to-select-the-organization-to-use-in-the-CLI)
+
+### `--file=`
+
+Specify a package file.
+
+When testing locally or monitoring a project, you can specify the file that Snyk should inspect for package information. When the file is not specified, Snyk tries to detect the appropriate file for your project.
+
+### `--package-manager=