From 281a6b08cc20391741710686545b532f2889e121 Mon Sep 17 00:00:00 2001 From: Yves Brissaud Date: Tue, 18 Apr 2023 10:26:35 +0200 Subject: [PATCH] Docker scan command is being removed Please see docker scout command instead Learn more at https://docs.docker.com/engine/reference/commandline/scout/ Signed-off-by: Yves Brissaud --- .github/workflows/build-pr.yml | 9 - .github/workflows/release-weekly-build.yml | 19 - Makefile | 30 +- README.md | 10 + builder.Makefile | 57 +- cmd/docker-scan/job_windows.go | 122 ----- cmd/docker-scan/main.go | 178 +------ config/config.go | 75 --- config/config_test.go | 62 --- e2e/auth_test.go | 141 ----- e2e/main_test.go | 122 ----- e2e/optin_test.go | 81 --- e2e/plugin_test.go | 114 ---- e2e/scan_test.go | 494 ------------------ e2e/testdata/Dockerfile | 1 - e2e/testdata/plugin-usage.golden | 24 - e2e/version_test.go | 137 ----- go.mod | 20 +- go.sum | 38 +- internal/authentication/authenticator.go | 165 ------ internal/authentication/authenticator_test.go | 271 ---------- internal/deprecation.go | 18 +- internal/hub/hub.go | 100 ---- internal/hub/instances.go | 100 ---- internal/hub/instances_test.go | 30 -- internal/optin/optin.go | 40 -- internal/optin/optin_test.go | 79 --- internal/provider/containerizedsnyk.go | 338 ------------ internal/provider/error.go | 53 -- internal/provider/error_test.go | 34 -- internal/provider/provider.go | 215 -------- internal/provider/snyk.go | 194 ------- internal/provider/snyk_test.go | 111 ---- internal/provider/testdata/snyk | 17 - internal/version.go | 11 +- internal/version_test.go | 23 +- vars.mk | 7 - 37 files changed, 54 insertions(+), 3486 deletions(-) delete mode 100644 cmd/docker-scan/job_windows.go delete mode 100644 config/config.go delete mode 100644 config/config_test.go delete mode 100644 e2e/auth_test.go delete mode 100644 e2e/main_test.go delete mode 100644 e2e/optin_test.go delete mode 100644 e2e/plugin_test.go delete mode 100644 e2e/scan_test.go delete mode 100644 e2e/testdata/Dockerfile delete mode 100644 e2e/testdata/plugin-usage.golden delete mode 100644 e2e/version_test.go delete mode 100644 internal/authentication/authenticator.go delete mode 100644 internal/authentication/authenticator_test.go delete mode 100644 internal/hub/hub.go delete mode 100644 internal/hub/instances.go delete mode 100644 internal/hub/instances_test.go delete mode 100644 internal/optin/optin.go delete mode 100644 internal/optin/optin_test.go delete mode 100644 internal/provider/containerizedsnyk.go delete mode 100644 internal/provider/error.go delete mode 100644 internal/provider/error_test.go delete mode 100644 internal/provider/provider.go delete mode 100644 internal/provider/snyk.go delete mode 100644 internal/provider/snyk_test.go delete mode 100755 internal/provider/testdata/snyk diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index 1d37fb80..b3db18ef 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -51,21 +51,12 @@ jobs: cache: true id: go - - name: Download binaries - run: make -f builder.Makefile download - - name: Build binary run: make TAG_NAME=${{ github.event.inputs.tag }} -f builder.Makefile build - name: Build Cross run: make cross - - name: Unit Tests - run: make test-unit - - - name: End-to-end Tests - run: make TAG_NAME=${{ github.event.inputs.tag }} -f builder.Makefile e2e - - name: Upload binary artifact uses: actions/upload-artifact@v2 with: diff --git a/.github/workflows/release-weekly-build.yml b/.github/workflows/release-weekly-build.yml index 724a28ad..dc31ead6 100644 --- a/.github/workflows/release-weekly-build.yml +++ b/.github/workflows/release-weekly-build.yml @@ -68,9 +68,6 @@ jobs: cache: true id: go - - name: Download binaries - run: make -f builder.Makefile download - - name: Build binary env: E2E_TEST_AUTH_TOKEN: ${{ secrets.E2E_TEST_AUTH_TOKEN }} @@ -79,22 +76,6 @@ jobs: E2E_HUB_TOKEN: ${{ secrets.E2E_HUB_TOKEN }} run: make TAG_NAME=${{ github.event.inputs.tag }} -f builder.Makefile build - - name: E2E - env: - E2E_TEST_AUTH_TOKEN: ${{ secrets.E2E_TEST_AUTH_TOKEN }} - E2E_HUB_URL: ${{ secrets.E2E_HUB_URL }} - E2E_HUB_USERNAME: ${{ secrets.E2E_HUB_USERNAME }} - E2E_HUB_TOKEN: ${{ secrets.E2E_HUB_TOKEN }} - run: make TAG_NAME=${{ github.event.inputs.tag }} -f builder.Makefile e2e - - - name: Unit test - env: - E2E_TEST_AUTH_TOKEN: ${{ secrets.E2E_TEST_AUTH_TOKEN }} - E2E_HUB_URL: ${{ secrets.E2E_HUB_URL }} - E2E_HUB_USERNAME: ${{ secrets.E2E_HUB_USERNAME }} - E2E_HUB_TOKEN: ${{ secrets.E2E_HUB_TOKEN }} - run: make TAG_NAME=${{ github.event.inputs.tag }} -f builder.Makefile test-unit - - name: Build Mac arm64 binary if: ${{ matrix.os == 'macos-latest' }} run: make TAG_NAME=${{ github.event.inputs.tag }} -f builder.Makefile build-mac-arm64 diff --git a/Makefile b/Makefile index dd762092..95363efe 100644 --- a/Makefile +++ b/Makefile @@ -18,18 +18,10 @@ BUILD_ARGS := --build-arg GO_VERSION=$(GO_VERSION)\ --build-arg CLI_VERSION=$(CLI_VERSION)\ --build-arg ALPINE_VERSION=$(ALPINE_VERSION)\ --build-arg GOLANGCI_LINT_VERSION=$(GOLANGCI_LINT_VERSION) \ - --build-arg TAG_NAME=$(GIT_TAG_NAME) \ - --build-arg GOTESTSUM_VERSION=$(GOTESTSUM_VERSION) \ - --build-arg SNYK_IMAGE_DIGEST=$(SNYK_IMAGE_DIGEST) - -E2E_ENV := --env E2E_TEST_AUTH_TOKEN \ - --env E2E_HUB_URL \ - --env E2E_HUB_USERNAME \ - --env E2E_HUB_TOKEN \ - --env E2E_TEST_NAME + --build-arg TAG_NAME=$(GIT_TAG_NAME) .PHONY: all -all: lint validate build test +all: lint validate build .PHONY: build build: ## Build docker-scan in a container @@ -49,24 +41,6 @@ install: build ## Install docker-scan to your local cli-plugins directory mkdir -p $(HOME)/.docker/cli-plugins cp bin/$(PLATFORM_BINARY) $(HOME)/.docker/cli-plugins/$(BINARY) -.PHONY: test ## Run unit tests then end-to-end tests -test: test-unit e2e - -.PHONY: e2e-build -e2e-build: - docker build $(BUILD_ARGS) . --target e2e -t docker-scan:e2e - -.PHONY: e2e -e2e: e2e-build ## Run the end-to-end tests - @docker run $(E2E_ENV) --rm -v /var/run/docker.sock:/var/run/docker.sock -v $(shell go env GOCACHE):/root/.cache/go-build docker-scan:e2e - -test-unit-build: - docker build $(BUILD_ARGS) . --target test-unit -t docker-scan:test-unit - -.PHONY: test-unit -test-unit: test-unit-build ## Run unit tests - docker run --rm -v $(shell go env GOCACHE):/root/.cache/go-build docker-scan:test-unit - .PHONY: lint lint: ## Run the go linter @docker build . --target lint diff --git a/README.md b/README.md index 137f3faa..169aaca7 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,16 @@ # Docker Scan +:warning: + +The `docker scan` command has been removed. + +To continue learning about the vulnerabilities of your images, and many other features, use the new `docker scout` command. + +Run `docker scout --help`, or learn more at https://docs.docker.com/engine/reference/commandline/scout/ + +--- + Docker Scan is a Command Line Interface to run vulnerability detection on your Dockerfiles and Docker images. diff --git a/builder.Makefile b/builder.Makefile index 478d08a7..7123b810 100644 --- a/builder.Makefile +++ b/builder.Makefile @@ -14,51 +14,13 @@ PKG_NAME=github.com/docker/scan-cli-plugin STATIC_FLAGS= CGO_ENABLED=0 LDFLAGS := "-s -w \ -X $(PKG_NAME)/internal.GitCommit=$(COMMIT) \ - -X $(PKG_NAME)/internal.Version=$(TAG_NAME) \ - -X $(PKG_NAME)/internal/provider.ImageDigest=$(SNYK_IMAGE_DIGEST) \ - -X $(PKG_NAME)/internal/provider.SnykDesktopVersion=$(SNYK_DESKTOP_VERSION)" + -X $(PKG_NAME)/internal.Version=$(TAG_NAME)" GO_BUILD = $(STATIC_FLAGS) go build -trimpath -ldflags=$(LDFLAGS) -SNYK_DOWNLOAD_NAME:=snyk-linux -SNYK_BINARY:=snyk -PWD:=$(shell pwd) -ifeq ($(GOOS),windows) - SNYK_DOWNLOAD_NAME:=snyk-win.exe - SNYK_BINARY=snyk.exe - PWD=$(subst \,/,$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))) -endif -ifeq ($(GOOS),darwin) - SNYK_DOWNLOAD_NAME:=snyk-macos -endif - -ifneq ($(strip $(E2E_TEST_NAME)),) - RUN_TEST=-test.run $(E2E_TEST_NAME) -endif - -VARS:= SNYK_DESKTOP_VERSION=${SNYK_DESKTOP_VERSION}\ - SNYK_USER_VERSION=${SNYK_USER_VERSION}\ - SNYK_OLD_VERSION=${SNYK_OLD_VERSION}\ - DOCKER_CONFIG=$(PWD)/docker-config\ - SNYK_OLD_PATH=$(PWD)/docker-config/snyk-old\ - SNYK_USER_PATH=$(PWD)/docker-config/snyk-user\ - SNYK_DESKTOP_PATH=$(PWD)/docker-config/snyk-desktop - .PHONY: lint lint: golangci-lint run --timeout 10m0s ./... -.PHONY: e2e -e2e: - mkdir -p docker-config/scan - mkdir -p docker-config/cli-plugins - cp ./bin/${PLATFORM_BINARY} docker-config/cli-plugins/${BINARY} - # TODO: gotestsum doesn't forward ldflags to go test with golang 1.15.0, so moving back to go test temporarily - $(VARS) go test ./e2e $(RUN_TEST) -ldflags=$(LDFLAGS) - -.PHONY: test-unit -test-unit: - gotestsum $(shell go list ./... | grep -vE '/e2e') - cross: GOOS=linux GOARCH=amd64 $(GO_BUILD) -o dist/docker-scan_linux_amd64 ./cmd/docker-scan GOOS=linux GOARCH=arm64 $(GO_BUILD) -o dist/docker-scan_linux_arm64 ./cmd/docker-scan @@ -78,20 +40,3 @@ build-linux-arm64: build: mkdir -p bin $(GO_BUILD) -o bin/$(PLATFORM_BINARY) ./cmd/docker-scan - -# For multi-platform (windows,macos,linux) github actions -.PHONY: download -download: - mkdir -p docker-config/snyk-user - curl https://github.com/snyk/snyk/releases/download/v${SNYK_USER_VERSION}/${SNYK_DOWNLOAD_NAME} -L -s -S -o docker-config/snyk-user/${SNYK_BINARY} - chmod +x docker-config/snyk-user/${SNYK_BINARY} - - mkdir -p docker-config/snyk-old - curl https://github.com/snyk/snyk/releases/download/v${SNYK_OLD_VERSION}/${SNYK_DOWNLOAD_NAME} -L -s -S -o docker-config/snyk-old/${SNYK_BINARY} - chmod +x docker-config/snyk-old/${SNYK_BINARY} - - mkdir -p docker-config/snyk-desktop - curl https://github.com/snyk/snyk/releases/download/v${SNYK_DESKTOP_VERSION}/${SNYK_DOWNLOAD_NAME} -L -s -S -o docker-config/snyk-desktop/${SNYK_BINARY} - chmod +x docker-config/snyk-desktop/${SNYK_BINARY} - - GO111MODULE=on go install gotest.tools/gotestsum@v${GOTESTSUM_VERSION} diff --git a/cmd/docker-scan/job_windows.go b/cmd/docker-scan/job_windows.go deleted file mode 100644 index 4d9755c2..00000000 --- a/cmd/docker-scan/job_windows.go +++ /dev/null @@ -1,122 +0,0 @@ -/* - Copyright 2020 Docker Inc. - - 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. -*/ - -/* - Copyright 2020 Docker, Inc. - 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. -*/ - -package main - -import ( - "fmt" - "os" - "syscall" - "unsafe" -) - -func init() { - if err := killSubProcessesOnClose(); err != nil { - fmt.Println("failed to create job:", err) - } -} - -var ( - kernel32 = syscall.NewLazyDLL("kernel32.dll") -) - -type jobObjectExtendedLimitInformation struct { - BasicLimitInformation struct { - PerProcessUserTimeLimit uint64 - PerJobUserTimeLimit uint64 - LimitFlags uint32 - MinimumWorkingSetSize uintptr - MaximumWorkingSetSize uintptr - ActiveProcessLimit uint32 - Affinity uintptr - PriorityClass uint32 - SchedulingClass uint32 - } - IoInfo struct { - ReadOperationCount uint64 - WriteOperationCount uint64 - OtherOperationCount uint64 - ReadTransferCount uint64 - WriteTransferCount uint64 - OtherTransferCount uint64 - } - ProcessMemoryLimit uintptr - JobMemoryLimit uintptr - PeakProcessMemoryUsed uintptr - PeakJobMemoryUsed uintptr -} - -// killSubProcessesOnClose will ensure on windows that all child processes of the current process are killed if parent is killed. -func killSubProcessesOnClose() error { - job, err := createJobObject() - if err != nil { - return err - } - info := jobObjectExtendedLimitInformation{} - info.BasicLimitInformation.LimitFlags = 0x2000 - if err := setInformationJobObject(job, info); err != nil { - _ = syscall.CloseHandle(job) - return err - } - proc, err := syscall.GetCurrentProcess() - if err != nil { - _ = syscall.CloseHandle(job) - return err - } - if err := assignProcessToJobObject(job, proc); err != nil { - _ = syscall.CloseHandle(job) - return err - } - return nil -} - -func createJobObject() (syscall.Handle, error) { - res, _, err := kernel32.NewProc("CreateJobObjectW").Call(uintptr(unsafe.Pointer(nil)), uintptr(unsafe.Pointer(nil))) - if res == 0 { - return syscall.InvalidHandle, os.NewSyscallError("CreateJobObject", err) - } - return syscall.Handle(res), nil -} - -func setInformationJobObject(job syscall.Handle, info jobObjectExtendedLimitInformation) error { - infoClass := uint32(9) - res, _, err := kernel32.NewProc("SetInformationJobObject").Call(uintptr(job), uintptr(infoClass), uintptr(unsafe.Pointer(&info)), uintptr(uint32(unsafe.Sizeof(info)))) - if res == 0 { - return os.NewSyscallError("SetInformationJobObject", err) - } - return nil -} - -func assignProcessToJobObject(job syscall.Handle, process syscall.Handle) error { - res, _, err := kernel32.NewProc("AssignProcessToJobObject").Call(uintptr(job), uintptr(process)) - if res == 0 { - return os.NewSyscallError("AssignProcessToJobObject", err) - } - return nil -} diff --git a/cmd/docker-scan/main.go b/cmd/docker-scan/main.go index 36794772..0e87e382 100644 --- a/cmd/docker-scan/main.go +++ b/cmd/docker-scan/main.go @@ -20,28 +20,23 @@ import ( "context" "fmt" "os" - "os/exec" "os/signal" - "runtime" "syscall" + "github.com/docker/cli/cli" + "github.com/docker/cli/cli-plugins/manager" "github.com/docker/cli/cli-plugins/plugin" "github.com/docker/cli/cli/command" - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/registry" - "github.com/docker/scan-cli-plugin/config" "github.com/docker/scan-cli-plugin/internal" - "github.com/docker/scan-cli-plugin/internal/optin" - "github.com/docker/scan-cli-plugin/internal/provider" "github.com/spf13/cobra" ) func main() { - ctx, closeFunc := newSigContext() + _, closeFunc := newSigContext() defer closeFunc() plugin.Run(func(dockerCli command.Cli) *cobra.Command { - cmd := newScanCmd(ctx, dockerCli) + cmd := newScanCmd(dockerCli) originalPreRun := cmd.PersistentPreRunE cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { if err := plugin.PersistentPreRunE(cmd, args); err != nil { @@ -61,20 +56,10 @@ func main() { } type options struct { - login bool - token string - dependencyTree bool - dockerFilePath string - excludeBase bool - jsonFormat bool - showVersion bool - forceOptIn bool - forceOptOut bool - severity string - groupIssues bool + showVersion bool } -func newScanCmd(ctx context.Context, dockerCli command.Cli) *cobra.Command { +func newScanCmd(dockerCli command.Cli) *cobra.Command { var flags options cmd := &cobra.Command{ Short: "Docker Scan", @@ -82,164 +67,27 @@ func newScanCmd(ctx context.Context, dockerCli command.Cli) *cobra.Command { Use: "scan [OPTIONS] IMAGE", Annotations: map[string]string{}, RunE: func(cmd *cobra.Command, args []string) error { - if !flags.jsonFormat { - internal.PrintDeprecationMessage(dockerCli) - } - if flags.showVersion { - return runVersion(ctx, dockerCli, flags) + return runVersion() } - if flags.login { - return runAuthentication(ctx, dockerCli, flags, args) + internal.PrintEOLMessage(dockerCli) + return cli.StatusError{ + StatusCode: 1, + Status: "error: docker scan has been removed", } - return runScan(ctx, cmd, dockerCli, flags, args) }, } - cmd.Flags().BoolVar(&flags.login, "login", false, "Authenticate to the scan provider using an optional token (with --token), or web base token if empty") - cmd.Flags().StringVar(&flags.token, "token", "", "Authentication token to login to the third party scanning provider") - cmd.Flags().BoolVar(&flags.dependencyTree, "dependency-tree", false, "Show dependency tree with scan results") - cmd.Flags().BoolVar(&flags.excludeBase, "exclude-base", false, "Exclude base image from vulnerability scanning (requires --file)") - cmd.Flags().StringVarP(&flags.dockerFilePath, "file", "f", "", "Dockerfile associated with image, provides more detailed results") - cmd.Flags().BoolVar(&flags.jsonFormat, "json", false, "Output results in JSON format") cmd.Flags().BoolVar(&flags.showVersion, "version", false, "Display version of the scan plugin") - cmd.Flags().BoolVar(&flags.forceOptIn, "accept-license", false, "Accept using a third party scanning provider") - cmd.Flags().BoolVar(&flags.forceOptOut, "reject-license", false, "Reject using a third party scanning provider") - cmd.Flags().StringVar(&flags.severity, "severity", "", "Only report vulnerabilities of provided level or higher (low|medium|high)") - cmd.Flags().BoolVar(&flags.groupIssues, "group-issues", false, "Aggregate duplicated vulnerabilities and group them to a single one (requires --json)") return cmd } -func configureProvider(ctx context.Context, dockerCli command.Cli, flags options, options ...provider.Ops) (provider.Provider, error) { - conf, err := checkConsent(flags, dockerCli) - if err != nil { - return nil, err - } - - opts := []provider.Ops{ - provider.WithContext(ctx), - provider.WithPath(conf.Path), - } - opts = append(opts, options...) - if flags.jsonFormat { - opts = append(opts, provider.WithJSON()) - opts = append(opts, provider.WithExperimental()) - if flags.groupIssues { - opts = append(opts, provider.WithGroupIssues()) - } - } else if flags.groupIssues { - return nil, fmt.Errorf("--json flag is mandatory to use --group-issues flag") - } - opts = append(opts, provider.WithAppVulns()) - - if flags.dockerFilePath != "" { - opts = append(opts, provider.WithDockerFile(flags.dockerFilePath)) - if flags.excludeBase { - opts = append(opts, provider.WithoutBaseImageVulnerabilities()) - } - } else if flags.excludeBase { - return nil, fmt.Errorf("--file flag is mandatory to use --exclude-base flag") - } - if flags.dependencyTree { - opts = append(opts, provider.WithDependencyTree()) - } - if flags.severity != "" { - if flags.severity != "low" && flags.severity != "medium" && flags.severity != "high" { - return nil, fmt.Errorf("--severity takes only 'low', 'medium' or 'high' values") - } - opts = append(opts, provider.WithSeverity(flags.severity)) - } - defaultProvider, err := provider.NewProvider(opts...) - if err != nil { - return nil, err - } - if runtime.GOOS == "linux" && !provider.UseExternalBinary(defaultProvider) { - return provider.NewDockerSnykProvider(dockerCli, defaultProvider) - } - return provider.NewSnykProvider(defaultProvider) -} - -func checkConsent(flags options, dockerCli command.Streams) (config.Config, error) { - conf, err := config.ReadConfigFile() - if err != nil { - return config.Config{}, err - } - if flags.showVersion { - return conf, nil - } - - if !conf.Optin || flags.forceOptIn || flags.forceOptOut { - switch { - case !flags.forceOptOut && !flags.forceOptIn: - conf.Optin = optin.AskForConsent(dockerCli.In(), dockerCli.Out()) - case flags.forceOptOut && flags.forceOptIn: - return config.Config{}, fmt.Errorf("enable and disable flags are mutualy exlusive") - case flags.forceOptIn: - conf.Optin = true - case flags.forceOptOut: - conf.Optin = false - } - - if err := config.SaveConfigFile(conf); err != nil { - return config.Config{}, err - } - if !conf.Optin { - os.Exit(0) - } - } - return conf, nil -} - -func runVersion(ctx context.Context, dockerCli command.Cli, flags options) error { - scanProvider, err := configureProvider(ctx, dockerCli, flags) - if err != nil { - return err - } - - version, err := internal.FullVersion(scanProvider) - if err != nil { - return err - } +func runVersion() error { + version := internal.FullVersion() fmt.Println(version) return nil } -func runAuthentication(ctx context.Context, dockerCli command.Cli, flags options, args []string) error { - if len(args) != 0 { - return fmt.Errorf(`--login flag expects no argument`) - } - scanProvider, err := configureProvider(ctx, dockerCli, flags) - if err != nil { - return err - } - return scanProvider.Authenticate(flags.token) -} - -func runScan(ctx context.Context, cmd *cobra.Command, dockerCli command.Cli, flags options, args []string) error { - scanProvider, err := configureProvider(ctx, dockerCli, flags, provider.WithAuthConfig(func(hub *registry.IndexInfo) types.AuthConfig { - return command.ResolveAuthConfig(context.Background(), dockerCli, hub) - }), provider.WithVersion(internal.Version)) - if len(args) != 1 { - if err := cmd.Usage(); err != nil { - return err - } - return fmt.Errorf(`"docker scan" requires exactly 1 argument`) - } - if err != nil { - return err - } - err = scanProvider.Scan(args[0]) - - if !flags.jsonFormat { - internal.PrintDeprecationMessage(dockerCli) - } - - if _, ok := err.(*exec.ExitError); ok { - os.Exit(1) - } - return err -} - func newSigContext() (context.Context, func()) { ctx, cancel := context.WithCancel(context.Background()) s := make(chan os.Signal, 1) diff --git a/config/config.go b/config/config.go deleted file mode 100644 index 6c11e94f..00000000 --- a/config/config.go +++ /dev/null @@ -1,75 +0,0 @@ -/* - Copyright 2020 Docker Inc. - - 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. -*/ - -package config - -import ( - "encoding/json" - "os" - "path/filepath" - "runtime" - - "github.com/pkg/errors" - - cliConfig "github.com/docker/cli/cli/config" -) - -// Config points to scan provider's binary -type Config struct { - Path string `json:"path"` - Optin bool `json:"optin"` -} - -// ReadConfigFile tries to read docker-scan configuration file that -// should be at ${DOCKER_CONFIG}/scan/config.json -func ReadConfigFile() (Config, error) { - var conf Config - path := filepath.Join(cliConfig.Dir(), "scan", "config.json") - if runtime.GOOS != "windows" && runtime.GOOS != "darwin" { - _, err := os.Stat(path) - if err != nil && os.IsNotExist(err) { - err := SaveConfigFile(Config{}) - if err != nil { - return conf, errors.Wrapf(err, "failed to create initial scan configuration file %q", path) - } - } - } - buf, err := os.ReadFile(path) - if err != nil { - _ = os.Remove(path) - return conf, errors.Wrap(err, "failed to read docker scan configuration file. Please restart Docker Desktop") - } - if err := json.Unmarshal(buf, &conf); err != nil { - _ = os.Remove(path) - return conf, errors.Wrapf(err, "invalid docker scan configuration file %s. Please restart Docker Desktop", path) - } - return conf, nil -} - -// SaveConfigFile tries to save docker-scan configuration file that -// should be at ${DOCKER_CONFIG}/scan/config.json -func SaveConfigFile(conf Config) error { - out, err := json.Marshal(conf) - if err != nil { - return err - } - if err = os.MkdirAll(filepath.Join(cliConfig.Dir(), "scan"), 0744); err != nil { - return errors.Wrap(err, "failed to create docker scan configuration directory") - } - - path := filepath.Join(cliConfig.Dir(), "scan", "config.json") - return errors.Wrap(os.WriteFile(path, out, os.FileMode(0644)), "failed to write docker scan configuration file") -} diff --git a/config/config_test.go b/config/config_test.go deleted file mode 100644 index 764c319d..00000000 --- a/config/config_test.go +++ /dev/null @@ -1,62 +0,0 @@ -/* - Copyright 2020 Docker Inc. - - 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. -*/ - -package config - -import ( - "encoding/json" - "os" - "path/filepath" - "testing" - - dockerConfigFile "github.com/docker/cli/cli/config/configfile" - "gotest.tools/v3/assert" - "gotest.tools/v3/env" -) - -func TestSaveConfigFile(t *testing.T) { - configDir, err := os.MkdirTemp(os.TempDir(), "config") - assert.NilError(t, err) - defer os.RemoveAll(configDir) //nolint:errcheck - - configFilePath := filepath.Join(configDir, "config.json") - dockerConfig := dockerConfigFile.ConfigFile{ - CLIPluginsExtraDirs: []string{ - "cli-plugins", - }, - Filename: configFilePath, - } - configFile, err := os.Create(configFilePath) - assert.NilError(t, err) - //nolint:errcheck - defer configFile.Close() - err = json.NewEncoder(configFile).Encode(dockerConfig) - if err != nil { - panic(err) - } - - defer env.Patch(t, "DOCKER_CONFIG", configDir) - - expected := Config{ - Path: configDir, - Optin: false, - } - assert.NilError(t, SaveConfigFile(expected)) - - result, err := ReadConfigFile() - assert.NilError(t, err) - assert.Equal(t, result, expected) -} diff --git a/e2e/auth_test.go b/e2e/auth_test.go deleted file mode 100644 index 7dc8e872..00000000 --- a/e2e/auth_test.go +++ /dev/null @@ -1,141 +0,0 @@ -/* - Copyright 2020 Docker Inc. - - 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. -*/ - -package e2e - -import ( - "fmt" - "os" - "runtime" - "strings" - "testing" - - "gotest.tools/v3/assert" - "gotest.tools/v3/env" - "gotest.tools/v3/icmd" -) - -func TestSnykAuthentication(t *testing.T) { - if runtime.GOOS != "darwin" && runtime.GOOS != "windows" { - t.Skip("invalid test: only on Docker Desktop") - } - // Add snyk binary to the path - path := os.Getenv("PATH") - defer env.Patch(t, "PATH", fmt.Sprintf(pathFormat(), os.Getenv("SNYK_DESKTOP_PATH"), path))() - - // create Snyk config file with empty token - homeDir, cleanFunction := createSnykConfFile(t, "") - defer cleanFunction() - - cmd, configDir, cleanup := dockerCli.createTestCmd() - defer cleanup() - createScanConfigFile(t, configDir) - - token := os.Getenv("E2E_TEST_AUTH_TOKEN") - assert.Assert(t, token != "", "E2E_TEST_AUTH_TOKEN needs to be filled") - - cmd.Command = dockerCli.Command("scan", "--accept-license", "--login", "--token", token) - icmd.RunCmd(cmd).Assert(t, icmd.Success) - - // snyk config file should be updated - buff, err := os.ReadFile(homeDir.Join(".config", "configstore", "snyk.json")) - assert.NilError(t, err) - assert.Assert(t, strings.Contains(string(buff), token), string(buff)) -} - -func TestAuthenticationFlagFailsWithImage(t *testing.T) { - cmd, _, cleanup := dockerCli.createTestCmd() - defer cleanup() - - token := os.Getenv("E2E_TEST_AUTH_TOKEN") - assert.Assert(t, token != "", "E2E_TEST_AUTH_TOKEN needs to be filled") - - cmd.Command = dockerCli.Command("scan", "--accept-license", "--login", "--token", token, "example:image") - icmd.RunCmd(cmd).Assert(t, icmd.Expected{ - ExitCode: 1, - Err: "--login flag expects no argument", - }) -} - -func TestAuthenticationChecksToken(t *testing.T) { - cmd, configDir, cleanup := dockerCli.createTestCmd() - defer cleanup() - createScanConfigFile(t, configDir) - - cmd.Command = dockerCli.Command("scan", "--accept-license", "--login", "--token", "invalid-token") - icmd.RunCmd(cmd).Assert(t, icmd.Expected{ - ExitCode: 1, - Err: `invalid authentication token "invalid-token"`, - }) -} - -func TestAuthWithContainerizedSnyk(t *testing.T) { - if runtime.GOOS == "darwin" || runtime.GOOS == "windows" { - t.Skip("invalid test on Docker Desktop") - } - cmd, configDir, cleanup := dockerCli.createTestCommand(false) - defer cleanup() - createScanConfigFileOptinAndPath(t, configDir, true, "") - - // create Snyk config directories without the config file - homeDir, cleanFunction := createSnykConfDirectories(t, false, "") - defer cleanFunction() - - token := os.Getenv("E2E_TEST_AUTH_TOKEN") - assert.Assert(t, token != "", "E2E_TEST_AUTH_TOKEN needs to be filled") - - cmd.Command = dockerCli.Command("scan", "--accept-license", "--login", "--token", token) - cmd.Env = append(cmd.Env, fmt.Sprintf("HOME=%s", homeDir.Path())) - icmd.RunCmd(cmd).Assert(t, icmd.Success) - - // snyk config file should be created - buff, err := os.ReadFile(homeDir.Join(".config", "configstore", "snyk.json")) - assert.NilError(t, err) - assert.Assert(t, strings.Contains(string(buff), token)) -} - -func TestAuthWithContainerSnykFlagFailsWithImage(t *testing.T) { - if runtime.GOOS == "darwin" || runtime.GOOS == "windows" { - t.Skip("invalid test on Docker Desktop") - } - cmd, configDir, cleanup := dockerCli.createTestCommand(false) - defer cleanup() - createScanConfigFileOptinAndPath(t, configDir, true, "") - - token := os.Getenv("E2E_TEST_AUTH_TOKEN") - assert.Assert(t, token != "", "E2E_TEST_AUTH_TOKEN needs to be filled") - - cmd.Command = dockerCli.Command("scan", "--accept-license", "--login", "--token", token, "example:image") - icmd.RunCmd(cmd).Assert(t, icmd.Expected{ - ExitCode: 1, - Err: "--login flag expects no argument", - }) -} - -func TestAuthWithContainerSnykChecksToken(t *testing.T) { - if runtime.GOOS == "darwin" || runtime.GOOS == "windows" { - t.Skip("invalid test on Docker Desktop") - } - cmd, configDir, cleanup := dockerCli.createTestCommand(false) - defer cleanup() - createScanConfigFileOptinAndPath(t, configDir, true, "") - - cmd.Command = dockerCli.Command("scan", "--accept-license", "--login", "--token", "invalid-token") - icmd.RunCmd(cmd).Assert(t, icmd.Expected{ - ExitCode: 1, - Err: `invalid authentication token "invalid-token"`, - }) -} diff --git a/e2e/main_test.go b/e2e/main_test.go deleted file mode 100644 index a2261f9b..00000000 --- a/e2e/main_test.go +++ /dev/null @@ -1,122 +0,0 @@ -/* - Copyright 2020 Docker Inc. - - 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. -*/ - -package e2e - -import ( - "encoding/json" - "os" - "path/filepath" - "runtime" - "testing" - - dockerConfigFile "github.com/docker/cli/cli/config/configfile" - "gotest.tools/v3/icmd" -) - -var ( - dockerCli dockerCliCommand -) - -type dockerCliCommand struct { - path string - cliPluginDir string -} - -func (d dockerCliCommand) createTestCmd() (icmd.Cmd, string, func()) { - return d.createTestCommand(true) -} - -func (d dockerCliCommand) createTestCommand(withSnykBinary bool) (icmd.Cmd, string, func()) { - configDir, err := os.MkdirTemp(os.TempDir(), "config") - if err != nil { - panic(err) - } - if err := os.MkdirAll(filepath.Join(configDir, "scan"), 0744); err != nil { - panic(err) - } - sourceDir := os.Getenv("SNYK_DESKTOP_PATH") - if withSnykBinary { - copyBinary("snyk", sourceDir, filepath.Join(configDir, "scan")) - } - - configFilePath := filepath.Join(configDir, "config.json") - dockerConfig := dockerConfigFile.ConfigFile{ - CLIPluginsExtraDirs: []string{ - d.cliPluginDir, - }, - Filename: configFilePath, - } - configFile, err := os.Create(configFilePath) - if err != nil { - panic(err) - } - //nolint:errcheck - defer configFile.Close() - err = json.NewEncoder(configFile).Encode(dockerConfig) - if err != nil { - panic(err) - } - cleanup := func() { - _ = os.RemoveAll(configDir) - } - env := append(os.Environ(), - "DOCKER_CONFIG="+configDir, - "DOCKER_CLI_EXPERIMENTAL=enabled") // TODO: Remove this once docker app plugin is no more experimental - return icmd.Cmd{Env: env}, configDir, cleanup -} - -func (d dockerCliCommand) Command(args ...string) []string { - return append([]string{d.path}, args...) -} - -func TestMain(m *testing.M) { - // Prepare docker cli to call the docker-scan plugin binary: - // - Create a symbolic link with the docker-scan binary to the plugin directory - cliPluginDir, err := os.MkdirTemp(os.TempDir(), "configContent") - if err != nil { - panic(err) - } - //nolint:errcheck - defer os.RemoveAll(cliPluginDir) - sourceDir := filepath.Join(os.Getenv("DOCKER_CONFIG"), "cli-plugins") - copyBinary("docker-scan", sourceDir, cliPluginDir) - - dockerCli = dockerCliCommand{path: "docker", cliPluginDir: cliPluginDir} - os.Exit(m.Run()) -} - -func copyBinary(binaryName, sourceDir, configDir string) { - if runtime.GOOS == "windows" { - binaryName += ".exe" - } - input, err := os.ReadFile(filepath.Join(sourceDir, binaryName)) - if err != nil { - panic(err) - } - err = os.WriteFile(filepath.Join(configDir, binaryName), input, 0744) - if err != nil { - panic(err) - } -} - -func pathFormat() string { - pathFormat := "%s:%s" - if runtime.GOOS == "windows" { - pathFormat = "%s;%s" - } - return pathFormat -} diff --git a/e2e/optin_test.go b/e2e/optin_test.go deleted file mode 100644 index 2aed8c0d..00000000 --- a/e2e/optin_test.go +++ /dev/null @@ -1,81 +0,0 @@ -//go:build !windows - -/* - Copyright 2020 Docker Inc. - - 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. -*/ - -package e2e - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - "strings" - "testing" - "time" - - "github.com/docker/scan-cli-plugin/config" - "gotest.tools/v3/assert" - "gotest.tools/v3/icmd" -) - -func TestFirstScanOptinMessage(t *testing.T) { - cmd, configDir, cleanup := dockerCli.createTestCmd() - defer cleanup() - // create a scan config file with optin disabled - createScanConfigFileOptin(t, configDir, false) - - // docker scan should ask for consent the first time the user runs it - in, out, err := os.Pipe() - assert.NilError(t, err) - cmd.Command = dockerCli.Command("scan", "myimage") - cmd.Stdin = in - - go func() { - time.Sleep(20 * time.Millisecond) - fmt.Fprintln(out, "y") - }() - - result := icmd.RunCmd(cmd) - assert.Assert(t, strings.Contains(result.Combined(), `Docker Scan relies upon access to Snyk, a third party provider, do you consent to proceed using Snyk? (y/N)`)) - - // check the consent has been stored in config file - data, err := os.ReadFile(filepath.Join(configDir, "scan", "config.json")) - assert.NilError(t, err) - var conf config.Config - assert.NilError(t, json.Unmarshal(data, &conf)) - assert.Equal(t, conf.Optin, true) -} - -func TestRefuseOptinWithDisableFlag(t *testing.T) { - cmd, configDir, cleanup := dockerCli.createTestCmd() - defer cleanup() - createScanConfigFile(t, configDir) - - // docker scan myimage should exit immediately - cmd.Command = dockerCli.Command("scan", "--reject-license", "myimage") - icmd.RunCmd(cmd).Assert(t, icmd.Expected{ - ExitCode: 0, - Out: "", - }) - - // check the consent has been stored in config file - data, err := os.ReadFile(filepath.Join(configDir, "scan", "config.json")) - assert.NilError(t, err) - var conf config.Config - assert.NilError(t, json.Unmarshal(data, &conf)) - assert.Equal(t, conf.Optin, false) -} diff --git a/e2e/plugin_test.go b/e2e/plugin_test.go deleted file mode 100644 index e894410b..00000000 --- a/e2e/plugin_test.go +++ /dev/null @@ -1,114 +0,0 @@ -/* - Copyright 2020 Docker Inc. - - 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. -*/ - -package e2e - -import ( - "fmt" - "os" - "path/filepath" - "runtime" - "strings" - "syscall" - "testing" - "time" - - "github.com/mitchellh/go-ps" - "gotest.tools/v3/assert" - "gotest.tools/v3/env" - "gotest.tools/v3/golden" - "gotest.tools/v3/icmd" -) - -func TestInvokePluginFromCLI(t *testing.T) { - cmd, _, cleanup := dockerCli.createTestCmd() - defer cleanup() - // docker --help should list app as a top command - cmd.Command = dockerCli.Command("--help") - icmd.RunCmd(cmd).Assert(t, icmd.Expected{ - Out: "scan* Docker Scan (Docker Inc.", - }) - - // docker app --help prints docker-app help - cmd.Command = dockerCli.Command("scan", "--help") - usage := icmd.RunCmd(cmd).Assert(t, icmd.Success).Combined() - - goldenFile := "plugin-usage.golden" - golden.Assert(t, usage, goldenFile) -} - -func TestHandleCtrlCGracefully(t *testing.T) { - cmd, configDir, cleanup := dockerCli.createTestCmd() - defer cleanup() - - // Create a dummy snyk binary which takes long to exit - assert.NilError(t, os.WriteFile(filepath.Join(configDir, "scan", "snyk"), []byte(`#!/bin/sh -sleep 1000`), 0700)) - createScanConfigFile(t, configDir) - - // Add mock snyk binary to the $PATH - path := os.Getenv("PATH") - path = fmt.Sprintf(pathFormat(), configDir+"/scan", path) - env.Patch(t, "PATH", path)() - // force the env variable on command side on Windows - if runtime.GOOS == "windows" { - cmd.Env = append(cmd.Env, fmt.Sprintf("PATH=%s", path)) - } - - cmd.Command = dockerCli.Command("scan", "--version") - - icmd.StartCmd(cmd) - time.Sleep(1 * time.Second) - - // Snyk command should be running - scanProcess := shouldProcessBeRunning(t, "docker-scan", true, 0) - sp, err := os.FindProcess(scanProcess.Pid()) - assert.NilError(t, err) - - shouldProcessBeRunning(t, "snyk", true, scanProcess.Pid()) - - // send interrupt signal to the docker scan --version command - switch runtime.GOOS { - case "windows": - assert.NilError(t, sp.Kill()) // windows can't handle SIGINT - case "darwin", "linux": - assert.NilError(t, sp.Signal(syscall.SIGINT)) - default: - t.Fatalf("os %q not supported", runtime.GOOS) - } - time.Sleep(1 * time.Second) - - // Snyk command should be terminated too - shouldProcessBeRunning(t, "snyk", false, scanProcess.Pid()) -} - -func shouldProcessBeRunning(t *testing.T, executable string, running bool, PPID int) ps.Process { - t.Helper() - processes, err := ps.Processes() - assert.NilError(t, err) - for _, process := range processes { - if strings.HasPrefix(process.Executable(), executable) || (PPID != 0 && process.PPid() == PPID) { - assert.Assert(t, process.Pid() != 0) - if PPID != 0 { - assert.Equal(t, process.PPid(), PPID) // snyk is the child process of docker scan - } - assert.Assert(t, running) - return process - } - } - assert.Assert(t, !running) - return nil -} diff --git a/e2e/scan_test.go b/e2e/scan_test.go deleted file mode 100644 index 0629cef8..00000000 --- a/e2e/scan_test.go +++ /dev/null @@ -1,494 +0,0 @@ -/* - Copyright 2020 Docker Inc. - - 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. -*/ - -package e2e - -import ( - "encoding/json" - "fmt" - "os" - "path" - "path/filepath" - "runtime" - "strings" - "testing" - - "github.com/docker/cli/cli/config/configfile" - "github.com/docker/cli/cli/config/types" - "github.com/docker/scan-cli-plugin/config" - "gotest.tools/v3/assert" - "gotest.tools/v3/assert/cmp" - "gotest.tools/v3/env" - "gotest.tools/v3/fs" - "gotest.tools/v3/icmd" -) - -const ( - ImageWithVulnerabilities = "alpine:3.10.0" - ImageWithoutVulnerabilities = "hello-world" - InvalidImage = "dockerscanci/scratch:1.0" // FROM scratch - ImageBaseImageVulnerabilities = "dockerscanci/base-image-vulns:1.0" // FROM alpine:3.10.0 - LocalBuildImage = "local:build" -) - -func TestScanFailsNoAuthentication(t *testing.T) { - // create Snyk config file with empty token - _, cleanFunction := createSnykConfFile(t, "") - defer cleanFunction() - - cmd, configDir, cleanup := dockerCli.createTestCmd() - defer cleanup() - createScanConfigFile(t, configDir) - - // write dockerCli config with authentication to a registry which isn't Hub - patchConfig(t, configDir, "com.example.registry", "invalid-user", "invalid-password") - - cmd.Command = dockerCli.Command("scan", "--accept-license", "example:image") - icmd.RunCmd(cmd).Assert(t, icmd.Expected{ - ExitCode: 1, - Err: `failed to get DockerScanID: You need to be logged in to Docker Hub to use the scan feature.`, - }) -} - -func TestScanFailsWithCleanMessage(t *testing.T) { - // create Snyk config file with empty token - _, cleanFunction := createSnykConfFile(t, "") - defer cleanFunction() - - cmd, configDir, cleanup := dockerCli.createTestCmd() - defer cleanup() - createScanConfigFile(t, configDir) - - cmd.Command = dockerCli.Command("logout") - icmd.RunCmd(cmd).Assert(t, icmd.Success) - - cmd.Command = dockerCli.Command("scan", "--accept-license", "example:image") - icmd.RunCmd(cmd).Assert(t, icmd.Expected{ - ExitCode: 1, - Err: `failed to get DockerScanID: You need to be logged in to Docker Hub to use the scan feature.`, - }) -} - -func TestScanSucceedWithDockerHub(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("Can't run on this ci platform (image does not exist fir the current platform)") - } - cmd, configDir, cleanup := dockerCli.createTestCmd() - defer cleanup() - - createScanConfigFile(t, configDir) - - // write dockerCli config with authentication to the Hub - patchConfig(t, configDir, os.Getenv("E2E_HUB_URL"), os.Getenv("E2E_HUB_USERNAME"), os.Getenv("E2E_HUB_TOKEN")) - - cmd.Command = dockerCli.Command("scan", ImageWithVulnerabilities) - result := icmd.RunCmd(cmd) - assert.Assert(t, result.ExitCode == 1) - if strings.HasPrefix(result.Combined(), "You") { - // We reach the monthly limits of 10 free scans - assert.Assert(t, strings.Contains(result.Combined(), "You have reached the scan limit of 10 monthly scans without authentication."), result.Combined()) - } else { - assert.Assert(t, cmp.Regexp("found .* vulnerabilities", result.Combined()), result.Combined()) - - } - -} - -func TestScanWithSnyk(t *testing.T) { - if runtime.GOOS != "linux" { - t.Skip("Can't run on this ci platform (windows containers or no engine installed)") - } - _, cleanFunction := createSnykConfFile(t, os.Getenv("E2E_TEST_AUTH_TOKEN")) - defer cleanFunction() - - cmd, configDir, cleanup := dockerCli.createTestCmd() - defer cleanup() - - createScanConfigFile(t, configDir) - - testCases := []struct { - name string - image string - exitCode int - contains string - }{ - { - name: "image-without-vulnerabilities", - image: ImageWithoutVulnerabilities, - exitCode: 0, - contains: "no vulnerable paths found", - }, - // Due to an issue linked to github actions env, we removed the test for the moment - // we got the error message that Snyk returns when it can't connect to the engine 'Invalid Docker archive' - /*{ - name: "invalid-docker-archive", - image: InvalidImage, - exitCode: 1, - contains: "(HTTP code 500) server error - empty export - not implemented", - },*/ - { - name: "image-with-vulnerabilities", - image: ImageWithVulnerabilities, - exitCode: 1, - contains: "vulnerability found", - }, - { - name: "invalid-image-name", - image: "scratch", - exitCode: 1, - contains: "manifest unknown", - }, - } - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - cmd.Command = dockerCli.Command("scan", testCase.image) - output := icmd.RunCmd(cmd).Assert(t, icmd.Expected{ExitCode: testCase.exitCode}).Combined() - assert.Assert(t, strings.Contains(output, testCase.contains), output) - }) - } -} - -func TestScanJsonOutput(t *testing.T) { - if runtime.GOOS != "linux" { - t.Skip("Can't run on this ci platform (windows containers or no engine installed)") - } - _, cleanFunction := createSnykConfFile(t, os.Getenv("E2E_TEST_AUTH_TOKEN")) - defer cleanFunction() - - cmd, configDir, cleanup := dockerCli.createTestCmd() - defer cleanup() - - createScanConfigFile(t, configDir) - - testCases := []struct { - name string - image string - exitCode int - isEmpty bool - }{ - { - name: "image-without-vulnerabilities", - image: ImageWithoutVulnerabilities, - exitCode: 0, - isEmpty: true, - }, - { - name: "invalid-docker-archive", - image: InvalidImage, - exitCode: 1, - isEmpty: true, - }, - { - name: "image-with-vulnerabilities", - image: ImageWithVulnerabilities, - exitCode: 1, - isEmpty: false, - }, - } - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - cmd.Command = dockerCli.Command("scan", "--accept-license", "--json", testCase.image) - output := icmd.RunCmd(cmd).Assert(t, icmd.Expected{ExitCode: testCase.exitCode}).Combined() - var jsonOutput JSONOutput - assert.NilError(t, json.Unmarshal([]byte(output), &jsonOutput)) - assert.Equal(t, len(jsonOutput.Vulnerabilities) == 0, testCase.isEmpty) - }) - } -} - -type JSONOutput struct { - Vulnerabilities []interface{} `json:"vulnerabilities"` -} - -func TestScanWithFileAndExcludeBaseImageVulns(t *testing.T) { - if runtime.GOOS != "linux" { - t.Skip("Can't run on this ci platform (windows containers or no engine installed)") - } - _, cleanFunction := createSnykConfFile(t, os.Getenv("E2E_TEST_AUTH_TOKEN")) - defer cleanFunction() - - cmd, configDir, cleanup := dockerCli.createTestCmd() - defer cleanup() - - createScanConfigFile(t, configDir) - - cmd.Command = dockerCli.Command("scan", "--accept-license", "--file", "./testdata/Dockerfile", "--exclude-base", ImageBaseImageVulnerabilities) - output := icmd.RunCmd(cmd).Assert(t, icmd.Success).Combined() - assert.Assert(t, strings.Contains(output, "no vulnerable paths found.")) -} - -func TestScanWithExcludeBaseImageVulns(t *testing.T) { - if runtime.GOOS != "linux" { - t.Skip("Can't run on this ci platform (windows containers or no engine installed)") - } - _, cleanFunction := createSnykConfFile(t, os.Getenv("E2E_TEST_AUTH_TOKEN")) - defer cleanFunction() - - cmd, configDir, cleanup := dockerCli.createTestCmd() - defer cleanup() - - createScanConfigFile(t, configDir) - - cmd.Command = dockerCli.Command("scan", "--accept-license", "--exclude-base", ImageBaseImageVulnerabilities) - icmd.RunCmd(cmd).Assert(t, icmd.Expected{ - ExitCode: 1, - Err: "--file flag is mandatory to use --exclude-base flag"}) -} - -func TestScanWithDependencies(t *testing.T) { - if runtime.GOOS != "linux" { - t.Skip("Can't run on this ci platform (windows containers or no engine installed)") - } - _, cleanFunction := createSnykConfFile(t, os.Getenv("E2E_TEST_AUTH_TOKEN")) - defer cleanFunction() - - cmd, configDir, cleanup := dockerCli.createTestCmd() - defer cleanup() - - createScanConfigFile(t, configDir) - - cmd.Command = dockerCli.Command("scan", "--accept-license", "--dependency-tree", ImageWithVulnerabilities) - output := icmd.RunCmd(cmd).Assert(t, icmd.Expected{ExitCode: 1}).Combined() - assert.Assert(t, strings.Contains(output, "docker-image|alpine @ 3.10.0")) // beginning of the dependency tree - assert.Assert(t, strings.Contains(output, "vulnerability found")) -} - -func TestScanWithSeverity(t *testing.T) { - if runtime.GOOS != "linux" { - t.Skip("Can't run on this ci platform (windows containers or no engine installed)") - } - _, cleanFunction := createSnykConfFile(t, os.Getenv("E2E_TEST_AUTH_TOKEN")) - defer cleanFunction() - - cmd, configDir, cleanup := dockerCli.createTestCmd() - defer cleanup() - - createScanConfigFile(t, configDir) - - cmd.Command = dockerCli.Command("scan", "--accept-license", "--severity=medium", ImageWithVulnerabilities) - output := icmd.RunCmd(cmd).Assert(t, icmd.Expected{ExitCode: 1}).Combined() - assert.Assert(t, strings.Contains(output, "alpine:3.10.0")) // beginning of the dependency tree - assert.Assert(t, cmp.Regexp("found .* issues", output)) - assert.Assert(t, !strings.Contains(output, "Low severity")) -} - -func TestScanWithSeverityBadValue(t *testing.T) { - if runtime.GOOS != "linux" { - t.Skip("Can't run on this ci platform (windows containers or no engine installed)") - } - _, cleanFunction := createSnykConfFile(t, os.Getenv("E2E_TEST_AUTH_TOKEN")) - defer cleanFunction() - - cmd, configDir, cleanup := dockerCli.createTestCmd() - defer cleanup() - - createScanConfigFile(t, configDir) - - cmd.Command = dockerCli.Command("scan", "--accept-license", "--severity=unsupportedValue", ImageWithVulnerabilities) - icmd.RunCmd(cmd).Assert(t, icmd.Expected{ - ExitCode: 1, - Err: "--severity takes only 'low', 'medium' or 'high' values"}) -} - -func TestScanWithJsonAndGroupIssues(t *testing.T) { - if runtime.GOOS != "linux" { - t.Skip("Can't run on this ci platform (windows containers or no engine installed)") - } - _, cleanFunction := createSnykConfFile(t, os.Getenv("E2E_TEST_AUTH_TOKEN")) - defer cleanFunction() - - cmd, configDir, cleanup := dockerCli.createTestCmd() - defer cleanup() - - createScanConfigFile(t, configDir) - - cmd.Command = dockerCli.Command("scan", "--accept-license", "--json", "--group-issues", ImageWithVulnerabilities) - output := icmd.RunCmd(cmd).Assert(t, icmd.Expected{ExitCode: 1}).Combined() - assert.Assert(t, strings.Contains(output, "vulnerable dependency paths")) // beginning of the dependency tree - assert.Assert(t, strings.Contains(output, `"from": [ - [ - "docker-image|alpine@3.10.0",`)) -} - -func TestScanWithGroupIssues(t *testing.T) { - if runtime.GOOS != "linux" { - t.Skip("Can't run on this ci platform (windows containers or no engine installed)") - } - _, cleanFunction := createSnykConfFile(t, os.Getenv("E2E_TEST_AUTH_TOKEN")) - defer cleanFunction() - - cmd, configDir, cleanup := dockerCli.createTestCmd() - defer cleanup() - - createScanConfigFile(t, configDir) - - cmd.Command = dockerCli.Command("scan", "--accept-license", "--group-issues", ImageBaseImageVulnerabilities) - icmd.RunCmd(cmd).Assert(t, icmd.Expected{ - ExitCode: 1, - Err: "--json flag is mandatory to use --group-issues flag"}) -} - -func TestScanWithContainerizedSnyk(t *testing.T) { - if runtime.GOOS != "linux" { - t.Skip("Can't run on this ci platform (windows containers or no engine installed)") - } - homeDir, cleanFunction := createSnykConfFile(t, os.Getenv("E2E_TEST_AUTH_TOKEN")) - defer cleanFunction() - - cmd, configDir, cleanup := dockerCli.createTestCmd() - defer cleanup() - createScanConfigFileOptinAndPath(t, configDir, true, "") - - testCases := []struct { - name string - image string - exitCode int - contains string - }{ - { - name: "image-without-vulnerabilities", - image: ImageWithoutVulnerabilities, - exitCode: 0, - contains: "no vulnerable paths found", - }, - { - name: "invalid-docker-archive", - image: InvalidImage, - exitCode: 1, - contains: "(HTTP code 500) server error - empty export - not implemented", - }, - { - name: "image-with-vulnerabilities", - image: ImageWithVulnerabilities, - exitCode: 1, - contains: "vulnerability found", - }, - { - name: "invalid-image-name", - image: "scratch", - exitCode: 1, - contains: "manifest unknown", - }, - } - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - cmd.Command = dockerCli.Command("scan", testCase.image) - cmd.Env = append(cmd.Env, fmt.Sprintf("HOME=%s", homeDir.Path())) - output := icmd.RunCmd(cmd).Assert(t, icmd.Expected{ExitCode: testCase.exitCode}).Combined() - assert.Assert(t, strings.Contains(output, testCase.contains), output) - }) - } -} - -func TestScanLocalImageWithContainerizedSnyk(t *testing.T) { - if runtime.GOOS != "linux" { - t.Skip("Can't run on this ci platform (windows containers or no engine installed)") - } - _, cleanFunction := createSnykConfFile(t, os.Getenv("E2E_TEST_AUTH_TOKEN")) - defer cleanFunction() - - cmd, configDir, cleanup := dockerCli.createTestCmd() - defer cleanup() - createScanConfigFileOptinAndPath(t, configDir, true, "") - - // Build a local image - cmd.Command = dockerCli.Command("build", "-f", "./testdata/Dockerfile", "-t", LocalBuildImage, ".") - icmd.RunCmd(cmd).Assert(t, icmd.Success) - - cmd.Command = dockerCli.Command("scan", LocalBuildImage) - output := icmd.RunCmd(cmd).Assert(t, icmd.Expected{ExitCode: 1}).Combined() - assert.Assert(t, strings.Contains(output, "vulnerability found")) -} - -func TestScanWithFileAndExcludeBaseImageVulnsContainerizedProvider(t *testing.T) { - if runtime.GOOS != "linux" { - t.Skip("Can't run on this ci platform (windows containers or no engine installed)") - } - pwd, _ := os.Getwd() - dockerfilePath := path.Join(pwd, "/testdata/Dockerfile") - _, cleanFunction := createSnykConfFile(t, os.Getenv("E2E_TEST_AUTH_TOKEN")) - defer cleanFunction() - - cmd, configDir, cleanup := dockerCli.createTestCmd() - defer cleanup() - createScanConfigFileOptinAndPath(t, configDir, true, "") - - cmd.Command = dockerCli.Command("scan", "--file", dockerfilePath, "--exclude-base", ImageBaseImageVulnerabilities) - output := icmd.RunCmd(cmd).Assert(t, icmd.Success).Combined() - assert.Assert(t, strings.Contains(output, "no vulnerable paths found.")) -} - -func createSnykConfDirectories(t *testing.T, withConfFile bool, token string) (*fs.Dir, func()) { - content := fmt.Sprintf(`{"api" : "%s"}`, token) - var confFiles []fs.PathOp - if withConfFile { - confFiles = append(confFiles, fs.WithFile("snyk.json", content)) - } - homeDir := fs.NewDir(t, t.Name(), - fs.WithDir(".config", - fs.WithDir("configstore", confFiles...))) - - homeFunc := env.Patch(t, "HOME", homeDir.Path()) - userProfileFunc := env.Patch(t, "USERPROFILE", homeDir.Path()) - cleanup := func() { - userProfileFunc() - homeFunc() - homeDir.Remove() - } - - return homeDir, cleanup -} - -func createSnykConfFile(t *testing.T, token string) (*fs.Dir, func()) { - return createSnykConfDirectories(t, true, token) -} - -func patchConfig(t *testing.T, configDir, url, userName, password string) { - buff, err := os.ReadFile(filepath.Join(configDir, "config.json")) - assert.NilError(t, err) - var conf configfile.ConfigFile - assert.NilError(t, json.Unmarshal(buff, &conf)) - - conf.AuthConfigs = map[string]types.AuthConfig{ - url: { - Username: userName, - Password: password, - }, - } - buff, err = json.Marshal(&conf) - assert.NilError(t, err) - - assert.NilError(t, os.WriteFile(filepath.Join(configDir, "config.json"), buff, 0644)) -} - -func createScanConfigFile(t *testing.T, configDir string) { - createScanConfigFileOptin(t, configDir, true) -} - -func createScanConfigFileOptin(t *testing.T, configDir string, optin bool) { - createScanConfigFileOptinAndPath(t, configDir, optin, filepath.Join(configDir, "scan", "snyk")) -} - -func createScanConfigFileOptinAndPath(t *testing.T, configDir string, optin bool, path string) { - conf := config.Config{ - Path: path, - Optin: optin, - } - buf, err := json.MarshalIndent(conf, "", " ") - assert.NilError(t, err) - err = os.WriteFile(filepath.Join(configDir, "scan", "config.json"), buf, 0644) - assert.NilError(t, err) -} diff --git a/e2e/testdata/Dockerfile b/e2e/testdata/Dockerfile deleted file mode 100644 index 33041737..00000000 --- a/e2e/testdata/Dockerfile +++ /dev/null @@ -1 +0,0 @@ -FROM alpine:3.10.0 \ No newline at end of file diff --git a/e2e/testdata/plugin-usage.golden b/e2e/testdata/plugin-usage.golden deleted file mode 100644 index 61a3f02b..00000000 --- a/e2e/testdata/plugin-usage.golden +++ /dev/null @@ -1,24 +0,0 @@ - -Usage: docker scan [OPTIONS] IMAGE - -A tool to scan your images - -Options: - --accept-license Accept using a third party scanning provider - --dependency-tree Show dependency tree with scan results - --exclude-base Exclude base image from vulnerability scanning - (requires --file) - -f, --file string Dockerfile associated with image, provides more - detailed results - --group-issues Aggregate duplicated vulnerabilities and group - them to a single one (requires --json) - --json Output results in JSON format - --login Authenticate to the scan provider using an - optional token (with --token), or web base - token if empty - --reject-license Reject using a third party scanning provider - --severity string Only report vulnerabilities of provided level - or higher (low|medium|high) - --token string Authentication token to login to the third - party scanning provider - --version Display version of the scan plugin diff --git a/e2e/version_test.go b/e2e/version_test.go deleted file mode 100644 index 66c82599..00000000 --- a/e2e/version_test.go +++ /dev/null @@ -1,137 +0,0 @@ -/* - Copyright 2020 Docker Inc. - - 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. -*/ - -package e2e - -import ( - "fmt" - "os" - "runtime" - "strings" - "testing" - - "github.com/docker/scan-cli-plugin/internal/provider" - "gotest.tools/v3/assert" - is "gotest.tools/v3/assert/cmp" - "gotest.tools/v3/env" - "gotest.tools/v3/icmd" - - "github.com/docker/scan-cli-plugin/internal" -) - -func TestVersionSnykUserBinary(t *testing.T) { - // Add user snyk binary to the $PATH - path := os.Getenv("PATH") - defer env.Patch(t, "PATH", fmt.Sprintf(pathFormat(), os.Getenv("SNYK_USER_PATH"), path))() - - cmd, configDir, cleanup := dockerCli.createTestCmd() - defer cleanup() - - createScanConfigFile(t, configDir) - - // docker scan --version should use user's Snyk binary - cmd.Command = dockerCli.Command("scan", "--version") - output := icmd.RunCmd(cmd).Assert(t, icmd.Success).Combined() - expected := fmt.Sprintf( - `Version: %s -Git commit: %s -Provider: %s -`, internal.Version, internal.GitCommit, getProviderVersion("SNYK_USER_VERSION")) - - if !strings.HasPrefix(output, expected) { - t.Fatalf("expected output to start with %q, got %q", expected, output) - } -} - -func TestVersionSnykOldBinary(t *testing.T) { - // Add old snyk binary to the $PATH - path := os.Getenv("PATH") - defer env.Patch(t, "PATH", fmt.Sprintf(pathFormat(), os.Getenv("SNYK_OLD_PATH"), path))() - - cmd, configDir, cleanup := dockerCli.createTestCmd() - defer cleanup() - - createScanConfigFile(t, configDir) - - // docker scan --version should fallback to desktop's Snyk binary and print a message on - // stderr stating that the user should upgrade Snyk. - cmd.Command = dockerCli.Command("scan", "--version") - output := icmd.RunCmd(cmd).Assert(t, icmd.Success).Combined() - expected := fmt.Sprintf( - `the Snyk version %s installed on your system is older as the one embedded by Docker Desktop (>=%s), using embedded Snyk version instead - -Version: %s -Git commit: %s -Provider: %s -`, os.Getenv("SNYK_OLD_VERSION"), provider.SnykDesktopVersion, - internal.Version, internal.GitCommit, getProviderVersion("SNYK_DESKTOP_VERSION")) - - if !strings.HasPrefix(output, expected) { - t.Fatalf("expected output to start with %q, got %q", expected, output) - } -} - -func TestVersionSnykDesktopBinary(t *testing.T) { - cmd, configDir, cleanup := dockerCli.createTestCmd() - defer cleanup() - - createScanConfigFile(t, configDir) - - // docker scan --version should print docker scan plugin version and snyk version - cmd.Command = dockerCli.Command("scan", "--version") - output := icmd.RunCmd(cmd).Assert(t, icmd.Success).Combined() - expected := fmt.Sprintf( - `Version: %s -Git commit: %s -Provider: %s -`, internal.Version, internal.GitCommit, getProviderVersion("SNYK_DESKTOP_VERSION")) - - if !strings.HasPrefix(output, expected) { - t.Fatalf("expected output to start with %q, got %q", expected, output) - } -} - -func TestVersionWithoutSnykOrConfig(t *testing.T) { - cmd, _, cleanup := dockerCli.createTestCmd() - defer cleanup() - - // docker scan --version should fail with a clean error - cmd.Command = dockerCli.Command("scan", "--version") - res := icmd.RunCmd(cmd) - if runtime.GOOS != "darwin" && runtime.GOOS != "windows" { // config file created by default without Docker Desktop - expected := fmt.Sprintf(`Version: %s -Git commit: %s -Provider: %s -`, internal.Version, internal.GitCommit, getProviderVersion("SNYK_DESKTOP_VERSION")) - res.Assert(t, icmd.Expected{ - ExitCode: 0, - Out: expected, - }) - } else { - output := res.Assert(t, icmd.Expected{ - ExitCode: 1, - }).Combined() - expected := "failed to read docker scan configuration file. Please restart Docker Desktop" - assert.Assert(t, is.Contains(output, expected)) - } -} - -func getProviderVersion(env string) string { - if runtime.GOOS == "linux" { - return fmt.Sprintf("Snyk (%s (standalone))", os.Getenv(env)) - } - return fmt.Sprintf("Snyk (%s)", os.Getenv(env)) -} diff --git a/go.mod b/go.mod index ac7687bc..57b277d6 100644 --- a/go.mod +++ b/go.mod @@ -3,19 +3,11 @@ module github.com/docker/scan-cli-plugin go 1.19 require ( - github.com/Masterminds/semver/v3 v3.1.1 github.com/charmbracelet/glamour v0.6.0 github.com/docker/cli v20.10.17+incompatible - github.com/docker/docker v20.10.17+incompatible github.com/fatih/color v1.7.0 - github.com/google/uuid v1.3.0 - github.com/mitchellh/go-homedir v1.1.0 - github.com/mitchellh/go-ps v1.0.0 - github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 - github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.5.0 golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 - gopkg.in/square/go-jose.v2 v2.6.0 gotest.tools/v3 v3.0.3 ) @@ -28,10 +20,10 @@ require ( github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect - github.com/containerd/cgroups v1.0.4 // indirect - github.com/containerd/containerd v1.6.6 // indirect + github.com/containerd/continuity v0.2.2 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect github.com/docker/distribution v2.8.1+incompatible // indirect + github.com/docker/docker v20.10.17+incompatible // indirect github.com/docker/docker-credential-helpers v0.6.4 // indirect github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect github.com/docker/go-connections v0.4.0 // indirect @@ -39,7 +31,6 @@ require ( github.com/docker/go-units v0.4.0 // indirect github.com/fvbommel/sortorder v1.0.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.8 // indirect github.com/gorilla/css v1.0.0 // indirect @@ -60,7 +51,9 @@ require ( github.com/muesli/termenv v0.13.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/runc v1.1.3 // indirect + github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect + github.com/pelletier/go-toml v1.9.3 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.12.2 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.37.0 // indirect @@ -71,9 +64,10 @@ require ( github.com/theupdateframework/notary v0.7.0 // indirect github.com/yuin/goldmark v1.5.2 // indirect github.com/yuin/goldmark-emoji v1.0.1 // indirect - go.opencensus.io v0.23.0 // indirect golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect golang.org/x/net v0.0.0-20221002022538-bcab6841153b // indirect golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect + golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/go.sum b/go.sum index 1723713e..a299fa77 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,5 @@ bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -48,8 +49,6 @@ github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= @@ -58,6 +57,7 @@ github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugX github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= @@ -129,7 +129,6 @@ github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM2 github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= -github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -138,7 +137,6 @@ github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLI github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= -github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e85keuznYcH5rqI438v41pKcBl4ZxQ= github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= @@ -160,14 +158,11 @@ github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4S github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= -github.com/containerd/cgroups v1.0.4 h1:jN/mbWBEaz+T1pi5OFtnkQ+8qnmEbAr1Oo1FRm5B0dA= -github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= -github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= @@ -182,8 +177,6 @@ github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09Zvgq github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= -github.com/containerd/containerd v1.6.6 h1:xJNPhbrmz8xAMDNoVjHy9YHtWwEQNS+CDkcIRh7t8Y0= -github.com/containerd/containerd v1.6.6/go.mod h1:ZoP1geJldzCVY3Tonoz7b1IXk8rIX0Nltt5QE4OMNk0= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= @@ -192,6 +185,7 @@ github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= github.com/containerd/continuity v0.2.2 h1:QSqfxcn8c+12slxwu00AtzXrsami0MJb/MQs9lOLHLA= +github.com/containerd/continuity v0.2.2/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk= github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= @@ -259,7 +253,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= -github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= @@ -366,7 +359,6 @@ github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblf github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -384,8 +376,6 @@ github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -423,7 +413,6 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -446,8 +435,6 @@ github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= @@ -564,10 +551,7 @@ github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WT github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= -github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/mapstructure v0.0.0-20150613213606-2caf8efc9366/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -577,7 +561,6 @@ github.com/moby/sys/mount v0.3.3 h1:fX1SVkXFJ47XWDoeFW4Sq7PdQJnV2QIDZAqjNqgEjUs= github.com/moby/sys/mount v0.3.3/go.mod h1:PBaEorSNTLG5t/+4EgukEQVlAvVEc6ZjTySwKdqp5K0= github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= -github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= @@ -640,8 +623,6 @@ github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= -github.com/opencontainers/runc v1.1.3 h1:vIXrkId+0/J2Ymu2m7VjGvbSlAId9XNRPhn2p4b+d8w= -github.com/opencontainers/runc v1.1.3/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= @@ -652,12 +633,12 @@ github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mo github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= -github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -721,7 +702,6 @@ github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiB github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= -github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= @@ -783,6 +763,7 @@ github.com/theupdateframework/notary v0.7.0 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4D github.com/theupdateframework/notary v0.7.0/go.mod h1:c9DRxcmhHmVLDay4/2fUYdISnHqbFDGRSlXPO0AhYWw= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= @@ -824,8 +805,6 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -1022,9 +1001,6 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1053,6 +1029,7 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1225,8 +1202,6 @@ gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1/go.mod h1:WbjuEoo1oadwzQ4apSDU+JTvmllE gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= -gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1240,6 +1215,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= diff --git a/internal/authentication/authenticator.go b/internal/authentication/authenticator.go deleted file mode 100644 index 7f2c1569..00000000 --- a/internal/authentication/authenticator.go +++ /dev/null @@ -1,165 +0,0 @@ -/* - Copyright 2020 Docker Inc. - - 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. -*/ - -package authentication - -import ( - "crypto" - "encoding/json" - "errors" - "fmt" - "os" - "path/filepath" - "time" - - cliConfig "github.com/docker/cli/cli/config" - "github.com/docker/docker/api/types" - "github.com/docker/scan-cli-plugin/internal/hub" - "gopkg.in/square/go-jose.v2" - "gopkg.in/square/go-jose.v2/jwt" -) - -const ( - expirationWindow = 1 * time.Minute -) - -// Authenticator logs on docker Hub and retrieves a DockerScanID -// if the one stored locally has expired -type Authenticator struct { - hub hub.Client - tokensPath string - jwks jose.JSONWebKeySet -} - -// NewAuthenticator returns an Authenticator -// configured to run against Docker Hub prod or staging -func NewAuthenticator(jwks jose.JSONWebKeySet, apiHubBaseURL string) *Authenticator { - return &Authenticator{ - hub: hub.Client{Domain: apiHubBaseURL}, - tokensPath: filepath.Join(cliConfig.Dir(), "scan", "tokens.json"), - jwks: jwks, - } -} - -// GetToken checks the local DockerScanID content for expiry, -// if expired it negotiates a new one on Docker Hub. -func (a *Authenticator) GetToken(hubAuthConfig types.AuthConfig) (string, error) { - // Retrieve token from local storage - token := a.getLocalToken(hubAuthConfig) - - // Check if the token is well formed and still valid - if err := a.checkTokenValidity(token); err == nil { - return token, nil - } - // Fetch a new token from Hub - var err error - token, err = a.negotiateScanIDToken(hubAuthConfig) - if err != nil { - return "", err - } - // Persist token on local storage - if err := a.updateLocalToken(hubAuthConfig, token); err != nil { - return "", err - } - return token, nil -} - -func (a *Authenticator) getLocalToken(hubAuthConfig types.AuthConfig) string { - buf, err := os.ReadFile(a.tokensPath) - if errors.Is(err, os.ErrNotExist) { - return "" - } - tokens := map[string]string{} - if err := json.Unmarshal(buf, &tokens); err != nil { - return "" - } - return tokens[hubAuthConfig.Username] -} - -func (a *Authenticator) checkTokenValidity(token string) error { - if token == "" { - return fmt.Errorf("empty token") - } - - parsedToken, err := jwt.ParseSigned(token) - if err != nil { - return fmt.Errorf("invalid token: %s", err) - } - publicKey, err := a.findKey(parsedToken) - if err != nil { - return err - } - out := jwt.Claims{} - if err := parsedToken.Claims(publicKey, &out); err != nil { - return fmt.Errorf("invalid token: signature does not match the content: %s", err) - } - if err := out.ValidateWithLeeway(jwt.Expected{Time: time.Now().Add(expirationWindow)}, 0); err != nil { - return fmt.Errorf("token has expired: %s", err) - } - return nil -} - -func (a *Authenticator) findKey(token *jwt.JSONWebToken) (crypto.PublicKey, error) { - var kid string - for _, header := range token.Headers { - if header.KeyID != "" { - kid = header.KeyID - break - } - } - if kid == "" { - return nil, fmt.Errorf("invalid token: key identifier does not match") - } - for _, key := range a.jwks.Keys { - if key.KeyID == kid { - return key.Public(), nil - } - } - return nil, fmt.Errorf("invalid token: key identifier does not match") -} - -func (a *Authenticator) negotiateScanIDToken(hubAuthConfig types.AuthConfig) (string, error) { - hubToken, err := a.hub.Login(hubAuthConfig) - if err != nil { - return "", err - } - return a.hub.GetScanID(hubToken) -} - -func (a *Authenticator) updateLocalToken(hubAuthConfig types.AuthConfig, token string) error { - stats, err := os.Stat(a.tokensPath) - mode := os.FileMode(0644) - if err != nil { - if !errors.Is(err, os.ErrNotExist) { - return err - } - } else { - mode = stats.Mode() - } - - buf, err := os.ReadFile(a.tokensPath) - if err != nil && !errors.Is(err, os.ErrNotExist) { - return err - } - tokens := map[string]string{} - _ = json.Unmarshal(buf, &tokens) // if an error occurs (invalid content), we just erase the content with a new map - tokens[hubAuthConfig.Username] = token - buf, err = json.Marshal(tokens) - if err != nil { - return err - } - return os.WriteFile(a.tokensPath, buf, mode) -} diff --git a/internal/authentication/authenticator_test.go b/internal/authentication/authenticator_test.go deleted file mode 100644 index c353f638..00000000 --- a/internal/authentication/authenticator_test.go +++ /dev/null @@ -1,271 +0,0 @@ -/* - Copyright 2020 Docker Inc. - - 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. -*/ - -package authentication - -import ( - "crypto" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "encoding/json" - "fmt" - "io" - "net/http" - "net/http/httptest" - "os" - "testing" - "time" - - "github.com/docker/docker/api/types" - "github.com/docker/scan-cli-plugin/internal/hub" - "gopkg.in/square/go-jose.v2" - "gopkg.in/square/go-jose.v2/jwt" - "gotest.tools/v3/assert" - "gotest.tools/v3/fs" -) - -func TestHubAuthenticateNegociatesToken(t *testing.T) { - authConfig := types.AuthConfig{} - - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch r.URL.String() { - case hub.LoginURL: - assert.Equal(t, r.Method, http.MethodPost) - buf, err := io.ReadAll(r.Body) - assert.NilError(t, err) - var actualAuthConfig types.AuthConfig - assert.NilError(t, json.Unmarshal(buf, &actualAuthConfig)) - assert.DeepEqual(t, actualAuthConfig, authConfig) - fmt.Fprint(w, `{"token":"hub-content"}`) - - case hub.ScanTokenURL: - assert.Equal(t, r.Method, http.MethodGet) - assert.Equal(t, r.Header.Get("Authorization"), "Bearer hub-content") - fmt.Fprint(w, `XXXX.YYYY.ZZZZ`) - - default: - t.FailNow() - } - })) - defer ts.Close() - - authenticator := NewAuthenticator(jose.JSONWebKeySet{}, ts.URL) - token, err := authenticator.negotiateScanIDToken(authConfig) - assert.NilError(t, err) - assert.Equal(t, token, "XXXX.YYYY.ZZZZ") -} - -func TestHubAuthenticateChecksTokenValidity(t *testing.T) { - testCases := []struct { - name string - content string - expected string - }{ - { - name: "missing file", - content: "", - expected: "", - }, - { - name: "invalid content", - content: "invalid content", - expected: "", - }, - { - name: "valid content with unknown user", - content: `{"hubUser1": "ZZZZ.YYYY.XXXX"}`, - expected: "", - }, - { - name: "valid content with hub user", - content: `{ - "hubUser1": "ZZZZ.YYYY.XXXX", - "hubUser2": "XXXX.YYYY.ZZZZ" -}`, - expected: "XXXX.YYYY.ZZZZ", - }, - } - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - var dir *fs.Dir - if testCase.content != "" { - dir = fs.NewDir(t, testCase.name, fs.WithFile("tokens.json", testCase.content)) - } else { - dir = fs.NewDir(t, testCase.name) - } - defer dir.Remove() - - authenticator := NewAuthenticator(jose.JSONWebKeySet{}, "") - authenticator.tokensPath = dir.Join("tokens.json") - - authConfig := types.AuthConfig{Username: "hubUser2"} - - token := authenticator.getLocalToken(authConfig) - assert.Equal(t, token, testCase.expected) - }) - } -} - -func TestUpdateLocalToken(t *testing.T) { - testCases := []struct { - name string - content string - expected string - }{ - { - name: "no file", - content: "", - expected: `{"hubUser1":"ZZZZ.YYYY.XXXX"}`, - }, - { - name: "invalid content", - content: "invalid content", - expected: `{"hubUser1":"ZZZZ.YYYY.XXXX"}`, - }, - { - name: "update content with new user", - content: `{"hubUser2":"XXXX.YYYY.ZZZZ"}`, - expected: `{"hubUser1":"ZZZZ.YYYY.XXXX","hubUser2":"XXXX.YYYY.ZZZZ"}`, - }, - } - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - var dir *fs.Dir - if testCase.content != "" { - dir = fs.NewDir(t, testCase.name, fs.WithFile("tokens.json", testCase.content)) - } else { - dir = fs.NewDir(t, testCase.name) - } - defer dir.Remove() - - authenticator := NewAuthenticator(jose.JSONWebKeySet{}, "") - authenticator.tokensPath = dir.Join("tokens.json") - - authConfig := types.AuthConfig{Username: "hubUser1"} - - err := authenticator.updateLocalToken(authConfig, "ZZZZ.YYYY.XXXX") - assert.NilError(t, err) - actual, err := os.ReadFile(dir.Join("tokens.json")) - assert.NilError(t, err) - assert.Equal(t, string(actual), testCase.expected) - }) - } -} - -func TestCheckTokenValidity(t *testing.T) { - // Generate JWKS file containing the public key - privateKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - jwks := jose.JSONWebKeySet{ - Keys: []jose.JSONWebKey{ - { - Key: privateKey.Public(), - KeyID: "key-id", - Algorithm: string(jose.ES256), - Use: "sig", - }, - }, - } - - // Generate JWT token - sig := newSigner(t, privateKey, "key-id") - now := time.Now() - - testCases := []struct { - name string - expectedError string - generateToken func() string - }{ - { - name: "empty token", - generateToken: func() string { return "" }, - expectedError: "empty token", - }, - { - name: "malformed token", - generateToken: func() string { return "malformed token" }, - expectedError: `invalid token`, - }, - { - name: "signature don't match", - generateToken: func() string { - otherPrivateKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - sig := newSigner(t, otherPrivateKey, "key-id") - return generateToken(t, sig, now) - }, - expectedError: "invalid token: signature does not match the content", - }, - { - name: "unknown key", - expectedError: "invalid token: key identifier does not match", - generateToken: func() string { - sig := newSigner(t, privateKey, "unknown-key-id") - return generateToken(t, sig, now) - }, - }, - { - name: "expired token", - expectedError: "token has expired", - generateToken: func() string { - return generateToken(t, sig, time.Unix(0, 0)) - }, - }, - { - name: "expired token in the last minute window", - expectedError: "token has expired", - generateToken: func() string { - return generateToken(t, sig, now.Add(-(59*time.Minute + 30*time.Second))) - }, - }, - { - name: "valid token", - generateToken: func() string { - return generateToken(t, sig, now) - }, - expectedError: "", - }, - } - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - authenticator := NewAuthenticator(jwks, "") - err := authenticator.checkTokenValidity(testCase.generateToken()) - if testCase.expectedError == "" { - assert.NilError(t, err) - } else { - assert.ErrorContains(t, err, testCase.expectedError) - } - }) - } -} - -func newSigner(t *testing.T, key crypto.PrivateKey, kid string) jose.Signer { - t.Helper() - sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: key}, (&jose.SignerOptions{}).WithType("JWT"). - WithHeader("kid", kid)) - assert.NilError(t, err) - return sig -} - -func generateToken(t *testing.T, sig jose.Signer, issueDate time.Time) string { - t.Helper() - cl := jwt.Claims{ - IssuedAt: jwt.NewNumericDate(issueDate), - Expiry: jwt.NewNumericDate(issueDate.Add(1 * time.Hour)), - } - raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() - assert.NilError(t, err) - return raw -} diff --git a/internal/deprecation.go b/internal/deprecation.go index ec41cc0b..caccbed9 100644 --- a/internal/deprecation.go +++ b/internal/deprecation.go @@ -25,18 +25,20 @@ import ( "golang.org/x/term" ) -const ( - message = "> The `docker scan` **command is deprecated** and will no longer be supported after April 13th, 2023. \n" + - "> Run the `docker scout cves` command to continue to get vulnerabilities on your images or install the Snyk CLI. \n" + - "> See https://www.docker.com/products/docker-scout for more details." +var ( + eolMessage = fmt.Sprintf(` +The %s command has been removed. + +To continue learning about the vulnerabilities of your images, and many other features, use the new %s command. +Run %s, or learn more at %s +`, "`docker scan`", "`docker scout`", "`docker scout --help`", "https://docs.docker.com/engine/reference/commandline/scout/") ) -// PrintDeprecationMessage displays on stderr the deprecation notice. -func PrintDeprecationMessage(cli command.Cli) { +func PrintEOLMessage(cli command.Cli) { r := getTermRenderer() - str, err := r.Render(message) + str, err := r.Render(eolMessage) if err != nil { - _, _ = fmt.Fprintln(cli.Err(), message) + _, _ = fmt.Fprintln(cli.Err(), eolMessage) } else { _, _ = fmt.Fprintln(cli.Err(), str) } diff --git a/internal/hub/hub.go b/internal/hub/hub.go deleted file mode 100644 index e9e73751..00000000 --- a/internal/hub/hub.go +++ /dev/null @@ -1,100 +0,0 @@ -/* - Copyright 2020 Docker Inc. - - 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. -*/ - -package hub - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" - - "github.com/docker/docker/api/types" -) - -const ( - // LoginURL path to the Hub login URL - LoginURL = "/v2/users/login" - // ScanTokenURL path to the Hub provider token generation URL - ScanTokenURL = "/api/scan/v1/provider/token" -) - -// Client sends authenticates on Hub and sends requests to the API -type Client struct { - Domain string -} - -// Login logs into Hub and returns the auth token -func (h *Client) Login(hubAuthConfig types.AuthConfig) (string, error) { - data, err := json.Marshal(hubAuthConfig) - if err != nil { - return "", err - } - body := bytes.NewBuffer(data) - - // Login on the Docker Hub - req, err := http.NewRequest("POST", h.Domain+LoginURL, body) - if err != nil { - return "", err - } - req.Header["Content-Type"] = []string{"application/json"} - buf, err := doRequest(req) - if err != nil { - return "", err - } - - creds := struct { - Token string `json:"token"` - }{} - if err := json.Unmarshal(buf, &creds); err != nil { - return "", err - } - return creds.Token, nil -} - -// GetScanID calls the scan service which returns a DockerScanID as a JWT token -func (h *Client) GetScanID(hubToken string) (string, error) { - req, err := http.NewRequest("GET", h.Domain+ScanTokenURL, nil) - if err != nil { - return "", err - } - req.Header["Authorization"] = []string{fmt.Sprintf("Bearer %s", hubToken)} - token, err := doRequest(req) - if err != nil { - return "", err - } - return string(token), nil -} - -func doRequest(req *http.Request) ([]byte, error) { - req.Header["Accept"] = []string{"application/json"} - resp, err := http.DefaultClient.Do(req) - if err != nil { - return nil, err - } - if resp.Body != nil { - defer resp.Body.Close() //nolint:errcheck - } - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("bad status code %q", resp.Status) - } - buf, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - return buf, nil -} diff --git a/internal/hub/instances.go b/internal/hub/instances.go deleted file mode 100644 index 57bcff9b..00000000 --- a/internal/hub/instances.go +++ /dev/null @@ -1,100 +0,0 @@ -/* - Copyright 2020 Docker Inc. - - 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. -*/ - -package hub - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "os" - - "github.com/docker/docker/api/types/registry" - "gopkg.in/square/go-jose.v2" -) - -// Instance stores all the specific pieces needed to dialog with Hub -type Instance struct { - APIHubBaseURL string - JwksURL string - RegistryInfo *registry.IndexInfo -} - -// GetInstance returns the current hub instance, which can be overridden by -// DOCKER_SCAN_HUB_INSTANCE env var -func GetInstance() *Instance { - override := os.Getenv("DOCKER_SCAN_HUB_INSTANCE") - switch override { - case "staging": - return &staging - case "prod": - return &prod - default: - return &prod - } -} - -// FetchJwks fetches a jwks.json file and parses it -func (i *Instance) FetchJwks() (jose.JSONWebKeySet, error) { - // fetch jwks.json file from URL - resp, err := http.Get(i.JwksURL) - if err != nil { - return jose.JSONWebKeySet{}, fmt.Errorf("failed to fetch JWKS: %s", err) - } - if resp.StatusCode < http.StatusOK && resp.StatusCode >= 300 { - return jose.JSONWebKeySet{}, fmt.Errorf("failed to fetch JWKS: invalid status code %v", resp.StatusCode) - } - if resp.Body == nil { - return jose.JSONWebKeySet{}, fmt.Errorf("failed to fetch JWKS: invalid jwks.json file") - } - defer resp.Body.Close() //nolint: errcheck - - // Read and parse jwks.json file - buf, err := io.ReadAll(resp.Body) - if err != nil { - return jose.JSONWebKeySet{}, fmt.Errorf("failed to read JWKS: %s", err) - } - var keySet jose.JSONWebKeySet - if err := json.Unmarshal(buf, &keySet); err != nil { - return jose.JSONWebKeySet{}, fmt.Errorf("invalid JWKS: %s", err) - } - return keySet, nil -} - -var ( - staging = Instance{ - APIHubBaseURL: "https://hub-stage.docker.com", - RegistryInfo: ®istry.IndexInfo{ - Name: "index-stage.docker.io", - Mirrors: nil, - Secure: true, - Official: false, - }, - JwksURL: "https://jwt-stage.docker.com/scan/.well-known/jwks.json", - } - - prod = Instance{ - APIHubBaseURL: "https://hub.docker.com", - RegistryInfo: ®istry.IndexInfo{ - Name: "index.docker.io", - Mirrors: nil, - Secure: true, - Official: true, - }, - JwksURL: "https://jwt.docker.com/scan/.well-known/jwks.json", - } -) diff --git a/internal/hub/instances_test.go b/internal/hub/instances_test.go deleted file mode 100644 index 7d09bc48..00000000 --- a/internal/hub/instances_test.go +++ /dev/null @@ -1,30 +0,0 @@ -/* - Copyright 2020 Docker Inc. - - 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. -*/ - -package hub - -import ( - "testing" - - "gotest.tools/v3/assert" -) - -func TestInstance_FetchJwks(t *testing.T) { - instance := GetInstance() - got, err := instance.FetchJwks() - assert.NilError(t, err) - assert.Assert(t, len(got.Keys) >= 1) -} diff --git a/internal/optin/optin.go b/internal/optin/optin.go deleted file mode 100644 index b75eef76..00000000 --- a/internal/optin/optin.go +++ /dev/null @@ -1,40 +0,0 @@ -/* - Copyright 2020 Docker Inc. - - 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. -*/ - -package optin - -import ( - "bufio" - "fmt" - "io" - "strings" -) - -// AskForConsent prompts a consent question to inform about Snyk usage on behalf -func AskForConsent(stdin io.Reader, stdout io.Writer) bool { - fmt.Fprintln(stdout, "Docker Scan relies upon access to Snyk, a third party provider, do you consent to proceed using Snyk? (y/N)") - reader := bufio.NewReader(stdin) - input, _ := reader.ReadString('\n') - input = strings.ToLower(strings.TrimSpace(input)) - switch input { - case "", "n", "no": - return false - case "y", "yes": - return true - default: // anything else reject the consent - return false - } -} diff --git a/internal/optin/optin_test.go b/internal/optin/optin_test.go deleted file mode 100644 index 2551230c..00000000 --- a/internal/optin/optin_test.go +++ /dev/null @@ -1,79 +0,0 @@ -/* - Copyright 2020 Docker Inc. - - 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. -*/ - -package optin - -import ( - "bytes" - "fmt" - "testing" - - "gotest.tools/v3/assert" -) - -func TestAskForConsent(t *testing.T) { - testCases := []struct { - name string - input string - consent bool - }{ - { - name: "empty input rejects consent", - input: "", - consent: false, - }, - { - name: "invalid input rejects consent", - input: "invalid", - consent: false, - }, - { - name: "Upper case YES accepts consent", - input: "YES", - consent: true, - }, - { - name: "Lower case yes accepts consent", - input: "yes", - consent: true, - }, - { - name: "just y accepts consent", - input: "y", - consent: true, - }, - { - name: "spaces are ignored", - input: " yes ", - consent: true, - }, - { - name: "n rejects consent", - input: "n", - consent: false, - }, - } - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - stdin, stdout := bytes.NewBuffer(nil), bytes.NewBuffer(nil) - fmt.Fprint(stdin, testCase.input) - consent := AskForConsent(stdin, stdout) - assert.Equal(t, consent, testCase.consent) - assert.Equal(t, stdout.String(), `Docker Scan relies upon access to Snyk, a third party provider, do you consent to proceed using Snyk? (y/N) -`) - }) - } -} diff --git a/internal/provider/containerizedsnyk.go b/internal/provider/containerizedsnyk.go deleted file mode 100644 index 4ec155b5..00000000 --- a/internal/provider/containerizedsnyk.go +++ /dev/null @@ -1,338 +0,0 @@ -/* - Copyright 2020 Docker Inc. - - 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. -*/ - -package provider - -import ( - "bytes" - "encoding/json" - "fmt" - "os" - "path/filepath" - "strings" - - v1 "github.com/opencontainers/image-spec/specs-go/v1" - - "github.com/docker/docker/pkg/jsonmessage" - - "github.com/docker/cli/cli/command" - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/strslice" - "github.com/docker/docker/pkg/archive" - "github.com/docker/docker/pkg/stdcopy" - - "github.com/google/uuid" - "github.com/mitchellh/go-homedir" -) - -// ImageDigest is the sha snyk/snyk:alpine image, set at build time -var ( - ImageDigest = "unknown" - image = fmt.Sprintf("snyk/snyk@%s", ImageDigest) -) - -type dockerSnykProvider struct { - cli command.Cli - Options -} - -type dockerEnvs []string -type dockerBindings []string - -type removeContainerFunc func() error - -func (d *dockerSnykProvider) removeContainer(containerID string) removeContainerFunc { - return func() error { - return d.cli.Client().ContainerRemove(d.context, containerID, types.ContainerRemoveOptions{}) - } -} - -// NewDockerSnykProvider returns a containerized Snyk implementation of scan provider -func NewDockerSnykProvider(cli command.Cli, defaultProvider Options) (Provider, error) { - provider := dockerSnykProvider{ - cli: cli, - Options: defaultProvider, - } - options := types.ImagePullOptions{} - _, _, err := cli.Client().ImageInspectWithRaw(provider.context, image) - if err != nil { - responseBody, err := cli.Client().ImagePull(provider.context, image, options) - if err != nil { - return nil, err - } - //nolint: errcheck - defer responseBody.Close() - return &provider, jsonmessage.DisplayJSONMessagesStream(responseBody, bytes.NewBuffer(nil), cli.Out().FD(), false, nil) - } - return &provider, nil -} - -// DockerSnykProviderOps function taking a pointer to a containerized Snyk Provider and returning an error if needed -type DockerSnykProviderOps func(provider *dockerSnykProvider) error - -func (d *dockerSnykProvider) Authenticate(token string) error { - if token != "" { - if _, err := uuid.Parse(token); err != nil { - return &invalidTokenError{token} - } - } - home, err := homedir.Dir() - if err != nil { - return err - } - containerName := fmt.Sprintf("synk-auth-%s", uuid.New().String()) - - containerID, removeContainer, err := d.createContainer(token, containerName) - if err != nil { - return err - } - //nolint: errcheck - defer removeContainer() - - err = d.copySnykConfigToContainer(containerID, home) - if err != nil { - return err - } - - streamFunc, err := d.startContainer(containerID) - if err != nil { - return err - } - err = d.checkContainerState(containerID) - if err != nil { - return err - } - streamFunc() - return d.copySnykConfigToHost(containerName, home) -} - -func (d *dockerSnykProvider) checkContainerState(containerID string) error { - statusc, errc := d.cli.Client().ContainerWait(d.context, containerID, container.WaitConditionNotRunning) - select { - case err := <-errc: - if err != nil { - return err - } - case s := <-statusc: - switch s.StatusCode { - case 0: - default: - return containerizedError{} - } - } - return nil -} - -func (d *dockerSnykProvider) createContainer(token string, containerName string) (string, removeContainerFunc, error) { - - envVars := dockerEnvs{ - "NO_UPDATE_NOTIFIER=true", - "SNYK_CFG_DISABLESUGGESTIONS=true", - "SNYK_INTEGRATION_NAME=DOCKER_DESKTOP", - "SNYK_INTEGRATION_VERSION=" + d.version, - "SNYK_UTM_MEDIUM=Partner", - "SNYK_UTM_SOURCE=Docker", - "SNYK_UTM_CAMPAIGN=Docker-Desktop-2020", - } - bindings := dockerBindings{ - "/var/run/docker.sock:/var/run/docker.sock", - "TMP:/root/.config/configstore", - } - - config, hostConfig := containerConfigs(envVars, bindings, strslice.StrSlice{"snyk", "auth", token}) - - result, err := d.cli.Client().ContainerCreate(d.context, &config, &hostConfig, nil, &v1.Platform{Architecture: "amd64", OS: "linux"}, containerName) - if err != nil { - return "", nil, fmt.Errorf("cannot create container: %s", err) - } - removeContainerFunc := d.removeContainer(result.ID) - return result.ID, removeContainerFunc, nil -} - -func (d *dockerSnykProvider) copySnykConfigToContainer(containerID string, home string) error { - configFile := fmt.Sprintf("%s/.config/configstore/snyk.json", home) - if _, err := os.Stat(configFile); err == nil { - options := types.CopyToContainerOptions{ - AllowOverwriteDirWithFile: false, - CopyUIDGID: true, - } - content, err := archive.Tar(configFile, archive.Gzip) - if err != nil { - return err - } - return d.cli.Client().CopyToContainer(d.context, containerID, "/root/.config/configstore/", content, options) - } - return nil - -} - -func (d *dockerSnykProvider) startContainer(containerID string) (func(), error) { - resp, err := d.cli.Client().ContainerAttach(d.context, containerID, types.ContainerAttachOptions{ - Stream: true, - Stdout: true, - Stderr: true, - }) - if err != nil { - return nil, err - } - go func() { - for { - _, err = stdcopy.StdCopy(d.out, d.err, resp.Reader) - } - }() - - return resp.Close, d.cli.Client().ContainerStart(d.context, containerID, types.ContainerStartOptions{}) -} - -func (d *dockerSnykProvider) copySnykConfigToHost(containerID string, home string) error { - reader, _, err := d.cli.Client().CopyFromContainer(d.context, containerID, "/root/.config/configstore/snyk.json") - if err != nil { - return err - } - configstoreFolder := filepath.Join(home, ".config", "configstore") - err = os.MkdirAll(configstoreFolder, 0744) - if err != nil { - return err - } - - // need NoLChown option to let tests pass when run as root, see https://github.com/habitat-sh/builder/issues/365#issuecomment-382862233 - return archive.Untar(reader, configstoreFolder, &archive.TarOptions{NoLchown: true}) -} - -func (d *dockerSnykProvider) Scan(image string) error { - var token string - snykAuthToken, err := getSnykAuthenticationToken() - if snykAuthToken == "" || err != nil { - var err error - token, err = getToken(d.Options) - if err != nil { - return fmt.Errorf("failed to get DockerScanID: %s", err) - } - token = fmt.Sprintf("SNYK_DOCKER_TOKEN=%s", token) - } else { - token = fmt.Sprintf("SNYK_TOKEN=%s", snykAuthToken) - } - // check snyk token - containerID, removeContainer, err := d.newCommand([]string{token}, append(d.flags, image)...) - if err != nil { - return err - } - //nolint: errcheck - defer removeContainer() - streamFunc, err := d.startContainer(containerID) - if err != nil { - return err - } - defer streamFunc() - - return d.checkContainerState(containerID) -} - -func (d *dockerSnykProvider) Version() (string, error) { - containerID, removeContainer, err := d.newCommand([]string{}, "--version") - if err != nil { - return "", err - } - //nolint: errcheck - defer removeContainer() - buff := bytes.NewBuffer(nil) - buffErr := bytes.NewBuffer(nil) - d.out = buff - d.err = buffErr - streamFunc, err := d.startContainer(containerID) - if err != nil { - return "", err - } - defer streamFunc() - - err = d.checkContainerState(containerID) - if err != nil { - return "", err - } - return fmt.Sprintf("Snyk (%s)", strings.TrimSpace(buff.String())), nil -} - -func (d *dockerSnykProvider) newCommand(envVars []string, arg ...string) (string, removeContainerFunc, error) { - bindings := dockerBindings{ - "/var/run/docker.sock:/var/run/docker.sock", - } - for index, argument := range arg { - if strings.HasPrefix(argument, "--file") { - argSplit := strings.Split(argument, "=") - filePath, err := filepath.Abs(argSplit[1]) - if err != nil { - return "", nil, err - } - - bindings = append(bindings, fmt.Sprintf(`%s:/app/Dockerfile`, filePath)) - arg[index] = "--file=/app/Dockerfile" - } - } - defaultEnvs := []string{ - "NO_UPDATE_NOTIFIER=true", - "SNYK_CFG_DISABLESUGGESTIONS=true", - "SNYK_INTEGRATION_NAME=DOCKER_DESKTOP", - "SNYK_INTEGRATION_VERSION=" + d.version, - } - envVars = append(envVars, defaultEnvs...) - - args := strslice.StrSlice{"snyk"} - args = append(args, arg...) - config, hostConfig := containerConfigs(envVars, bindings, args) - - result, err := d.cli.Client().ContainerCreate(d.context, &config, &hostConfig, nil, nil, "") - if err != nil { - return "", nil, fmt.Errorf("cannot create container: %s", err) - } - removeContainer := d.removeContainer(result.ID) - return result.ID, removeContainer, nil -} - -func containerConfigs(envVars dockerEnvs, bindings dockerBindings, entrypoint strslice.StrSlice) (container.Config, container.HostConfig) { - config := container.Config{ - Image: image, - Env: envVars, - Entrypoint: entrypoint, - AttachStderr: true, - AttachStdout: true, - } - - hostConfig := container.HostConfig{ - Binds: bindings, - } - return config, hostConfig -} - -func getSnykAuthenticationToken() (string, error) { - home, err := homedir.Dir() - if err != nil { - return "", err - } - snykConfFilePath := filepath.Join(home, ".config", "configstore", "snyk.json") - buff, err := os.ReadFile(snykConfFilePath) - if err != nil { - if os.IsNotExist(err) { - return "", nil - } - return "", err - } - var config snykConfig - if err := json.Unmarshal(buff, &config); err != nil { - return "", err - } - return config.API, nil -} diff --git a/internal/provider/error.go b/internal/provider/error.go deleted file mode 100644 index 6a9ee37f..00000000 --- a/internal/provider/error.go +++ /dev/null @@ -1,53 +0,0 @@ -/* - Copyright 2020 Docker Inc. - - 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. -*/ - -package provider - -import "fmt" - -type authenticationError struct { -} - -func (a authenticationError) Error() string { - return "authentication error" -} - -// IsAuthenticationError check if the error type is an authentication error -func IsAuthenticationError(err error) bool { - _, ok := err.(*authenticationError) - return ok -} - -type invalidTokenError struct { - token string -} - -func (i invalidTokenError) Error() string { - return fmt.Sprintf("invalid authentication token %q", i.token) -} - -// IsInvalidTokenError check if the error type is an invalid token error -func IsInvalidTokenError(err error) bool { - _, ok := err.(*invalidTokenError) - return ok -} - -type containerizedError struct { -} - -func (c containerizedError) Error() string { - return "" -} diff --git a/internal/provider/error_test.go b/internal/provider/error_test.go deleted file mode 100644 index ab057ef1..00000000 --- a/internal/provider/error_test.go +++ /dev/null @@ -1,34 +0,0 @@ -/* - Copyright 2020 Docker Inc. - - 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. -*/ - -package provider - -import ( - "errors" - "testing" - - "gotest.tools/v3/assert" -) - -func TestIsAuthenticationError(t *testing.T) { - assert.Assert(t, IsAuthenticationError(&authenticationError{})) - assert.Assert(t, !IsAuthenticationError(errors.New(""))) -} - -func TestIsInvalidTokenError(t *testing.T) { - assert.Assert(t, IsInvalidTokenError(&invalidTokenError{})) - assert.Assert(t, !IsInvalidTokenError(errors.New(""))) -} diff --git a/internal/provider/provider.go b/internal/provider/provider.go deleted file mode 100644 index cae4a0f8..00000000 --- a/internal/provider/provider.go +++ /dev/null @@ -1,215 +0,0 @@ -/* - Copyright 2020 Docker Inc. - - 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. -*/ - -package provider - -import ( - "context" - "fmt" - "io" - "os" - "os/exec" - - "github.com/docker/scan-cli-plugin/internal/authentication" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/registry" - "github.com/docker/scan-cli-plugin/internal/hub" -) - -// Provider abstracts a scan provider -type Provider interface { - Authenticate(token string) error - Scan(image string) error - Version() (string, error) -} - -// Options default options for all provider types -type Options struct { - flags []string - auth types.AuthConfig - context context.Context - out io.Writer - err io.Writer - path string - version string -} - -// NewProvider returns default provider options setup with the give options -func NewProvider(options ...Ops) (Options, error) { - provider := Options{ - flags: []string{"container", "test"}, - out: os.Stdout, - err: os.Stderr, - } - for _, op := range options { - if err := op(&provider); err != nil { - return Options{}, err - } - } - return provider, nil -} - -// UseExternalBinary return true if the provider path option is setup -func UseExternalBinary(providerOpts Options) bool { - return providerOpts.path != "" -} - -// Ops defines options to setup a provider -type Ops func(provider *Options) error - -// WithAuthConfig update the Snyk provider with the auth configuration from Docker CLI -func WithAuthConfig(authResolver func(*registry.IndexInfo) types.AuthConfig) Ops { - return func(provider *Options) error { - provider.auth = authResolver(hub.GetInstance().RegistryInfo) - return nil - } -} - -// WithContext update the provider with a cancelable context -func WithContext(ctx context.Context) Ops { - return func(options *Options) error { - options.context = ctx - return nil - } -} - -// WithStreams sets the out and err streams to be used by commands -func WithStreams(out, err io.Writer) Ops { - return func(options *Options) error { - options.out = out - options.err = err - return nil - } -} - -// WithJSON set JSONFormat to display scan result in JSON -func WithJSON() Ops { - return func(provider *Options) error { - provider.flags = append(provider.flags, "--json") - return nil - } -} - -// WithoutBaseImageVulnerabilities don't display the vulnerabilities from the base image -func WithoutBaseImageVulnerabilities() Ops { - return func(provider *Options) error { - provider.flags = append(provider.flags, "--exclude-base-image-vulns") - return nil - } -} - -// WithDockerFile improve result by providing a Dockerfile -func WithDockerFile(path string) Ops { - return func(provider *Options) error { - provider.flags = append(provider.flags, "--file="+path) - return nil - } -} - -// WithDependencyTree shows the dependency tree before scan results -func WithDependencyTree() Ops { - return func(provider *Options) error { - provider.flags = append(provider.flags, "--print-deps") - return nil - } -} - -// WithFailOn only fail when there are vulnerabilities that can be fixed -func WithFailOn(failOn string) Ops { - return func(provider *Options) error { - provider.flags = append(provider.flags, "--fail-on="+failOn) - return nil - } -} - -// WithSeverity only reports vulnerabilities of the provided level or higher -func WithSeverity(severity string) Ops { - return func(provider *Options) error { - provider.flags = append(provider.flags, "--severity-threshold="+severity) - return nil - } -} - -// WithGroupIssues groups same issues in a single one when using --json flag -func WithGroupIssues() Ops { - return func(provider *Options) error { - provider.flags = append(provider.flags, "--group-issues") - return nil - } -} - -// WithAppVulns scans for applications vulnerabilities as well -func WithAppVulns() Ops { - return func(provider *Options) error { - provider.flags = append(provider.flags, "--app-vulns") - // We started with a default depth value of 2 - provider.flags = append(provider.flags, fmt.Sprintf("--nested-jars-depth=%d", 2)) - return nil - } -} - -// WithPath update the provider binary with the path from the configuration -func WithPath(path string) Ops { - return func(provider *Options) error { - if p, err := exec.LookPath("snyk"); err == nil { - if ok, err := checkUserSnykBinaryVersion(p); ok { - path = p - } else { - fmt.Printf("%s\n\n", err.Error()) - } - } - provider.path = path - return nil - } -} - -// WithExperimental allows running `--json` flag in combination of `--app-vulns` -func WithExperimental() Ops { - return func(provider *Options) error { - provider.flags = append(provider.flags, "--experimental") - return nil - } -} - -// WithVersion set the version of the scan cli plugin to the provider -func WithVersion(version string) Ops { - return func(provider *Options) error { - provider.version = version - return nil - } -} - -func getToken(opts Options) (string, error) { - if opts.auth.Username == "" { - return "", fmt.Errorf(`You need to be logged in to Docker Hub to use the scan feature. - -If you are not using Docker Desktop, either -- use the "docker login" CLI command with a username and password. Note this will not work if - 2FA is required or if SSO enforcement is enabled on Docker Hub; or -- use the "docker login" CLI command with a username and Personal Access Token. This requires - a token to be generated in advance. - -If you are using Docker Desktop: login via the UI or whale menu`) - } - h := hub.GetInstance() - jwks, err := h.FetchJwks() - if err != nil { - return "", err - } - authenticator := authentication.NewAuthenticator(jwks, h.APIHubBaseURL) - return authenticator.GetToken(opts.auth) -} diff --git a/internal/provider/snyk.go b/internal/provider/snyk.go deleted file mode 100644 index 582af68f..00000000 --- a/internal/provider/snyk.go +++ /dev/null @@ -1,194 +0,0 @@ -/* - Copyright 2020 Docker Inc. - - 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. -*/ - -package provider - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "os" - "os/exec" - "path/filepath" - "strings" - - "github.com/Masterminds/semver/v3" - "github.com/google/uuid" - "github.com/mitchellh/go-homedir" -) - -var ( - // SnykDesktopVersion is the version of the Snyk CLI Binary embedded with Docker Desktop - SnykDesktopVersion = "unknown" -) - -type snykProvider struct { - Options -} - -// NewSnykProvider returns a Snyk implementation of scan provider -func NewSnykProvider(defaultProvider Options, snykOps ...SnykProviderOps) (Provider, error) { - provider := snykProvider{ - Options: defaultProvider, - } - for _, snykOp := range snykOps { - if err := snykOp(&provider); err != nil { - return nil, err - } - } - return &provider, nil -} - -// SnykProviderOps function taking a pointer to a Snyk Provider and returning an error if needed -type SnykProviderOps func(*snykProvider) error - -func (s *snykProvider) Authenticate(token string) error { - if token != "" { - if _, err := uuid.Parse(token); err != nil { - return &invalidTokenError{token} - } - } - cmd := s.newCommand("auth", token) - cmd.Env = append(cmd.Env, - "SNYK_UTM_MEDIUM=Partner", - "SNYK_UTM_SOURCE=Docker", - "SNYK_UTM_CAMPAIGN=Docker-Desktop-2020") - cmd.Stdout = s.out - cmd.Stderr = s.err - return checkCommandErr(cmd.Run()) -} - -func (s *snykProvider) Scan(image string) error { - // check snyk token - cmd := s.newCommand(append(s.flags, image)...) - if authenticated, err := isAuthenticatedOnSnyk(); authenticated == "" || err != nil { - var err error - token, err := getToken(s.Options) - if err != nil { - return fmt.Errorf("failed to get DockerScanID: %s", err) - } - cmd.Env = append(cmd.Env, fmt.Sprintf("SNYK_DOCKER_TOKEN=%s", token)) - } else { - cmd.Env = append(cmd.Env, fmt.Sprintf("SNYK_TOKEN=%s", authenticated)) - } - - cmd.Stdout = s.out - cmd.Stderr = s.err - return checkCommandErr(cmd.Run()) -} - -func (s *snykProvider) Version() (string, error) { - cmd := s.newCommand("--version") - buff := bytes.NewBuffer(nil) - buffErr := bytes.NewBuffer(nil) - cmd.Stdout = buff - cmd.Stderr = buffErr - if err := cmd.Run(); err != nil { - errMsg := fmt.Sprintf("failed to get snyk version: %s", checkCommandErr(err)) - if buffErr.String() != "" { - errMsg = fmt.Sprintf(errMsg+",%s", buffErr.String()) - } - return "", fmt.Errorf(errMsg) - } - return fmt.Sprintf("Snyk (%s)", strings.TrimSpace(buff.String())), nil -} - -func (s *snykProvider) newCommand(arg ...string) *exec.Cmd { - cmd := exec.CommandContext(s.context, s.path, arg...) - cmd.Env = append(os.Environ(), - "NO_UPDATE_NOTIFIER=true", - "SNYK_CFG_DISABLESUGGESTIONS=true", - "SNYK_INTEGRATION_NAME=DOCKER_DESKTOP", - "SNYK_INTEGRATION_VERSION="+s.version, - ) - return cmd -} - -func checkCommandErr(err error) error { - if err == nil { - return nil - } - if err == exec.ErrNotFound { - // Could not find Snyk in $PATH - return fmt.Errorf("could not find Snyk binary") - } else if _, ok := err.(*exec.Error); ok { - return fmt.Errorf("could not find Snyk binary") - } else if _, ok := err.(*os.PathError); ok { - // The specified path for Snyk binary does not exist - return fmt.Errorf("could not find Snyk binary") - } - return err -} - -type snykConfig struct { - API string `json:"api,omitempty"` -} - -func isAuthenticatedOnSnyk() (string, error) { - home, err := homedir.Dir() - if err != nil { - return "", err - } - snykConfFilePath := filepath.Join(home, ".config", "configstore", "snyk.json") - buff, err := os.ReadFile(snykConfFilePath) - if err != nil { - if os.IsNotExist(err) { - return "", nil - } - return "", err - } - var config snykConfig - if err := json.Unmarshal(buff, &config); err != nil { - return "", err - } - - return config.API, nil -} - -func checkUserSnykBinaryVersion(path string) (bool, error) { - cmd := exec.Command(path, "--version") - buff := bytes.NewBuffer(nil) - cmd.Stdout = buff - cmd.Stderr = io.Discard - if err := cmd.Run(); err != nil { - // an error occurred, so let's use the desktop binary - return false, err - } - ver, err := semver.NewVersion(cleanVersion(buff.String())) - if err != nil { - return false, err - } - constraint, err := semver.NewConstraint(minimalSnykVersion()) - if err != nil { - return false, err - } - matchConstraint := constraint.Check(ver) - if !matchConstraint { - return matchConstraint, fmt.Errorf("the Snyk version %s installed on your system is older as the one embedded by Docker Desktop (%s), using embedded Snyk version instead", - ver, minimalSnykVersion()) - } - return matchConstraint, nil -} - -func cleanVersion(version string) string { - version = strings.TrimSpace(version) - return strings.Split(version, " ")[0] -} - -func minimalSnykVersion() string { - return fmt.Sprintf(">=%s", SnykDesktopVersion) -} diff --git a/internal/provider/snyk_test.go b/internal/provider/snyk_test.go deleted file mode 100644 index 30d7cbbd..00000000 --- a/internal/provider/snyk_test.go +++ /dev/null @@ -1,111 +0,0 @@ -/* - Copyright 2020 Docker Inc. - - 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. -*/ - -package provider - -import ( - "bytes" - "context" - "os" - "path/filepath" - "runtime" - "strings" - "testing" - - "gotest.tools/v3/assert" - "gotest.tools/v3/env" - "gotest.tools/v3/fs" -) - -var ( - _ Provider = &snykProvider{} -) - -const ( - snykToken = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" - fakeVersion = "abcd1234" -) - -func TestSnykLoginEnvVars(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("Can't run this test on windows") - } - - provider, outStream := setupMockSnykBinary(t) - - err := provider.Authenticate(snykToken) - assert.NilError(t, err) - - // SNYK_INTEGRATION_NAME is always set - assert.Assert(t, strings.Contains(outStream.String(), "SNYK_INTEGRATION_NAME=DOCKER_DESKTOP")) - // SNYK_INTEGRATION_VERSION is always set - assert.Assert(t, strings.Contains(outStream.String(), "SNYK_INTEGRATION_VERSION="+fakeVersion)) - // NO_UPDATE_NOTIFIER disables node.js automatic update notification in console - assert.Assert(t, strings.Contains(outStream.String(), "NO_UPDATE_NOTIFIER=true")) - // SNYK_CFG_DISABLESUGGESTIONS removes user hints from snyk - assert.Assert(t, strings.Contains(outStream.String(), "SNYK_CFG_DISABLESUGGESTIONS=true")) - // Check UTMs variables - assert.Assert(t, strings.Contains(outStream.String(), "SNYK_UTM_MEDIUM=Partner")) - assert.Assert(t, strings.Contains(outStream.String(), "SNYK_UTM_SOURCE=Docker")) - assert.Assert(t, strings.Contains(outStream.String(), "SNYK_UTM_CAMPAIGN=Docker-Desktop-2020")) -} - -func TestSnykScanEnvVars(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("Can't run this test on windows") - } - - // Create Snyk config file with token - tempDir := fs.NewDir(t, t.Name(), - fs.WithDir(".config", - fs.WithDir("configstore", - fs.WithFile("snyk.json", `{"api":"`+snykToken+`"}`)))) - defer tempDir.Remove() - defer env.Patch(t, "HOME", tempDir.Path())() - - provider, outStream := setupMockSnykBinary(t) - - err := provider.Scan("image") - assert.NilError(t, err) - - // SNYK_INTEGRATION_NAME is always set - assert.Assert(t, strings.Contains(outStream.String(), "SNYK_INTEGRATION_NAME=DOCKER_DESKTOP")) - // SNYK_INTEGRATION_VERSION is always set - assert.Assert(t, strings.Contains(outStream.String(), "SNYK_INTEGRATION_VERSION="+fakeVersion)) - // NO_UPDATE_NOTIFIER disables node.js automatic update notification in console - assert.Assert(t, strings.Contains(outStream.String(), "NO_UPDATE_NOTIFIER=true")) - // SNYK_CFG_DISABLESUGGESTIONS removes user hints from snyk - assert.Assert(t, strings.Contains(outStream.String(), "SNYK_CFG_DISABLESUGGESTIONS=true")) -} - -func setupMockSnykBinary(t *testing.T) (Provider, *bytes.Buffer) { - pwd, err := os.Getwd() - assert.NilError(t, err) - snykPath := filepath.Join(pwd, "testdata", "snyk") - outStream := bytes.NewBuffer(nil) - errStream := bytes.NewBuffer(nil) - - defaultProvider, err := NewProvider(WithContext(context.Background()), - WithPath(snykPath), - WithStreams(outStream, errStream), - WithVersion(fakeVersion), - ) - assert.NilError(t, err) - provider, err := NewSnykProvider( - defaultProvider) - assert.NilError(t, err) - return provider, outStream -} diff --git a/internal/provider/testdata/snyk b/internal/provider/testdata/snyk deleted file mode 100755 index 56440602..00000000 --- a/internal/provider/testdata/snyk +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash - -# Copyright The Compose Specification Authors. - -# 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. - -env diff --git a/internal/version.go b/internal/version.go index 9c083efc..1b44b786 100644 --- a/internal/version.go +++ b/internal/version.go @@ -19,8 +19,6 @@ package internal import ( "fmt" "strings" - - "github.com/docker/scan-cli-plugin/internal/provider" ) var ( @@ -31,16 +29,11 @@ var ( ) // FullVersion return plugin version, git commit and the provider cli version -func FullVersion(scanProvider provider.Provider) (string, error) { - providerVersion, err := scanProvider.Version() - if err != nil { - return "", err - } +func FullVersion() string { res := []string{ fmt.Sprintf("Version: %s", Version), fmt.Sprintf("Git commit: %s", GitCommit), - fmt.Sprintf("Provider: %s", providerVersion), } - return strings.Join(res, "\n"), nil + return strings.Join(res, "\n") } diff --git a/internal/version_test.go b/internal/version_test.go index 9b5413d6..e1e1d113 100644 --- a/internal/version_test.go +++ b/internal/version_test.go @@ -23,29 +23,10 @@ import ( "gotest.tools/v3/assert" ) -type providerStub struct { - version string -} - -func (s *providerStub) Version() (string, error) { - return s.version, nil -} - -func (s *providerStub) Scan(image string) error { - return nil -} - -func (s *providerStub) Authenticate(token string) error { - return nil -} - func TestFullVersion(t *testing.T) { - stub := &providerStub{version: "stub-version"} - actual, err := FullVersion(stub) - assert.NilError(t, err) + actual := FullVersion() expected := fmt.Sprintf( `Version: %s -Git commit: %s -Provider: %s`, Version, GitCommit, "stub-version") +Git commit: %s`, Version, GitCommit) assert.Equal(t, actual, expected) } diff --git a/vars.mk b/vars.mk index d3867f7a..f886f67e 100644 --- a/vars.mk +++ b/vars.mk @@ -1,14 +1,7 @@ -# Pinned Versions -SNYK_DESKTOP_VERSION=1.1064.0 -SNYK_USER_VERSION=1.1064.0 -SNYK_OLD_VERSION=1.382.1 -# Digest of the 1.1064.0 snyk/snyk:docker image -SNYK_IMAGE_DIGEST=sha256:068f428b9448d2d09aa70bf7dfe7ac98eda25dbc189637528f1a279d3fcae593 GO_VERSION=1.17.5 CLI_VERSION=20.10.11 ALPINE_VERSION=3.15.0 GOLANGCI_LINT_VERSION=v1.27.0-alpine -GOTESTSUM_VERSION=1.8.1 LTAG_VERSION=v0.2.3 GOOS ?= $(shell go env GOOS)