diff --git a/.github/workflows/add-to-project.yml b/.github/workflows/add-to-project.yml index ae1bc3921..599648f3d 100644 --- a/.github/workflows/add-to-project.yml +++ b/.github/workflows/add-to-project.yml @@ -24,7 +24,7 @@ jobs: name: Add issue to project runs-on: ubuntu-latest steps: - - uses: actions/add-to-project@31b3f3ccdc584546fc445612dec3f38ff5edb41c # v0.5.0 + - uses: actions/add-to-project@0609a2702eefb44781da00f8e04901d6e5cd2b92 # v0.6.0 with: project-url: https://github.com/orgs/notaryproject/projects/10 github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a42470d77..ad756860a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,14 +31,14 @@ jobs: fail-fast: true steps: - name: Set up Go ${{ matrix.go-version }} - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ matrix.go-version }} check-latest: true - name: Check out code uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Cache Go modules - uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 + uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 id: go-mod-cache with: path: ~/go/pkg/mod @@ -59,4 +59,4 @@ jobs: make e2e-covdata fi - name: Upload coverage to codecov.io - uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4 + uses: codecov/codecov-action@54bcd8715eee62d40e33596ef5e8f0f48dbbccab # v4.1.0 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 2d99d3eed..97fdf76c5 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -15,9 +15,13 @@ name: "CodeQL" on: push: - branches: main + branches: + - main + - release-* pull_request: - branches: main + branches: + - main + - release-* schedule: - cron: '38 21 * * 1' @@ -40,13 +44,13 @@ jobs: - name: Checkout repository uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set up Go ${{ matrix.go-version }} environment - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ matrix.go-version }} check-latest: true - name: Initialize CodeQL - uses: github/codeql-action/init@2cb752a87e96af96708ab57187ab6372ee1973ab # v2.22.0 + uses: github/codeql-action/init@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5 with: languages: go - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@2cb752a87e96af96708ab57187ab6372ee1973ab # v2.22.0 + uses: github/codeql-action/analyze@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5 diff --git a/.github/workflows/license-checker.yml b/.github/workflows/license-checker.yml index 07303f82e..54e121173 100644 --- a/.github/workflows/license-checker.yml +++ b/.github/workflows/license-checker.yml @@ -15,9 +15,13 @@ name: License Checker on: push: - branches: main + branches: + - main + - release-* pull_request: - branches: main + branches: + - main + - release-* permissions: contents: read diff --git a/.github/workflows/release-github.yml b/.github/workflows/release-github.yml index 0b173fd88..6bb5fd097 100644 --- a/.github/workflows/release-github.yml +++ b/.github/workflows/release-github.yml @@ -33,7 +33,7 @@ jobs: fail-fast: true steps: - name: Set up Go ${{ matrix.go-version }} - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ matrix.go-version }} check-latest: true diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index a8c771fcb..8ad552e43 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -18,7 +18,9 @@ on: # Weekly on Saturdays. - cron: '30 1 * * 6' push: - branches: [ main ] + branches: + - main + - release-* paths: - '!docs/**' - '!specs/**' @@ -44,7 +46,7 @@ jobs: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@483ef80eb98fb506c348f7d62e28055e49fe2398 # tag=v2.3.0 + uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # tag=v2.3.1 with: results_file: results.sarif results_format: sarif @@ -52,13 +54,13 @@ jobs: publish_results: true - name: "Upload artifact" - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # tag=v3.1.3 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # tag=v4.3.1 with: name: SARIF file path: results.sarif retention-days: 5 - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@2cb752a87e96af96708ab57187ab6372ee1973ab # v2.22.0 + uses: github/codeql-action/upload-sarif@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5 with: sarif_file: results.sarif diff --git a/CODEOWNERS b/CODEOWNERS index 201450366..204535fab 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,3 +1,3 @@ # Repo-Level Owners (in alphabetical order) # Note: This is only for the notaryproject/notation repo -* @gokarnm @JeyJeyGao @justincormack @niazfk @priteshbandi @rgnote @shizhMSFT @stevelasker @Two-Hearts +* @gokarnm @JeyJeyGao @justincormack @niazfk @priteshbandi @rgnote @shizhMSFT @stevelasker @toddysm @Two-Hearts diff --git a/MAINTAINERS b/MAINTAINERS index d2a48ede0..82cfeee2c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2,13 +2,14 @@ # Pattern: [First Name] [Last Name] <[Email Address]> ([GitHub Handle]) Justin Cormack (@justincormack) Niaz Khan (@niazfk) +Pritesh Bandi (@priteshbandi) +Shiwei Zhang (@shizhMSFT) Steve Lasker (@stevelasker) +Toddy Mladenov (@toddysm) # Repo-Level Maintainers (in alphabetical order) # Note: This is for the notaryproject/notation repo Junjie Gao (@JeyJeyGao) Milind Gokarn (@gokarnm) Patrick Zheng (@Two-Hearts) -Pritesh Bandi (@priteshbandi) Rakesh Gariganti (@rgnote) -Shiwei Zhang (@shizhMSFT) diff --git a/README.md b/README.md index 0316c149c..c50e9f564 100644 --- a/README.md +++ b/README.md @@ -24,10 +24,10 @@ You can find the Notary Project [README](https://github.com/notaryproject/.githu ## Quick Start -- [Quick start: Sign and validate a container image](https://notaryproject.dev/docs/quickstart-guides/quickstart/) +- [Quick start: Sign and validate a container image](https://notaryproject.dev/docs/quickstart-guides/quickstart-sign-image-artifact/) - [Try out Notation in this Killercoda interactive sandbox environment](https://killercoda.com/notaryproject/scenario/notation) - Build, sign, and verify container images using Notation with [Azure Key Vault](https://docs.microsoft.com/azure/container-registry/container-registry-tutorial-sign-build-push?wt.mc_id=azurelearn_inproduct_oss_notaryproject) or [AWS Signer](https://docs.aws.amazon.com/signer/latest/developerguide/container-workflow.html) - + ## Community Notary Project is a [CNCF Incubating project](https://www.cncf.io/projects/notary/). We :heart: your contribution. diff --git a/cmd/notation/inspect.go b/cmd/notation/inspect.go index a92b0c083..eb442fc46 100644 --- a/cmd/notation/inspect.go +++ b/cmd/notation/inspect.go @@ -94,7 +94,7 @@ Example - [Experimental] Inspect signatures on an OCI artifact identified by a d Long: longMessage, Args: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { - return errors.New("missing reference") + return errors.New("missing reference to the artifact: use `notation inspect --help` to see what parameters are required") } opts.reference = args[0] return nil diff --git a/cmd/notation/internal/plugin/plugin.go b/cmd/notation/internal/plugin/plugin.go new file mode 100644 index 000000000..078f66ea3 --- /dev/null +++ b/cmd/notation/internal/plugin/plugin.go @@ -0,0 +1,86 @@ +// Copyright The Notary Project 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. + +package plugin + +import ( + "context" + "fmt" + "io" + "net/http" + "time" + + "github.com/notaryproject/notation/internal/httputil" +) + +// MaxPluginSourceBytes specifies the limit on how many bytes are allowed in the +// server's response to the download from URL request. +// +// The plugin source size must be strictly less than this value. +var MaxPluginSourceBytes int64 = 256 * 1024 * 1024 // 256 MiB + +// PluginSourceType is an enum for plugin source +type PluginSourceType int + +const ( + // PluginSourceTypeFile means plugin source is file + PluginSourceTypeFile PluginSourceType = 1 + iota + + // PluginSourceTypeURL means plugin source is URL + PluginSourceTypeURL +) + +const ( + // MediaTypeZip means plugin file is zip + MediaTypeZip = "application/zip" + + // MediaTypeGzip means plugin file is gzip + MediaTypeGzip = "application/x-gzip" +) + +// DownloadPluginFromURLTimeout is the timeout when downloading plugin from a +// URL +const DownloadPluginFromURLTimeout = 10 * time.Minute + +// DownloadPluginFromURL downloads plugin source from url to a tmp file on file +// system +func DownloadPluginFromURL(ctx context.Context, pluginURL string, tmpFile io.Writer) error { + // Get the data + client := httputil.NewAuthClient(ctx, &http.Client{Timeout: DownloadPluginFromURLTimeout}) + req, err := http.NewRequest(http.MethodGet, pluginURL, nil) + if err != nil { + return err + } + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + // Check server response + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("%s %q: https response bad status: %s", resp.Request.Method, resp.Request.URL, resp.Status) + } + // Write the body to file + lr := &io.LimitedReader{ + R: resp.Body, + N: MaxPluginSourceBytes, + } + _, err = io.Copy(tmpFile, lr) + if err != nil { + return err + } + if lr.N == 0 { + return fmt.Errorf("%s %q: https response reached the %d MiB size limit", resp.Request.Method, resp.Request.URL, MaxPluginSourceBytes/1024/1024) + } + return nil +} diff --git a/cmd/notation/list.go b/cmd/notation/list.go index e06215821..a651386eb 100644 --- a/cmd/notation/list.go +++ b/cmd/notation/list.go @@ -43,14 +43,32 @@ func listCommand(opts *listOpts) *cobra.Command { inputType: inputTypeRegistry, // remote registry by default } } + longMessage := `List all the signatures associated with signed artifact + +Example - List signatures of an OCI artifact: + notation list /@ + +Example - List signatures of an OCI artifact identified by a tag (Notation will resolve tag to digest) + notation list /: +` + experimentalExamples := ` +Example - [Experimental] List signatures of an OCI artifact using the Referrers API. If it's not supported (returns 404), fallback to the Referrers tag schema + notation list --allow-referrers-api /@ + +Example - [Experimental] List signatures of an OCI artifact referenced in an OCI layout + notation list --oci-layout "@" + +Example - [Experimental] List signatures of an OCI artifact identified by a tag and referenced in an OCI layout + notation list --oci-layout ":" +` command := &cobra.Command{ Use: "list [flags] ", Aliases: []string{"ls"}, Short: "List signatures of the signed artifact", - Long: "List all the signatures associated with signed artifact", + Long: longMessage, Args: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { - return errors.New("no reference specified") + return errors.New("missing reference to the artifact: use `notation list --help` to see what parameters are required") } opts.reference = args[0] return nil @@ -74,6 +92,7 @@ func listCommand(opts *listOpts) *cobra.Command { command.Flags().BoolVar(&opts.ociLayout, "oci-layout", false, "[Experimental] list signatures stored in OCI image layout") experimental.HideFlags(command, "", []string{"allow-referrers-api", "oci-layout"}) command.Flags().IntVar(&opts.maxSignatures, "max-signatures", 100, "maximum number of signatures to evaluate or examine") + experimental.HideFlags(command, experimentalExamples, []string{"allow-referrers-api", "oci-layout"}) return command } diff --git a/cmd/notation/login.go b/cmd/notation/login.go index 10d9e169f..e1a2bb49c 100644 --- a/cmd/notation/login.go +++ b/cmd/notation/login.go @@ -25,9 +25,9 @@ import ( "github.com/notaryproject/notation-go/log" "github.com/notaryproject/notation/internal/auth" "github.com/notaryproject/notation/internal/cmd" - credentials "github.com/oras-project/oras-credentials-go" "github.com/spf13/cobra" "golang.org/x/term" + "oras.land/oras-go/v2/registry/remote/credentials" ) const urlDocHowToAuthenticate = "https://notaryproject.dev/docs/how-to/registry-authentication/" diff --git a/cmd/notation/logout.go b/cmd/notation/logout.go index d368848a7..6a89c4889 100644 --- a/cmd/notation/logout.go +++ b/cmd/notation/logout.go @@ -20,8 +20,8 @@ import ( "github.com/notaryproject/notation/internal/auth" "github.com/notaryproject/notation/internal/cmd" - credentials "github.com/oras-project/oras-credentials-go" "github.com/spf13/cobra" + "oras.land/oras-go/v2/registry/remote/credentials" ) type logoutOpts struct { diff --git a/cmd/notation/main.go b/cmd/notation/main.go index feb103aa3..8d5be0e74 100644 --- a/cmd/notation/main.go +++ b/cmd/notation/main.go @@ -16,7 +16,9 @@ package main import ( "os" + "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation/cmd/notation/cert" + "github.com/notaryproject/notation/cmd/notation/plugin" "github.com/notaryproject/notation/cmd/notation/policy" "github.com/spf13/cobra" ) @@ -31,6 +33,16 @@ func main() { // to avoid leaking credentials os.Unsetenv(defaultUsernameEnv) os.Unsetenv(defaultPasswordEnv) + + // update Notation config directory + if notationConfig := os.Getenv("NOTATION_CONFIG"); notationConfig != "" { + dir.UserConfigDir = notationConfig + } + + // update Notation Libexec directory (for plugins) + if notationLibexec := os.Getenv("NOTATION_LIBEXEC"); notationLibexec != "" { + dir.UserLibexecDir = notationLibexec + } }, } cmd.AddCommand( @@ -40,7 +52,7 @@ func main() { cert.Cmd(), policy.Cmd(), keyCommand(), - pluginCommand(), + plugin.Cmd(), loginCommand(nil), logoutCommand(nil), versionCommand(), diff --git a/cmd/notation/manifest.go b/cmd/notation/manifest.go index af2e3e921..da33b1f72 100644 --- a/cmd/notation/manifest.go +++ b/cmd/notation/manifest.go @@ -48,7 +48,10 @@ func resolveReference(ctx context.Context, inputType inputType, reference string case inputTypeRegistry: ref, err := registry.ParseReference(reference) if err != nil { - return ocispec.Descriptor{}, "", fmt.Errorf("failed to resolve user input reference: %w", err) + return ocispec.Descriptor{}, "", fmt.Errorf("%q: %w. Expecting /: or /@", reference, err) + } + if ref.Reference == "" { + return ocispec.Descriptor{}, "", fmt.Errorf("%q: invalid reference: no tag or digest. Expecting /: or /@", reference) } tagOrDigestRef = ref.Reference resolvedRef = ref.Registry + "/" + ref.Repository @@ -113,16 +116,16 @@ func parseOCILayoutReference(raw string) (string, string, error) { // find `tag` idx := strings.LastIndex(raw, ":") if idx == -1 || (idx == 1 && len(raw) > 2 && unicode.IsLetter(rune(raw[0])) && raw[2] == '\\') { - return "", "", notationerrors.ErrorOCILayoutMissingReference{} + return "", "", notationerrors.ErrorOCILayoutMissingReference{Msg: fmt.Sprintf("%q: invalid reference: missing tag or digest. Expecting : or @", raw)} } else { path, ref = raw[:idx], raw[idx+1:] } } if path == "" { - return "", "", fmt.Errorf("found empty file path in %q", raw) + return "", "", fmt.Errorf("%q: invalid reference: missing oci-layout file path. Expecting : or @", raw) } if ref == "" { - return "", "", fmt.Errorf("found empty reference in %q", raw) + return "", "", notationerrors.ErrorOCILayoutMissingReference{Msg: fmt.Sprintf("%q: invalid reference: missing tag or digest. Expecting : or @", raw)} } return path, ref, nil } diff --git a/cmd/notation/plugin/cmd.go b/cmd/notation/plugin/cmd.go new file mode 100644 index 000000000..98c494b8f --- /dev/null +++ b/cmd/notation/plugin/cmd.go @@ -0,0 +1,31 @@ +// Copyright The Notary Project 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. + +package plugin + +import "github.com/spf13/cobra" + +func Cmd() *cobra.Command { + command := &cobra.Command{ + Use: "plugin", + Short: "Manage plugins", + } + + command.AddCommand( + listCommand(), + installCommand(nil), + uninstallCommand(nil), + ) + + return command +} diff --git a/cmd/notation/plugin/install.go b/cmd/notation/plugin/install.go new file mode 100644 index 000000000..b7538fc16 --- /dev/null +++ b/cmd/notation/plugin/install.go @@ -0,0 +1,352 @@ +// Copyright The Notary Project 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. + +package plugin + +import ( + "archive/tar" + "archive/zip" + "compress/gzip" + "context" + "errors" + "fmt" + "io" + "io/fs" + "net/url" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/notaryproject/notation-go/dir" + "github.com/notaryproject/notation-go/log" + "github.com/notaryproject/notation-go/plugin" + notationplugin "github.com/notaryproject/notation/cmd/notation/internal/plugin" + "github.com/notaryproject/notation/internal/cmd" + "github.com/notaryproject/notation/internal/osutil" + "github.com/spf13/cobra" +) + +const ( + notationPluginTmpDir = "notation-plugin" + notationPluginDownloadTmpFile = "notation-plugin-download" +) + +type pluginInstallOpts struct { + cmd.LoggingFlagOpts + pluginSourceType notationplugin.PluginSourceType + pluginSource string + inputChecksum string + isFile bool + isURL bool + force bool +} + +func installCommand(opts *pluginInstallOpts) *cobra.Command { + if opts == nil { + opts = &pluginInstallOpts{} + } + command := &cobra.Command{ + Use: "install [flags] <--file|--url> ", + Aliases: []string{"add"}, + Short: "Install a plugin", + Long: `Install a plugin + +Example - Install plugin from file system: + notation plugin install --file wabbit-plugin-v1.0.zip + +Example - Install plugin from file system with user input SHA256 checksum: + notation plugin install --file wabbit-plugin-v1.0.zip --sha256sum 113062a462674a0e35cb5cad75a0bb2ea16e9537025531c0fd705018fcdbc17e + +Example - Install plugin from file system regardless if it's already installed: + notation plugin install --file wabbit-plugin-v1.0.zip --force + +Example - Install plugin from file system with .tar.gz: + notation plugin install --file wabbit-plugin-v1.0.tar.gz + +Example - Install plugin from file system with a single plugin executable file: + notation plugin install --file notation-wabbit-plugin + +Example - Install plugin from URL, SHA256 checksum is required: + notation plugin install --url https://wabbit-networks.com/intaller/linux/amd64/wabbit-plugin-v1.0.tar.gz --sha256sum f8a75d9234db90069d9eb5660e5374820edf36d710bd063f4ef81e7063d3810b +`, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + switch { + case opts.isFile: + return errors.New("missing plugin file path") + case opts.isURL: + return errors.New("missing plugin URL") + } + return errors.New("missing plugin source location") + } + if len(args) > 1 { + return fmt.Errorf("can only install one plugin at a time, but got %v", args) + } + opts.pluginSource = args[0] + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + switch { + case opts.isFile: + opts.pluginSourceType = notationplugin.PluginSourceTypeFile + case opts.isURL: + opts.pluginSourceType = notationplugin.PluginSourceTypeURL + } + return install(cmd, opts) + }, + } + opts.LoggingFlagOpts.ApplyFlags(command.Flags()) + command.Flags().BoolVar(&opts.isFile, "file", false, "install plugin from a file on file system") + command.Flags().BoolVar(&opts.isURL, "url", false, fmt.Sprintf("install plugin from an HTTPS URL. The plugin download timeout is %s", notationplugin.DownloadPluginFromURLTimeout)) + command.Flags().StringVar(&opts.inputChecksum, "sha256sum", "", "must match SHA256 of the plugin source, required when \"--url\" flag is set") + command.Flags().BoolVar(&opts.force, "force", false, "force the installation of the plugin") + command.MarkFlagsMutuallyExclusive("file", "url") + command.MarkFlagsOneRequired("file", "url") + return command +} + +func install(command *cobra.Command, opts *pluginInstallOpts) error { + // set log level + ctx := opts.LoggingFlagOpts.InitializeLogger(command.Context()) + // core process + switch opts.pluginSourceType { + case notationplugin.PluginSourceTypeFile: + if err := installPlugin(ctx, opts.pluginSource, opts.inputChecksum, opts.force); err != nil { + return fmt.Errorf("plugin installation failed: %w", err) + } + return nil + case notationplugin.PluginSourceTypeURL: + if opts.inputChecksum == "" { + return errors.New("installing from URL requires non-empty SHA256 checksum of the plugin source") + } + pluginURL, err := url.Parse(opts.pluginSource) + if err != nil { + return fmt.Errorf("failed to parse plugin download URL %s with error: %w", pluginURL, err) + } + if pluginURL.Scheme != "https" { + return fmt.Errorf("failed to download plugin from URL: only the HTTPS scheme is supported, but got %s", pluginURL.Scheme) + } + tmpFile, err := os.CreateTemp("", notationPluginDownloadTmpFile) + if err != nil { + return fmt.Errorf("failed to create temporary file required for downloading plugin: %w", err) + } + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + fmt.Printf("Downloading plugin from %s\n", opts.pluginSource) + err = notationplugin.DownloadPluginFromURL(ctx, opts.pluginSource, tmpFile) + if err != nil { + return fmt.Errorf("failed to download plugin from URL %s with error: %w", opts.pluginSource, err) + } + fmt.Println("Download completed") + if err := installPlugin(ctx, tmpFile.Name(), opts.inputChecksum, opts.force); err != nil { + return fmt.Errorf("plugin installation failed: %w", err) + } + return nil + default: + return errors.New("plugin installation failed: unknown plugin source type") + } +} + +// installPlugin installs the plugin given plugin source path +func installPlugin(ctx context.Context, inputPath string, inputChecksum string, force bool) error { + // sanity check + inputFileInfo, err := os.Stat(inputPath) + if err != nil { + return err + } + if !inputFileInfo.Mode().IsRegular() { + return fmt.Errorf("%s is not a valid file", inputPath) + } + // checksum check + if inputChecksum != "" { + if err := osutil.ValidateSHA256Sum(inputPath, inputChecksum); err != nil { + return err + } + } + // install the plugin based on file type + fileType, err := osutil.DetectFileType(inputPath) + if err != nil { + return err + } + switch fileType { + case notationplugin.MediaTypeZip: + rc, err := zip.OpenReader(inputPath) + if err != nil { + return err + } + defer rc.Close() + // check for '..' in file name to avoid zip slip vulnerability + for _, f := range rc.File { + if strings.Contains(f.Name, "..") { + return fmt.Errorf("file name in zip cannot contain '..', but found %q", f.Name) + } + } + return installPluginFromFS(ctx, rc, force) + case notationplugin.MediaTypeGzip: + // when file is gzip, required to be tar + return installPluginFromTarGz(ctx, inputPath, force) + default: + // input file is not in zip or gzip, try install directly + if inputFileInfo.Size() >= osutil.MaxFileBytes { + return fmt.Errorf("file size reached the %d MiB size limit", osutil.MaxFileBytes/1024/1024) + } + installOpts := plugin.CLIInstallOptions{ + PluginPath: inputPath, + Overwrite: force, + } + return installPluginWithOptions(ctx, installOpts) + } +} + +// installPluginFromFS extracts, validates and installs the plugin files +// from a fs.FS +// +// Note: zip.ReadCloser implments fs.FS +func installPluginFromFS(ctx context.Context, pluginFS fs.FS, force bool) error { + // set up logger + logger := log.GetLogger(ctx) + root := "." + // extracting all regular files from root into tmpDir + tmpDir, err := os.MkdirTemp("", notationPluginTmpDir) + if err != nil { + return fmt.Errorf("failed to create temporary directory: %w", err) + } + defer os.RemoveAll(tmpDir) + var pluginFileSize int64 + if err := fs.WalkDir(pluginFS, root, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + fName := d.Name() + if d.IsDir() && fName != root { // skip any dir in the fs except root + return fs.SkipDir + } + info, err := d.Info() + if err != nil { + return err + } + // only accept regular files + if !info.Mode().IsRegular() { + return nil + } + // check for plugin file size to avoid zip bomb vulnerability + pluginFileSize += info.Size() + if pluginFileSize >= osutil.MaxFileBytes { + return fmt.Errorf("total file size reached the %d MiB size limit", osutil.MaxFileBytes/1024/1024) + } + logger.Debugf("Extracting file %s...", fName) + rc, err := pluginFS.Open(path) + if err != nil { + return err + } + defer rc.Close() + tmpFilePath := filepath.Join(tmpDir, fName) + return osutil.CopyFromReaderToDir(rc, tmpFilePath, info.Mode()) + }); err != nil { + return err + } + // install core process + installOpts := plugin.CLIInstallOptions{ + PluginPath: tmpDir, + Overwrite: force, + } + return installPluginWithOptions(ctx, installOpts) +} + +// installPluginFromTarGz extracts and untar a plugin tar.gz file, validates and +// installs the plugin +func installPluginFromTarGz(ctx context.Context, tarGzPath string, force bool) error { + logger := log.GetLogger(ctx) + rc, err := os.Open(tarGzPath) + if err != nil { + return err + } + defer rc.Close() + decompressedStream, err := gzip.NewReader(rc) + if err != nil { + return err + } + defer decompressedStream.Close() + tarReader := tar.NewReader(decompressedStream) + // extracting all regular files into tmpDir + tmpDir, err := os.MkdirTemp("", notationPluginTmpDir) + if err != nil { + return fmt.Errorf("failed to create temporary directory: %w", err) + } + defer os.RemoveAll(tmpDir) + var pluginFileSize int64 + for { + header, err := tarReader.Next() + if err != nil { + if err == io.EOF { + break + } + return err + } + // check for '..' in file name to avoid zip slip vulnerability + if strings.Contains(header.Name, "..") { + return fmt.Errorf("file name in tar.gz cannot contain '..', but found %q", header.Name) + } + // only accept regular files + if !header.FileInfo().Mode().IsRegular() { + continue + } + // check for plugin file size to avoid zip bomb vulnerability + pluginFileSize += header.FileInfo().Size() + if pluginFileSize >= osutil.MaxFileBytes { + return fmt.Errorf("total file size reached the %d MiB size limit", osutil.MaxFileBytes/1024/1024) + } + fName := filepath.Base(header.Name) + logger.Debugf("Extracting file %s...", fName) + tmpFilePath := filepath.Join(tmpDir, fName) + if err := osutil.CopyFromReaderToDir(tarReader, tmpFilePath, header.FileInfo().Mode()); err != nil { + return err + } + } + // install core process + installOpts := plugin.CLIInstallOptions{ + PluginPath: tmpDir, + Overwrite: force, + } + return installPluginWithOptions(ctx, installOpts) +} + +// installPluginWithOptions installs plugin with CLIInstallOptions +func installPluginWithOptions(ctx context.Context, opts plugin.CLIInstallOptions) error { + mgr := plugin.NewCLIManager(dir.PluginFS()) + existingPluginMetadata, newPluginMetadata, err := mgr.Install(ctx, opts) + if err != nil { + var errPluginDowngrade plugin.PluginDowngradeError + if errors.As(err, &errPluginDowngrade) { + return fmt.Errorf("%w.\nIt is not recommended to install an older version. To force the installation, use the \"--force\" option", errPluginDowngrade) + } + + var errExeFile *plugin.PluginExecutableFileError + if errors.As(err, &errExeFile) { + return fmt.Errorf("%w.\nPlease ensure that the plugin executable file is compatible with %s/%s and has appropriate permissions.", err, runtime.GOOS, runtime.GOARCH) + } + + var errMalformedPlugin *plugin.PluginMalformedError + if errors.As(err, &errMalformedPlugin) { + return fmt.Errorf("%w.\nPlease ensure that the plugin executable file is intact and compatible with %s/%s. Contact the plugin publisher for further assistance.", errMalformedPlugin, runtime.GOOS, runtime.GOARCH) + } + return err + } + if existingPluginMetadata != nil { + fmt.Printf("Successfully updated plugin %s from version %s to %s\n", newPluginMetadata.Name, existingPluginMetadata.Version, newPluginMetadata.Version) + } else { + fmt.Printf("Successfully installed plugin %s, version %s\n", newPluginMetadata.Name, newPluginMetadata.Version) + } + return nil +} diff --git a/cmd/notation/plugin.go b/cmd/notation/plugin/list.go similarity index 84% rename from cmd/notation/plugin.go rename to cmd/notation/plugin/list.go index be2c2f3ad..bfc3ebffe 100644 --- a/cmd/notation/plugin.go +++ b/cmd/notation/plugin/list.go @@ -11,9 +11,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -package main +package plugin import ( + "errors" "fmt" "os" "text/tabwriter" @@ -24,16 +25,7 @@ import ( "github.com/spf13/cobra" ) -func pluginCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "plugin", - Short: "Manage plugins", - } - cmd.AddCommand(pluginListCommand()) - return cmd -} - -func pluginListCommand() *cobra.Command { +func listCommand() *cobra.Command { return &cobra.Command{ Use: "list [flags]", Aliases: []string{"ls"}, @@ -53,6 +45,11 @@ func listPlugins(command *cobra.Command) error { mgr := plugin.NewCLIManager(dir.PluginFS()) pluginNames, err := mgr.List(command.Context()) if err != nil { + var errPluginDirWalk plugin.PluginDirectoryWalkError + if errors.As(err, &errPluginDirWalk) { + pluginDir, _ := dir.PluginFS().SysPath("") + return fmt.Errorf("%w.\nPlease ensure that the current user has permission to access the plugin directory: %s", errPluginDirWalk, pluginDir) + } return err } diff --git a/cmd/notation/plugin/uninstall.go b/cmd/notation/plugin/uninstall.go new file mode 100644 index 000000000..6c20c1106 --- /dev/null +++ b/cmd/notation/plugin/uninstall.go @@ -0,0 +1,107 @@ +// Copyright The Notary Project 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. + +package plugin + +import ( + "context" + "errors" + "fmt" + "os" + + "github.com/notaryproject/notation-go/dir" + "github.com/notaryproject/notation-go/plugin" + "github.com/notaryproject/notation/cmd/notation/internal/cmdutil" + "github.com/notaryproject/notation/internal/cmd" + "github.com/spf13/cobra" +) + +type pluginUninstallOpts struct { + cmd.LoggingFlagOpts + pluginName string + confirmed bool +} + +func uninstallCommand(opts *pluginUninstallOpts) *cobra.Command { + if opts == nil { + opts = &pluginUninstallOpts{} + } + command := &cobra.Command{ + Use: "uninstall [flags] ", + Aliases: []string{"remove", "rm"}, + Short: "Uninstall a plugin", + Long: `Uninstall a plugin + +Example - Uninstall plugin: + notation plugin uninstall wabbit-plugin +`, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("plugin name is required") + } + if len(args) > 1 { + return errors.New("only one plugin can be removed at a time") + } + opts.pluginName = args[0] + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + return uninstallPlugin(cmd, opts) + }, + } + + opts.LoggingFlagOpts.ApplyFlags(command.Flags()) + command.Flags().BoolVarP(&opts.confirmed, "yes", "y", false, "do not prompt for confirmation") + return command +} + +func uninstallPlugin(command *cobra.Command, opts *pluginUninstallOpts) error { + // set logger + ctx := opts.LoggingFlagOpts.InitializeLogger(command.Context()) + pluginName := opts.pluginName + exist, err := checkPluginExistence(ctx, pluginName) + if err != nil { + return fmt.Errorf("failed to check plugin existence: %w", err) + } + if !exist { + return fmt.Errorf("unable to find plugin %s.\nTo view a list of installed plugins, use `notation plugin list`", pluginName) + } + // core process + prompt := fmt.Sprintf("Are you sure you want to uninstall plugin %q?", pluginName) + confirmed, err := cmdutil.AskForConfirmation(os.Stdin, prompt, opts.confirmed) + if err != nil { + return fmt.Errorf("failed when asking for confirmation: %w", err) + } + if !confirmed { + return nil + } + mgr := plugin.NewCLIManager(dir.PluginFS()) + if err := mgr.Uninstall(ctx, pluginName); err != nil { + return fmt.Errorf("failed to uninstall plugin %s: %w", pluginName, err) + } + fmt.Printf("Successfully uninstalled plugin %s\n", pluginName) + return nil +} + +// checkPluginExistence returns true if plugin exists in the system +func checkPluginExistence(ctx context.Context, pluginName string) (bool, error) { + mgr := plugin.NewCLIManager(dir.PluginFS()) + _, err := mgr.Get(ctx, pluginName) + if err != nil { + if errors.Is(err, os.ErrNotExist) { // plugin does not exist + return false, nil + } + return false, err + } + return true, nil +} diff --git a/cmd/notation/registry.go b/cmd/notation/registry.go index 2526e144a..d4c748a36 100644 --- a/cmd/notation/registry.go +++ b/cmd/notation/registry.go @@ -18,20 +18,17 @@ import ( "errors" "fmt" "net" - "net/http" "github.com/notaryproject/notation-go/log" notationregistry "github.com/notaryproject/notation-go/registry" "github.com/notaryproject/notation/cmd/notation/internal/experimental" notationauth "github.com/notaryproject/notation/internal/auth" - "github.com/notaryproject/notation/internal/trace" - "github.com/notaryproject/notation/internal/version" + "github.com/notaryproject/notation/internal/httputil" "github.com/notaryproject/notation/pkg/configutil" - credentials "github.com/oras-project/oras-credentials-go" - "github.com/sirupsen/logrus" "oras.land/oras-go/v2/registry" "oras.land/oras-go/v2/registry/remote" "oras.land/oras-go/v2/registry/remote/auth" + "oras.land/oras-go/v2/registry/remote/credentials" ) // inputType denotes the user input type @@ -74,9 +71,11 @@ func getRemoteRepository(ctx context.Context, opts *SecureFlagOpts, reference st logger := log.GetLogger(ctx) ref, err := registry.ParseReference(reference) if err != nil { - return nil, err + return nil, fmt.Errorf("%q: %w. Expecting /: or /@", reference, err) + } + if ref.Reference == "" { + return nil, fmt.Errorf("%q: invalid reference: no tag or digest. Expecting /: or /@", reference) } - // generate notation repository remoteRepo, err := getRepositoryClient(ctx, opts, ref) if err != nil { @@ -120,19 +119,6 @@ func getRegistryLoginClient(ctx context.Context, opts *SecureFlagOpts, serverAdd return reg, nil } -func setHttpDebugLog(ctx context.Context, authClient *auth.Client) { - if logrusLog, ok := log.GetLogger(ctx).(*logrus.Logger); ok && logrusLog.Level != logrus.DebugLevel { - return - } - if authClient.Client == nil { - authClient.Client = http.DefaultClient - } - if authClient.Client.Transport == nil { - authClient.Client.Transport = http.DefaultTransport - } - authClient.Client.Transport = trace.NewTransport(authClient.Client.Transport) -} - // getAuthClient returns an *auth.Client and a bool indicating if the registry // is insecure. // @@ -155,12 +141,7 @@ func getAuthClient(ctx context.Context, opts *SecureFlagOpts, ref registry.Refer } // build authClient - authClient := &auth.Client{ - Cache: auth.NewCache(), - ClientID: "notation", - } - authClient.SetUserAgent("notation/" + version.GetVersion()) - setHttpDebugLog(ctx, authClient) + authClient := httputil.NewAuthClient(ctx, nil) if !withCredential { return authClient, insecureRegistry, nil } diff --git a/cmd/notation/registry_test.go b/cmd/notation/registry_test.go index 3566dac1a..5d5526a1f 100644 --- a/cmd/notation/registry_test.go +++ b/cmd/notation/registry_test.go @@ -33,7 +33,7 @@ func TestRegistry_getRemoteRepositoryWithReferrersAPISupported(t *testing.T) { t.Fatal("failed to enable experimental") } ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest { + if r.Method == http.MethodGet && r.URL.Path == "/v2/test/v1/referrers/"+zeroDigest { w.WriteHeader(http.StatusOK) w.Write([]byte(`{ "test": "TEST" }`)) return @@ -49,7 +49,7 @@ func TestRegistry_getRemoteRepositoryWithReferrersAPISupported(t *testing.T) { secureOpts := SecureFlagOpts{ InsecureRegistry: true, } - _, err = getRemoteRepository(context.Background(), &secureOpts, uri.Host+"/test", true) + _, err = getRemoteRepository(context.Background(), &secureOpts, uri.Host+"/test:v1", true) if err != nil { t.Errorf("getRemoteRepository() expected nil error, but got error: %v", err) } @@ -61,7 +61,7 @@ func TestRegistry_getRemoteRepositoryWithReferrersAPINotSupported(t *testing.T) t.Fatal("failed to enable experimental") } ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest { + if r.Method == http.MethodGet && r.URL.Path == "/v2/test/v1/referrers/"+zeroDigest { w.WriteHeader(http.StatusNotFound) return } @@ -76,7 +76,7 @@ func TestRegistry_getRemoteRepositoryWithReferrersAPINotSupported(t *testing.T) secureOpts := SecureFlagOpts{ InsecureRegistry: true, } - _, err = getRemoteRepository(context.Background(), &secureOpts, uri.Host+"/test", true) + _, err = getRemoteRepository(context.Background(), &secureOpts, uri.Host+"/test:v1", true) if err != nil { t.Errorf("getRemoteRepository() expected nil error, but got error: %v", err) } @@ -84,7 +84,7 @@ func TestRegistry_getRemoteRepositoryWithReferrersAPINotSupported(t *testing.T) func TestRegistry_getRemoteRepositoryWithReferrersTagSchema(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest { + if r.Method == http.MethodGet && r.URL.Path == "/v2/test/v1/referrers/"+zeroDigest { w.WriteHeader(http.StatusOK) w.Write([]byte(`{ "test": "TEST" }`)) return @@ -100,7 +100,7 @@ func TestRegistry_getRemoteRepositoryWithReferrersTagSchema(t *testing.T) { secureOpts := SecureFlagOpts{ InsecureRegistry: true, } - _, err = getRemoteRepository(context.Background(), &secureOpts, uri.Host+"/test", false) + _, err = getRemoteRepository(context.Background(), &secureOpts, uri.Host+"/test:v1", false) if err != nil { t.Errorf("getRemoteRepository() expected nil error, but got error: %v", err) } diff --git a/cmd/notation/sign.go b/cmd/notation/sign.go index 784647695..5a1c5c8f7 100644 --- a/cmd/notation/sign.go +++ b/cmd/notation/sign.go @@ -90,7 +90,7 @@ Example - [Experimental] Sign an OCI artifact identified by a tag and referenced Long: longMessage, Args: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { - return errors.New("missing reference") + return errors.New("missing reference to the artifact: use `notation sign --help` to see what parameters are required") } opts.reference = args[0] return nil diff --git a/cmd/notation/verify.go b/cmd/notation/verify.go index cfad73159..2ac0b9f48 100644 --- a/cmd/notation/verify.go +++ b/cmd/notation/verify.go @@ -16,12 +16,14 @@ package main import ( "errors" "fmt" + "io/fs" "os" "reflect" "github.com/notaryproject/notation-go" "github.com/notaryproject/notation-go/verifier" "github.com/notaryproject/notation-go/verifier/trustpolicy" + "github.com/notaryproject/notation-go/verifier/truststore" "github.com/notaryproject/notation/cmd/notation/internal/experimental" "github.com/notaryproject/notation/internal/cmd" "github.com/notaryproject/notation/internal/ioutil" @@ -73,7 +75,7 @@ Example - [Experimental] Verify a signature on an OCI artifact identified by a t Long: longMessage, Args: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { - return errors.New("missing reference") + return errors.New("missing reference to the artifact: use `notation verify --help` to see what parameters are required") } opts.reference = args[0] return nil @@ -157,6 +159,24 @@ func checkVerificationFailure(outcomes []*notation.VerificationOutcome, printOut // write out on failure if err != nil || len(outcomes) == 0 { if err != nil { + var errTrustStore truststore.TrustStoreError + if errors.As(err, &errTrustStore) { + if errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("%w. Use command 'notation cert add' to create and add trusted certificates to the trust store", errTrustStore) + } else { + return fmt.Errorf("%w. %w", errTrustStore, errTrustStore.InnerError) + } + } + + var errCertificate truststore.CertificateError + if errors.As(err, &errCertificate) { + if errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("%w. Use command 'notation cert add' to create and add trusted certificates to the trust store", errCertificate) + } else { + return fmt.Errorf("%w. %w", errCertificate, errCertificate.InnerError) + } + } + var errorVerificationFailed notation.ErrorVerificationFailed if !errors.As(err, &errorVerificationFailed) { return fmt.Errorf("signature verification failed: %w", err) diff --git a/go.mod b/go.mod index 291f509fc..2f677354b 100644 --- a/go.mod +++ b/go.mod @@ -1,31 +1,31 @@ module github.com/notaryproject/notation -go 1.20 +go 1.21 require ( - github.com/notaryproject/notation-core-go v1.0.0 - github.com/notaryproject/notation-go v1.0.0 + github.com/notaryproject/notation-core-go v1.0.2 + github.com/notaryproject/notation-go v1.1.1-0.20240201073933-4606472ebdcb github.com/opencontainers/go-digest v1.0.0 - github.com/opencontainers/image-spec v1.1.0-rc5 - github.com/oras-project/oras-credentials-go v0.3.1 + github.com/opencontainers/image-spec v1.1.0 github.com/sirupsen/logrus v1.9.3 - github.com/spf13/cobra v1.7.0 + github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 - golang.org/x/term v0.13.0 - oras.land/oras-go/v2 v2.3.0 + golang.org/x/term v0.17.0 + oras.land/oras-go/v2 v2.4.0 ) require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect - github.com/fxamacker/cbor/v2 v2.4.0 // indirect - github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect - github.com/go-ldap/ldap/v3 v3.4.5 // indirect + github.com/fxamacker/cbor/v2 v2.5.0 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect + github.com/go-ldap/ldap/v3 v3.4.6 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/google/uuid v1.3.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/veraison/go-cose v1.1.0 // indirect github.com/x448/float16 v0.8.4 // indirect - golang.org/x/crypto v0.11.0 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.13.0 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.17.0 // indirect ) diff --git a/go.sum b/go.sum index ba155f59e..1de953474 100644 --- a/go.sum +++ b/go.sum @@ -2,37 +2,37 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= -github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= -github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= -github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= -github.com/go-ldap/ldap/v3 v3.4.5 h1:ekEKmaDrpvR2yf5Nc/DClsGG9lAmdDixe44mLzlW5r8= -github.com/go-ldap/ldap/v3 v3.4.5/go.mod h1:bMGIq3AGbytbaMwf8wdv5Phdxz0FWHTIYMSzyrYgnQs= +github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= +github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= +github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= +github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A= +github.com/go-ldap/ldap/v3 v3.4.6/go.mod h1:IGMQANNtxpsOzj7uUAMjpGBaOVTC4DYyIy8VsTdxmtc= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/notaryproject/notation-core-go v1.0.0 h1:FgOAihtFW4XU9JYyTzItg1xW3OaN4eCasw5Bp00Ydu4= -github.com/notaryproject/notation-core-go v1.0.0/go.mod h1:eoHFJ2e6b31GZO9hckCms5kfXvHLTySvJ1QwRLB9ZCk= -github.com/notaryproject/notation-go v1.0.0 h1:pH+0NVmZu1IhE8zUhK9Oxna3OlHNdy+crNntnuCiThs= -github.com/notaryproject/notation-go v1.0.0/go.mod h1:NpfUnDt94vLSCJ8fAWplgTbf3fmq3JLSEnjDFl7j16U= +github.com/notaryproject/notation-core-go v1.0.2 h1:VEt+mbsgdANd9b4jqgmx2C7U0DmwynOuD2Nhxh3bANw= +github.com/notaryproject/notation-core-go v1.0.2/go.mod h1:2HkQzUwg08B3x9oVIztHsEh7Vil2Rj+tYgxH+JObLX4= +github.com/notaryproject/notation-go v1.1.1-0.20240201073933-4606472ebdcb h1:OVkHyQD0O8hTsuDPzdpgdteHDN9ormV5M3/pi9ka4II= +github.com/notaryproject/notation-go v1.1.1-0.20240201073933-4606472ebdcb/go.mod h1:v0e8Y7gEzTtx7aNw3tG6da7atr59JRdePVMMkTGNXzA= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= -github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= -github.com/oras-project/oras-credentials-go v0.3.1 h1:sfGqZ8sjPifEaOtjHOQTPr8D+Tql4bpw58Dd9wjmm9w= -github.com/oras-project/oras-credentials-go v0.3.1/go.mod h1:fFCebDQo0Do+gnM96uV9YUnRay0pwuRQupypvofsp4s= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -48,23 +48,23 @@ github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcY github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -72,20 +72,23 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -95,5 +98,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -oras.land/oras-go/v2 v2.3.0 h1:lqX1aXdN+DAmDTKjiDyvq85cIaI4RkIKp/PghWlAGIU= -oras.land/oras-go/v2 v2.3.0/go.mod h1:GeAwLuC4G/JpNwkd+bSZ6SkDMGaaYglt6YK2WvZP7uQ= +oras.land/oras-go/v2 v2.4.0 h1:i+Wt5oCaMHu99guBD0yuBjdLvX7Lz8ukPbwXdR7uBMs= +oras.land/oras-go/v2 v2.4.0/go.mod h1:osvtg0/ClRq1KkydMAEu/IxFieyjItcsQ4ut4PPF+f8= diff --git a/internal/auth/credentials.go b/internal/auth/credentials.go index d5de0ae37..9f04a6eeb 100644 --- a/internal/auth/credentials.go +++ b/internal/auth/credentials.go @@ -17,7 +17,7 @@ import ( "fmt" "github.com/notaryproject/notation-go/dir" - credentials "github.com/oras-project/oras-credentials-go" + "oras.land/oras-go/v2/registry/remote/credentials" ) // NewCredentialsStore returns a new credentials store from the settings in the diff --git a/internal/cmd/options.go b/internal/cmd/options.go index 614aa7d86..789dc5cf4 100644 --- a/internal/cmd/options.go +++ b/internal/cmd/options.go @@ -18,10 +18,10 @@ import ( "github.com/notaryproject/notation-go/log" "github.com/notaryproject/notation/internal/trace" - credentialstrace "github.com/oras-project/oras-credentials-go/trace" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/pflag" + credentialstrace "oras.land/oras-go/v2/registry/remote/credentials/trace" ) // SignerFlagOpts cmd opts for using cmd.GetSigner diff --git a/internal/httputil/client.go b/internal/httputil/client.go new file mode 100644 index 000000000..ee0324ed5 --- /dev/null +++ b/internal/httputil/client.go @@ -0,0 +1,35 @@ +// Copyright The Notary Project 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. + +package httputil + +import ( + "context" + "net/http" + + "github.com/notaryproject/notation/internal/trace" + "github.com/notaryproject/notation/internal/version" + "oras.land/oras-go/v2/registry/remote/auth" +) + +// NewAuthClient returns an *auth.Client +func NewAuthClient(ctx context.Context, httpClient *http.Client) *auth.Client { + client := &auth.Client{ + Client: httpClient, + Cache: auth.NewCache(), + ClientID: "notation", + } + client.SetUserAgent("notation/" + version.GetVersion()) + trace.SetHTTPDebugLog(ctx, client) + return client +} diff --git a/internal/osutil/file.go b/internal/osutil/file.go index 70fdfdf8c..06f69792a 100644 --- a/internal/osutil/file.go +++ b/internal/osutil/file.go @@ -14,13 +14,21 @@ package osutil import ( + "crypto/sha256" + "encoding/hex" "fmt" "io" "io/fs" + "net/http" "os" "path/filepath" + "strings" ) +// MaxFileBytes is the maximum file bytes. +// When used, the value should be strictly less than this number. +var MaxFileBytes int64 = 256 * 1024 * 1024 // 256 MiB + // WriteFile writes to a path with all parent directories created. func WriteFile(path string, data []byte) error { if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { @@ -72,8 +80,8 @@ func CopyToDir(src, dst string) (int64, error) { if err := os.MkdirAll(dst, 0700); err != nil { return 0, err } - certFile := filepath.Join(dst, filepath.Base(src)) - destination, err := os.Create(certFile) + dstFile := filepath.Join(dst, filepath.Base(src)) + destination, err := os.Create(dstFile) if err != nil { return 0, err } @@ -94,3 +102,55 @@ func IsRegularFile(path string) (bool, error) { return fileStat.Mode().IsRegular(), nil } + +// CopyFromReaderToDir copies file from src to dst where dst is the destination +// file path. +func CopyFromReaderToDir(src io.Reader, dst string, perm fs.FileMode) error { + dstFile, err := os.Create(dst) + if err != nil { + return err + } + if _, err := io.Copy(dstFile, src); err != nil { + dstFile.Close() + return err + } + if err := dstFile.Chmod(perm); err != nil { + dstFile.Close() + return err + } + return dstFile.Close() +} + +// DetectFileType returns a file's content type given path +func DetectFileType(path string) (string, error) { + rc, err := os.Open(path) + if err != nil { + return "", err + } + defer rc.Close() + lr := io.LimitReader(rc, 512) + header, err := io.ReadAll(lr) + if err != nil { + return "", err + } + return http.DetectContentType(header), nil +} + +// ValidateSHA256Sum returns nil if SHA256 of file at path equals to checksum. +func ValidateSHA256Sum(path string, checksum string) error { + rc, err := os.Open(path) + if err != nil { + return err + } + defer rc.Close() + sha256Hash := sha256.New() + if _, err := io.Copy(sha256Hash, rc); err != nil { + return err + } + sha256sum := sha256Hash.Sum(nil) + enc := hex.EncodeToString(sha256sum[:]) + if !strings.EqualFold(enc, checksum) { + return fmt.Errorf("plugin SHA-256 checksum does not match user input. Expecting %s", checksum) + } + return nil +} diff --git a/internal/osutil/file_test.go b/internal/osutil/file_test.go index 6bd70e541..59226f9b1 100644 --- a/internal/osutil/file_test.go +++ b/internal/osutil/file_test.go @@ -15,7 +15,6 @@ package osutil import ( "bytes" - "io/ioutil" "os" "path/filepath" "runtime" @@ -23,11 +22,11 @@ import ( ) func validFileContent(t *testing.T, filename string, content []byte) { - b, err := ioutil.ReadFile(filename) + b, err := os.ReadFile(filename) if err != nil { t.Fatal(err) } - if bytes.Compare(content, b) != 0 { + if !bytes.Equal(content, b) { t.Fatal("file content is not correct") } } @@ -260,3 +259,13 @@ func TestCopyToDir(t *testing.T) { validFileContent(t, filepath.Join(destDir, "file.txt"), data) }) } + +func TestValidateChecksum(t *testing.T) { + expectedErrorMsg := "plugin SHA-256 checksum does not match user input. Expecting abcd123" + if err := ValidateSHA256Sum("./testdata/test", "abcd123"); err == nil || err.Error() != expectedErrorMsg { + t.Fatalf("expected err %s, but got %v", expectedErrorMsg, err) + } + if err := ValidateSHA256Sum("./testdata/test", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); err != nil { + t.Fatalf("expected nil err, but got %v", err) + } +} diff --git a/internal/osutil/testdata/test b/internal/osutil/testdata/test new file mode 100644 index 000000000..e69de29bb diff --git a/internal/trace/context.go b/internal/trace/context.go index 50d59a26a..8563d73ee 100644 --- a/internal/trace/context.go +++ b/internal/trace/context.go @@ -1,3 +1,16 @@ +// Copyright The Notary Project 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. + // Copied and adapted from oras (https://github.com/oras-project/oras) /* Copyright The ORAS Authors. diff --git a/internal/trace/context_test.go b/internal/trace/context_test.go index 3d640b968..14e35976d 100644 --- a/internal/trace/context_test.go +++ b/internal/trace/context_test.go @@ -1,3 +1,16 @@ +// Copyright The Notary Project 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. + // Copied and adapted from oras (https://github.com/oras-project/oras) /* Copyright The ORAS Authors. diff --git a/internal/trace/transport.go b/internal/trace/transport.go index ec386bcd7..a83ebda19 100644 --- a/internal/trace/transport.go +++ b/internal/trace/transport.go @@ -1,3 +1,16 @@ +// Copyright The Notary Project 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. + // Copied and adapted from oras (https://github.com/oras-project/oras) /* Copyright The ORAS Authors. @@ -17,10 +30,13 @@ limitations under the License. package trace import ( + "context" "net/http" "strings" "github.com/notaryproject/notation-go/log" + "github.com/sirupsen/logrus" + "oras.land/oras-go/v2/registry/remote/auth" ) // Transport is an http.RoundTripper that keeps track of the in-flight @@ -69,3 +85,17 @@ func logHeader(header http.Header, e log.Logger) { e.Debugf(" Empty header") } } + +// SetHTTPDebugLog sets up http debug log with logrus.Logger +func SetHTTPDebugLog(ctx context.Context, authClient *auth.Client) { + if logrusLog, ok := log.GetLogger(ctx).(*logrus.Logger); !ok || logrusLog.Level != logrus.DebugLevel { + return + } + if authClient.Client == nil { + authClient.Client = &http.Client{} + } + if authClient.Client.Transport == nil { + authClient.Client.Transport = http.DefaultTransport + } + authClient.Client.Transport = NewTransport(authClient.Client.Transport) +} diff --git a/internal/trace/transport_test.go b/internal/trace/transport_test.go index 601f93072..58e6dfbc2 100644 --- a/internal/trace/transport_test.go +++ b/internal/trace/transport_test.go @@ -1,3 +1,16 @@ +// Copyright The Notary Project 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. + // Copied and adapted from oras (https://github.com/oras-project/oras) /* Copyright The ORAS Authors. diff --git a/internal/version/version.go b/internal/version/version.go index d0956a11d..2f7749b48 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -15,7 +15,7 @@ package version var ( // Version shows the current notation version, optionally with pre-release. - Version = "v1.0.0" + Version = "v1.1.0" // BuildMetadata stores the build metadata. // diff --git a/specs/commandline/blob.md b/specs/commandline/blob.md new file mode 100644 index 000000000..1fdb76ec6 --- /dev/null +++ b/specs/commandline/blob.md @@ -0,0 +1,500 @@ +# notation blob + +## Description + +Use `notation blob` command to sign, verify, and inspect signatures associated with arbitrary blobs. Notation can sign and verify any arbitrary bag of bits like zip files, documents, executables, etc. When a user signs a blob, `notation` produces a detached signature, which the user can transport/distribute using any medium that the user prefers along with the original blob. On the verification side, Notation can verify the blob's signature and assert that the blob has not been tampered with during its transmission. + +Users can use `notation blob policy` command to manage trust policies for verifying a blob signature. The `notation blob policy` command provides a user-friendly way to manage trust policies for signed blobs. It allows users to show trust policy configuration, import/export a trust policy configuration file from/to a JSON file. For more details, see [blob trust policy specification and examples](https://github.com/notaryproject/specifications/blob/main/specs/trust-store-trust-policy.md#blob-trust-policy). + +The sample trust policy file (`trustpolicy.blob.json`) for verifying signed blobs is shown below. This sample trust policy file, contains three different statements for different usecases: + +- The Policy named "wabbit-networks-policy" is for verifying blob artifacts signed by Wabbit Networks. +- Policy named "skip-verification-policy" is for skipping verification on blob artifacts. +- Policy "global-verification-policy" is for auditing verification results when user does not provide `--policy-name` argument in `notation blob verify` command. + +```jsonc +{ + "version": "1.0", + "trustPolicies": [ + { + "name": "wabbit-networks-policy", + "signatureVerification": { + "level": "strict" + }, + "trustStores": [ + "ca:wabbit-networks", + ], + "trustedIdentities": [ + "x509.subject: C=US, ST=WA, L=Seattle, O=wabbit-networks.io, OU=Security Tools" + ] + }, + { + "name": "skip-verification-policy", + "signatureVerification": { + "level" : "skip" + } + }, + { + "name": "global-verification-policy", + "globalPolicy": true, + "signatureVerification": { + "level" : "audit" + }, + "trustStores": ["ca:acme-rockets"], + "trustedIdentities": ["*"] + } + ] +} +``` + +## Outline + +### notation blob command + +```text +Sign, inspect, and verify signatures and configure trust policies. + +Usage: + notation blob [command] + +Available Commands: + inspect inspect a signature associated with a blob + policy manage trust policy configuration for signed blobs + sign produce a detached signature for a given blob + verify verify a signature associated with a blob + +Flags: + -h, --help help for blob +``` + +### notation blob sign + +```text +Produce a signature for a given blob. A detached signature file will be written to the currently working directory with blob file name + ".sig" + signature format as the file extension. For example, signature file name for "myBlob.bin" will be "myBlob.bin.sig.jws" for JWS signature format or "myBlob.bin.sig.cose" for COSE signature format. + +Usage: + notation blob sign [flags] + +Flags: + --signature-directory string optional path where the blob signature needs to be placed (default: currently working directory) + --media-type string optional media type of the blob (default: "application/octet-stream") + -e, --expiry duration optional expiry that provides a "best by use" time for the blob. The duration is specified in minutes(m) and/or hours(h). For example: 12h, 30m, 3h20m + --id string key id (required if --plugin is set). This is mutually exclusive with the --key flag + -k, --key string signing key name, for a key previously added to notation's key list. This is mutually exclusive with the --id and --plugin flags + --plugin string signing plugin name. This is mutually exclusive with the --key flag + --plugin-config stringArray {key}={value} pairs that are passed as it is to a plugin, refer plugin's documentation to set appropriate values. + --signature-format string signature envelope format, options: "jws", "cose" (default "jws") + -m, --user-metadata stringArray {key}={value} pairs that are added to the signature payload + -d, --debug debug mode + -v, --verbose verbose mode + -h, --help help for sign +``` + +### notation blob inspect + +```text +Inspect a signature associated with a blob + +Usage: + notation blob inspect [flags] + +Flags: + -o, --output string output format, options: 'json', 'text' (default "text") + -d, --debug debug mode + -v, --verbose verbose mode + -h, --help help for inspect +``` + +### notation blob policy + +```text +Manage trust policy configuration for arbitrary blob signature verification. + +Usage: + notation blob policy [command] + +Available Commands: + import import trust policy configuration from a JSON file + show show trust policy configuration + +Flags: + -h, --help help for policy +``` + +### notation blob policy import + +```text +Import blob trust policy configuration from a JSON file + +Usage: + notation blob policy import [flags] + +Flags: + --force override the existing trust policy configuration, never prompt + -h, --help help for import +``` + +### notation blob policy show + +```text +Show blob trust policy configuration + +Usage: + notation blob policy show [flags] + +Flags: + -h, --help help for show +``` + +### notation blob verify + +```text +Verify a signature associated with a blob + +Usage: + notation blob verify [flags] --signature + +Flags: + --signature string location of the blob signature file + --media-type string optional media type of the blob to verify + --policy-name string optional policy name to verify against. If not provided, notation verifies against the global policy if it exists. + -m, --user-metadata stringArray user defined {key}={value} pairs that must be present in the signature for successful verification if provided + --plugin-config stringArray {key}={value} pairs that are passed as it is to a plugin, if the verification is associated with a verification plugin, refer plugin documentation to set appropriate values + -o, --output string output format, options: 'json', 'text' (default "text") + -d, --debug debug mode + -v, --verbose verbose mode + -h, --help help for inspect +``` + +## Usage + +## Produce blob signatures + +### Sign a blob by adding a new key + +```shell +# Prerequisites: +# - A signing plugin is installed. See plugin documentation (https://github.com/notaryproject/notaryproject/blob/main/specs/plugin-extensibility.md) for more details. +# - Configure the signing plugin as instructed by plugin vendor. + +# Add a default signing key referencing the remote key identifier, and the plugin associated with it. +notation key add --default --name --plugin --id + +# sign a blob +notation blob sign /tmp/my-blob.bin +``` + +An example for a successful signing: + +```console +$ notation blob sign /tmp/my-blob.bin +Successfully signed /tmp/my-blob.bin +Signature file written to /absolute/path/to/cwd/my-blob.bin.sig.jws +``` + +### Sign a blob by generating the signature in a particular directory +```console +$ notation blob sign --signature-directory /tmp/xyz/sigs /tmp/my-blob.bin +Successfully signed /tmp/my-blob.bin +Signature file written to /tmp/xyz/sigs/my-blob.bin.sig.jws +``` + +### Sign a blob using a relative path +```console +$ notation blob sign ./relative/path/my-blob.bin +Successfully signed ./relative/path/my-blob.bin +Signature file written to /absolute/path/to/cwd/my-blob.bin.sig.jws +``` + +### Sign a blob with a plugin + +```shell +notation blob sign --plugin --id /tmp/my-blob.bin +``` + +### Sign a blob using COSE signature format + +```console +# Prerequisites: +# A default signing key is configured using CLI "notation key" + +# Use option "--signature-format" to set the signature format to COSE. +$ notation blob sign --signature-format cose /tmp/my-blob.bin +Successfully signed /tmp/my-blob.bin +Signature file written to /absolute/path/to/cwd/my-blob.bin.sig.cose +``` + +### Sign a blob using the default signing key + +```shell +# Prerequisites: +# A default signing key is configured using CLI "notation key" + +notation blob sign /tmp/my-blob.bin +``` + +### Sign a blob with user metadata + +```shell +# Prerequisites: +# A default signing key is configured using CLI "notation key" + +# sign a blob and add user-metadata io.wabbit-networks.buildId=123 to the payload +notation blob sign --user-metadata io.wabbit-networks.buildId=123 /tmp/my-blob.bin + +# sign a blob and add user-metadata io.wabbit-networks.buildId=123 and io.wabbit-networks.buildTime=1672944615 to the payload +notation blob sign --user-metadata io.wabbit-networks.buildId=123 --user-metadata io.wabbit-networks.buildTime=1672944615 /tmp/my-blob.bin +``` + +### Sign a blob and specify the media type for the blob + +```shell +notation blob sign --media-type /tmp/my-blob.bin +``` + +### Sign a blob and specify the signature expiry duration, for example 24 hours + +```shell +notation blob sign --expiry 24h /tmp/my-blob.bin +``` + +### Sign a blob using a specified signing key + +```shell +# List signing keys to get the key name +notation key list + +# Sign a container image using the specified key name +notation blob sign --key /tmp/my-blob.bin +``` + +## Inspect blob signatures + +### Display details of the given blob signature and its associated certificate properties + + +```text +notation blob inspect [flags] /tmp/my-blob.bin.sig.jws +``` + +### Inspect the given blob signature + +```shell +# Prerequisites: Signatures is produced by notation blob sign command +notation blob inspect /tmp/my-blob.bin.sig.jws +``` + +An example output: +```shell +/tmp/my-blob.bin.sig.jws + ├── signature algorithm: RSASSA-PSS-SHA-256 + ├── signature envelope type: jws + ├── signed attributes + │ ├── content type: application/vnd.cncf.notary.payload.v1+json + │ ├── signing scheme: notary.signingAuthority.x509 + │ ├── signing time: Fri Jun 23 22:04:01 2023 + │ ├── expiry: Sat Jun 29 22:04:01 2024 + │ └── io.cncf.notary.verificationPlugin: com.example.nv2plugin + ├── unsigned attributes + │ ├── io.cncf.notary.timestampSignature: + │ └── io.cncf.notary.signingAgent: notation/1.0.0 + ├── certificates + │ ├── SHA256 fingerprint: b13a843be16b1f461f08d61c14f3eab7d87c073570da077217541a7eb31c084d + │ │ ├── issued to: wabbit-com Software + │ │ ├── issued by: wabbit-com Software Root Certificate Authority + │ │ └── expiry: Sun Jul 06 20:50:17 2025 + │ ├── SHA256 fingerprint: 4b9fa61d5aed0fabbc7cb8fe2efd049da57957ed44f2b98f7863ce18effd3b89 + │ │ ├── issued to: wabbit-com Software Code Signing PCA 2010 + │ │ ├── issued by: wabbit-com Software Root Certificate Authority + │ │ └── expiry: Sun Jul 06 20:50:17 2025 + │ └── SHA256 fingerprint: ea3939548ad0c0a86f164ab8b97858854238c797f30bddeba6cb28688f3f6536 + │ ├── issued to: wabbit-com Software Root Certificate Authority + │ ├── issued by: wabbit-com Software Root Certificate Authority + │ └── expiry: Sat Jun 23 22:04:01 2035 + └── signed artifact + ├── media type: application/octet-stream + ├── digest: sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + └── size: 16724 +``` + +### Inspect the given blob signature with JSON Output + +```shell +notation blob inspect -o json /tmp/my-blob.bin.sig.jws +``` + +## Import/Export trust policy configuration files + +### Import blob trust policy configuration from a JSON file + +An example of import trust policy configuration from a JSON file: + +```shell +notation blob policy import ./my_policy.json +``` + +The trust policy configuration in the JSON file should be validated according to [trust policy properties](https://github.com/notaryproject/notaryproject/specs/trust-store-trust-policy.md#blob-trust-policy). A successful message should be printed out if trust policy configuration are imported successfully. Error logs including the reason should be printed out if the importing fails. + +If there is an existing trust policy configuration, prompt for users to confirm whether discarding existing configuration or not. Users can use `--force` flag to discard existing trust policy configuration without prompt. + +### Show blob trust policies + +Use the following command to show trust policy configuration: + +```shell +notation blob policy show +``` + +Upon successful execution, the trust policy configuration is printed out to standard output. If trust policy is not configured or is malformed, users should receive an error message via standard error output, and a tip to import trust policy configuration from a JSON file. + +### Export blob trust policy configuration into a JSON file + +Users can redirect the output of command `notation blob policy show` to a JSON file. + +```shell +notation blob policy show > ./blob_trust_policy.json +``` + +### Update trust policy configuration + +The steps to update blob trust policy configuration: + +1. Export trust policy configuration into a JSON file. + + ```shell + notation blob policy show > ./blob_trust_policy.json + ``` + +2. Edit the exported JSON file "blob_trust_policy.json", update trust policy configuration and save the file. +3. Import trust policy configuration from the file. + + ```shell + notation blob policy import ./blob_trust_policy.json + ``` + +## Verify blob signatures +The `notation blob verify` command can be used to verify blob signatures. In order to verify signatures, user will need to setup a trust policy file `trustpolicy.blob.json` with Policies for blobs. Below are two examples of how a policy configuration file can be setup for verifying blob signatures. + +- The Policy named "wabbit-networks-policy" is for verifying blob artifacts signed by Wabbit Networks. +- Policy named "global-verification-policy" is for auditing verification results when user doesn't not provide `--policy-name` argument in `notation blob verify` command. + +```jsonc +{ + "version": "1.0", + "trustPolicies": [ + { + "name": "wabbit-networks-policy", + "signatureVerification": { + "level": "strict" + }, + "trustStores": [ + "ca:wabbit-networks", + ], + "trustedIdentities": [ + "x509.subject: C=US, ST=WA, L=Seattle, O=wabbit-networks.io, OU=Security Tools" + ] + }, + { + "name": "global-verification-policy", + "globalPolicy": true, + "signatureVerification": { + "level" : "audit" + }, + "trustStores": ["ca:acme-rockets"], + "trustedIdentities": ["*"] + } + ] +} +``` + +### Verify the signature of a blob + +Configure trust store and trust policy properly before using `notation blob verify` command. + +```shell + +# Prerequisites: Blob and its associated signature is present on the filesystem. +# Configure trust store by adding a certificate file into trust store named "wabbit-network" of type "ca" +notation certificate add --type ca --store wabbit-networks wabbit-networks.crt + +# Setup the trust policy in a JSON file named "trustpolicy.blob.json" under directory "{NOTATION_CONFIG}". + +# Verify the blob signature +notation blob verify --signature /tmp/my-blob.bin.sig.jws /tmp/my-blob.bin +``` + +An example of output messages for a successful verification: + +```text +Successfully verified signature /tmp/my-blob.bin.sig.jws +``` + +### Verify the signature with user metadata + +Use the `--user-metadata` flag to verify that provided key-value pairs are present in the payload of the valid signature. + +```shell +# Verify the signature and verify that io.wabbit-networks.buildId=123 is present in the signed payload +notation blob verify --user-metadata io.wabbit-networks.buildId=123 --signature /tmp/my-blob.bin.sig.jws /tmp/my-blob.bin +``` + +An example of output messages for a successful verification: + +```text +Successfully verified signature /tmp/my-blob.bin.sig.jws + +The signature contains the following user metadata: + +KEY VALUE +io.wabbit-networks.buildId 123 +``` + +An example of output messages for an unsuccessful verification: + +```text +Error: signature verification failed: unable to find specified metadata in the given signature +``` + +### Verify the signature with media type + +Use the `--media-type` flag to verify that signature is for the provided media-type. + +```shell +# Verify the signature and verify that application/my-media-octet-stream is the media type +notation blob verify --media-type application/my-media-octet-stream --signature /tmp/my-blob.bin.sig.jws /tmp/my-blob.bin +``` + +An example of output messages for a successful verification: + +```text +Successfully verified signature /tmp/my-blob.bin.sig.jws + +The blob is of media type `application/my-media-octet-stream`. + +``` + +An example of output messages for an unsuccessful verification: + +```text +Error: Signature verification failed due to a mismatch in the blob's media type 'application/xyz' and the expected type 'application/my-media-octet-stream'. +``` + +### Verify the signature using a policy name + +Use the `--policy-name` flag to select a policy to verify the signature against. + +```shell +notation blob verify --policy-name wabbit-networks-policy --signature ./sigs/my-blob.bin.sig.jws ./blobs/my-blob.bin +``` + +An example of output messages for a successful verification: + +```text +Successfully verified signature ./sigs/my-blob.bin.sig.jws using policy `wabbit-networks-policy` + +``` +An example of output messages for an unsuccessful verification: + +```text +Error: signature verification failed for policy `wabbit-networks-policy` +``` \ No newline at end of file diff --git a/specs/commandline/inspect.md b/specs/commandline/inspect.md index 33db06563..5dc9b334d 100644 --- a/specs/commandline/inspect.md +++ b/specs/commandline/inspect.md @@ -2,7 +2,7 @@ ## Description -Use `notation inspect` command to inspect all the signatures associated with signed artifact in a human readable format. +Use `notation inspect` command to inspect all the signatures associated with artifacts stored in OCI compliant registries in a human readable format. Upon successful execution, both the digest of the signed artifact and the digests of signatures manifest along with their properties associated with the signed artifact are printed in the following format: @@ -24,10 +24,13 @@ Upon successful execution, both the digest of the signed artifact and the digest └── ``` +> [!NOTE] +> This command is for inspecting signatures associated with OCI artifacts only. Use `notation blob inspect` command for inspecting signatures associated with arbitrary blobs. + ## Outline ```text -Inspect all signatures associated with the signed artifact. +Inspect all signatures associated with a signed OCI artifact. Usage: notation inspect [flags] diff --git a/specs/commandline/list.md b/specs/commandline/list.md index 49726724c..d73be9758 100644 --- a/specs/commandline/list.md +++ b/specs/commandline/list.md @@ -2,7 +2,7 @@ ## Description -Use `notation list` to list all the signatures associated with signed artifact. +Use `notation list` to list all the signatures associated with a signed OCI artifact. `Tags` are mutable, but `Digests` uniquely and immutably identify an artifact. If a tag is used to identify a signed artifact, notation resolves the tag to the `digest` first. @@ -18,7 +18,7 @@ Upon successful execution, both the digest of the signed artifact and the digest ## Outline ```text -List all the signatures associated with signed artifact +List all the signatures associated with a signed OCI artifact Usage: notation list [flags] @@ -49,7 +49,7 @@ notation list /: An example output: ```shell -localhost:5000/net-monitor:v1 +localhost:5000/net-monitor@sha256:8456f085dd609fd12cdebc5f80b6f33f25f670a7a9a03c8fa750b8aee0c4d657 └── application/vnd.cncf.notary.signature ├── sha256:647039638efb22a021f59675c9449dd09956c981a44b82c1ff074513c2c9f273 └── sha256:6bfb3c4fd485d6810f9656ddd4fb603f0c414c5f0b175ef90eeb4090ebd9bfa1 diff --git a/specs/commandline/plugin.md b/specs/commandline/plugin.md index be34979ef..c0460ab50 100644 --- a/specs/commandline/plugin.md +++ b/specs/commandline/plugin.md @@ -2,7 +2,7 @@ ## Description -Use `notation plugin` to manage plugins. See notation [plugin documentation](https://github.com/notaryproject/notaryproject/blob/main/specs/plugin-extensibility.md) for more details. The `notation plugin` command by itself performs no action. In order to operate on a plugin, one of the subcommands must be used. +Use `notation plugin` to manage plugins. See notation [plugin documentation](https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/plugin-extensibility.md) for more details. The `notation plugin` command by itself performs no action. In order to manage notation plugins, one of the subcommands must be used. ## Outline @@ -15,9 +15,9 @@ Usage: notation plugin [command] Available Commands: + install Install a plugin list List installed plugins - install Installs a plugin - remove Removes a plugin + uninstall Uninstall a plugin Flags: -h, --help help for plugin @@ -41,51 +41,116 @@ Aliases: ### notation plugin install ```text -Installs a plugin +Install a plugin Usage: - notation plugin install [flags] + notation plugin install [flags] <--file|--url> Flags: - -h, --help help for install - -f, --force force the installation of a plugin + -d, --debug debug mode + --file install plugin from a file on file system + --force force the installation of the plugin + -h, --help help for install + --sha256sum string must match SHA256 of the plugin source, required when "--url" flag is set + --url install plugin from an HTTPS URL. The plugin download timeout is 10m0s + -v, --verbose verbose mode Aliases: install, add ``` -### notation plugin remove +### notation plugin uninstall ```text -Removes a plugin +Uninstall a plugin Usage: - notation plugin remove [flags] + notation plugin uninstall [flags] Flags: + -d, --debug debug mode -h, --help help for remove - + -v, --verbose verbose mode + -y, --yes do not prompt for confirmation Aliases: - remove, rm, uninstall, delete + uninstall, remove, rm ``` ## Usage -### Install a plugin +## Install a plugin + +### Install a plugin from file system + +Install a Notation plugin from the host file system. `.zip`, `.tar.gz`, and `single plugin executable file` formats are supported. In this scenario, SHA-256 checksum validation is optional. ```shell -notation plugin install +$ notation plugin install --file +``` + +Upon successful execution, the plugin is copied to Notation's plugin directory. If the plugin directory does not exist, it will be created. The name and version of the installed plugin are displayed as follows. + +```console +Successfully installed plugin , version +``` + +If the entered plugin checksum digest doesn't match the published checksum, Notation will return an error message and will not start installation. + +```console +Error: plugin installation failed: plugin sha256sum does not match user input. Expecting +``` + +If the plugin version is higher than the existing plugin, Notation will start installation and overwrite the existing plugin. + +```console +Successfully updated plugin from version to +``` + +If the plugin version is equal to the existing plugin, Notation will not start installation and return the following message. Users can use a flag `--force` to skip plugin version check and force the installation. + +```console +Error: plugin installation failed: plugin with version already exists. +``` + +If the plugin version is lower than the existing plugin, Notation will return an error message and will not start installation. Users can use a flag `--force` to skip plugin version check and force the installation. + +```console +Error: plugin installation failed: failed to install plugin . The installing plugin version is lower than the existing plugin version . +It is not recommended to install an older version. To force the installation, use the "--force" option. ``` +### Install a plugin from URL -Upon successful execution, the plugin is copied to plugins directory and name+version of plugin is displayed. If the plugin directory does not exist, it will be created. When an existing plugin is detected, the versions are compared and if the existing plugin is a lower version then it is replaced by the newer version. +Install a Notation plugin from a URL. Notation only supports HTTPS URL, which means that the URL must start with "https://". The URL MUST point to a resource in `.zip`, `.tar.gz`, or `single plugin executable file` format. In this scenario, the SHA-256 checksum of the resource MUST be provided. + +```shell +$ notation plugin install --sha256sum --url +``` ### Uninstall a plugin ```shell -notation plugin remove +notation plugin uninstall +``` + +Upon successful execution, the plugin is uninstalled from the plugin directory. + +```shell +Are you sure you want to uninstall plugin ""? [y/n] y +Successfully uninstalled plugin +``` + +Uninstall the plugin without prompt for confirmation. + +```shell +notation plugin uninstall --yes ``` -Upon successful execution, the plugin is removed from the plugins directory. If the plugin is not found, an error is returned showing the syntax for the plugin list command to show the installed plugins. +If the plugin is not found, an error is returned showing the syntax for the plugin list command to show the installed plugins. + +```shell +Error: unable to find plugin . +To view a list of installed plugins, use `notation plugin list` +``` ### List installed plugins @@ -99,6 +164,6 @@ An example of output from `notation plugin list`: ```text NAME DESCRIPTION VERSION CAPABILITIES ERROR -azure-kv Sign artifacts with keys in Azure Key Vault v0.5.0-rc.1 [SIGNATURE_GENERATOR.RAW] -com.amazonaws.signer.notation.plugin AWS Signer plugin for Notation 1.0.290 [SIGNATURE_GENERATOR.ENVELOPE SIGNATURE_VERIFIER.TRUSTED_IDENTITY SIGNATURE_VERIFIER.REVOCATION_CHECK] +azure-kv Sign artifacts with keys in Azure Key Vault v1.0.0 Signature generation +com.amazonaws.signer.notation.plugin AWS Signer plugin for Notation 1.0.290 Signature envelope generation, Trusted Identity validation, Certificate chain revocation check ``` diff --git a/specs/commandline/policy.md b/specs/commandline/policy.md index 56abd236a..f31361757 100644 --- a/specs/commandline/policy.md +++ b/specs/commandline/policy.md @@ -2,14 +2,16 @@ ## Description -As part of signature verification workflow, users need to configure the trust policy configuration file to specify trusted identities that signed the artifacts, the level of signature verification to use and other settings. For more details, see [trust policy specification and examples](https://github.com/notaryproject/notaryproject/blob/v1.0.0-rc.2/specs/trust-store-trust-policy.md#trust-policy). +As part of signature verification workflow of signed OCI artifacts, users need to configure trust policy configuration file to specify trusted identities that signed the artifacts, the level of signature verification to use and other settings. For more details, see [OCI trust policy specification and examples](https://github.com/notaryproject/specifications/blob/main/specs/trust-store-trust-policy.md#oci-trust-policy). -The `notation policy` command provides a user-friendly way to manage trust policies. It allows users to show trust policy configuration, import/export a trust policy configuration file from/to a JSON file. To get started user can refer to the following trust policy configuration sample. In this sample, there are four policies configured for different requirements: +The `notation policy` command provides a user-friendly way to manage trust policies for signed OCI images. It allows users to show trust policy configuration, import/export a trust policy configuration file from/to a JSON file. Users who want to manage trust policies for signed arbitrary blobs, please refer to `notation blob policy` command. -- The Policy named "wabbit-networks-images" is for verifying images signed by Wabbit Networks and stored in two repositories `registry.acme-rockets.io/software/net-monitor` and `registry.acme-rockets.io/software/net-logger`. -- Policy named "unsigned-image" is for skipping the verification on unsigned images stored in repository `registry.acme-rockets.io/software/unsigned/net-utils`. -- Policy "allow-expired-images" is for logging instead of failing expired images stored in repository `registry.acme-rockets.io/software/legacy/metrics`. -- Policy "global-policy-for-all-other-images" is for verifying any other images that signed by the ACME Rockets. +To get started, user can refer to the following trust policy configuration sample `trustpolicy.json` that is applicable for verifying signed OCI artifacts using `notation verify` command. In this sample, there are four policies configured for different requirements: + +- The Policy named "wabbit-networks-images" is for verifying OCI artifacts signed by Wabbit Networks and stored in two repositories `registry.acme-rockets.io/software/net-monitor` and `registry.acme-rockets.io/software/net-logger`. +- Policy named "unsigned-image" is for skipping the verification on unsigned OCI artifacts stored in repository `registry.acme-rockets.io/software/unsigned/net-utils`. +- Policy "allow-expired-images" is for logging instead of failing expired OCI artifacts stored in repository `registry.acme-rockets.io/software/legacy/metrics`. +- Policy "global-policy-for-all-other-images" is for verifying any other OCI artifacts that signed by the ACME Rockets. ```jsonc { @@ -72,7 +74,7 @@ The `notation policy` command provides a user-friendly way to manage trust polic ### notation policy command ```text -Manage trust policy configuration for signature verification. +Manage trust policy configuration for OCI artifact signature verification. Usage: notation policy [command] @@ -88,7 +90,7 @@ Flags: ### notation policy import ```text -Import trust policy configuration from a JSON file +Import OCI trust policy configuration from a JSON file Usage: notation policy import [flags] @@ -101,7 +103,7 @@ Flags: ### notation policy show ```text -Show trust policy configuration +Show OCI trust policy configuration Usage: notation policy show [flags] @@ -120,7 +122,7 @@ An example of import trust policy configuration from a JSON file: notation policy import ./my_policy.json ``` -The trust policy configuration in the JSON file should be validated according to [trust policy properties](https://github.com/notaryproject/notaryproject/blob/v1.0.0-rc.2/specs/trust-store-trust-policy.md#trust-policy-properties). A successful message should be printed out if trust policy configuration are imported successfully. Error logs including the reason should be printed out if the importing fails. +The trust policy configuration in the JSON file should be validated according to [trust policy properties](https://github.com/notaryproject/notaryproject/specs/trust-store-trust-policy.md#trust-policy-properties). A successful message should be printed out if trust policy configuration are imported successfully. Error logs including the reason should be printed out if the importing fails. If there is an existing trust policy configuration, prompt for users to confirm whether discarding existing configuration or not. Users can use `--force` flag to discard existing trust policy configuration without prompt. @@ -132,29 +134,29 @@ Use the following command to show trust policy configuration: notation policy show ``` -Upon successful execution, the trust policy configuration are printed out to standard output. If trust policy is not configured or is malformed, users should receive an error message via standard error output, and a tip to import trust policy configuration from a JSON file. +Upon successful execution, the trust policy configuration is printed out to standard output. If trust policy is not configured or is malformed, users should receive an error message via standard error output, and a tip to import trust policy configuration from a JSON file. -### Export trust policy configuration into a JSON file +### Export OCI trust policy configuration into a JSON file Users can redirect the output of command `notation policy show` to a JSON file. ```shell -notation policy show > ./trust_policy.json +notation policy show > ./oci_trust_policy.json ``` ### Update trust policy configuration -The steps to update trust policy configuration: +The steps to update OCI trust policy configuration: 1. Export trust policy configuration into a JSON file. ```shell - notation policy show > ./trust_policy.json + notation policy show > ./oci_trust_policy.json ``` -2. Edit the exported JSON file "trust_policy.json", update trust policy configuration and save the file. +2. Edit the exported JSON file "oci_trust_policy.json", update trust policy configuration and save the file. 3. Import trust policy configuration from the file. ```shell - notation policy import ./trust_policy.json + notation policy import ./oci_trust_policy.json ``` diff --git a/specs/commandline/sign.md b/specs/commandline/sign.md index 5c3569d5d..1bb445797 100644 --- a/specs/commandline/sign.md +++ b/specs/commandline/sign.md @@ -2,7 +2,7 @@ ## Description -Use `notation sign` to sign artifacts. +Use `notation sign` to sign artifacts stored in OCI compliant registries. Signs an OCI artifact stored in the registry. Always sign artifact using digest(`@sha256:...`) rather than a tag(`:v1`) because tags are mutable and a tag reference can point to a different artifact than the one signed. If a tag is used, notation resolves the tag to the `digest` before signing. @@ -19,6 +19,8 @@ Warning: Always sign the artifact using digest(`@sha256:...`) rather than a tag( Successfully signed /@ ``` +NOTE: This command is for signing OCI artifacts only. Use `notation blob sign` command for signing arbitrary blobs. + ## Outline ```text @@ -63,7 +65,7 @@ Notation uses [OCI image manifest][oci-image-spec] to store signatures in regist ```shell # Prerequisites: -# - A signing plugin is installed. See plugin documentation (https://github.com/notaryproject/notaryproject/blob/main/specs/plugin-extensibility.md) for more details. +# - A signing plugin is installed. See plugin documentation (https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/plugin-extensibility.md) for more details. # - Configure the signing plugin as instructed by plugin vendor. # Add a default signing key referencing the remote key identifier, and the plugin associated with it. diff --git a/specs/commandline/verify.md b/specs/commandline/verify.md index 3fe40a3a4..943e7bcd3 100644 --- a/specs/commandline/verify.md +++ b/specs/commandline/verify.md @@ -2,7 +2,7 @@ ## Description -Use `notation verify` command to verify signatures associated with the artifact. Signature verification succeeds if verification succeeds for at least one of the signatures associated with the artifact. Upon successful verification, the output message is printed out as follows: +Use `notation verify` command to verify signatures associated with artifacts stored in OCI compliant registries. Signature verification succeeds if verification succeeds for at least one of the signatures associated with the artifact. Upon successful verification, the output message is printed out as follows: ```text Successfully verified signature for /@ @@ -25,6 +25,9 @@ The artifact was signed with the following user metadata. KEY VALUE ``` +> [!NOTE] +> This command is for verifying OCI artifacts only. Use `notation blob verify` command for verifying arbitrary blobs. + ## Outline @@ -59,7 +62,7 @@ Use `notation certificate` command to configure trust stores. ### Configure Trust Policy -Users who consume signed artifact from a registry use the trust policy to specify trusted identities which sign the artifacts, and level of signature verification to use. The trust policy is a JSON document. User needs to create a file named `trustpolicy.json` under `{NOTATION_CONFIG}`. See [Notation Directory Structure](https://notaryproject.dev/docs/user-guides/how-to/directory-structure/) for `{NOTATION_CONFIG}`. +Users who consume signed artifact from a registry use the trust policy to specify trusted identities which sign the artifacts, and level of signature verification to use. The trust policy is a JSON document. User needs to create a file named `trustpolicy.json` or `trustpolicy.oci.json` under `{NOTATION_CONFIG}`. See [Notation Directory Structure](https://notaryproject.dev/docs/user-guides/how-to/directory-structure/) for `{NOTATION_CONFIG}`. An example of `trustpolicy.json`: @@ -106,7 +109,7 @@ Example values on trust policy properties: | trustedIdentities | "x509.subject: C=US, ST=WA, L=Seattle, O=wabbit-networks.io, OU=Finance, CN=SecureBuilder" | User only trusts the identity with specific subject. User can use `notation certificate show` command to get the `subject` info. | | trustedIdentities | "*" | User trusts any identity (signing certificate) issued by the CA(s) in trust stores. | -User can configure multiple trust policies for different scenarios. See [Trust Policy Schema and properties](https://github.com/notaryproject/notaryproject/blob/main/specs/trust-store-trust-policy.md#trust-policy) for details. +User can configure multiple trust policies for different scenarios. See [Trust Policy Schema and properties](https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/trust-store-trust-policy.md#trust-policy) for details. ### Verify signatures on an OCI artifact stored in a registry diff --git a/specs/notation-cli.md b/specs/notation-cli.md index dcb03352e..24e83f12a 100644 --- a/specs/notation-cli.md +++ b/specs/notation-cli.md @@ -6,16 +6,17 @@ This spec contains reference information on using notation commands. Each comman | Command | Description | | ------------------------------------------- | ---------------------------------------------------------------------- | +| [blob](./commandline/blob.md) | Sign, verify and inspect singatures associated with blobs | | [certificate](./commandline/certificate.md) | Manage certificates in trust store | -| [inspect](./commandline/inspect.md) | Inspect signatures | +| [inspect](./commandline/inspect.md) | Inspect OCI signatures | | [key](./commandline/key.md) | Manage keys used for signing | -| [list](./commandline/list.md) | List signatures of the signed artifact | -| [login](./commandline/login.md) | Login to registries | -| [logout](./commandline/logout.md) | Log out from the logged in registries | +| [list](./commandline/list.md) | List signatures of a signed OCI artifact | +| [login](./commandline/login.md) | Log into OCI registries | +| [logout](./commandline/logout.md) | Log out from the logged in OCI registries | | [plugin](./commandline/plugin.md) | Manage plugins | -| [policy](./commandline/policy.md) | Manage trust policy configuration for signature verification | -| [sign](./commandline/sign.md) | Sign artifacts | -| [verify](./commandline/verify.md) | Verify artifacts | +| [policy](./commandline/policy.md) | Manage trust policy configuration for OCI signature verification | +| [sign](./commandline/sign.md) | Sign OCI artifacts | +| [verify](./commandline/verify.md) | Verify OCI artifacts | | [version](./commandline/version.md) | Print the version of notation CLI | ## Notation Outline @@ -27,16 +28,17 @@ Usage: notation [command] Available Commands: + blob Sign, verify and inspect signatures associated with blobs certificate Manage certificates in trust store - inspect Inspect all signatures associated with the signed artifact + inspect Inspect all signatures associated with a signed OCI artifact key Manage keys used for signing - list List signatures of the signed artifact - login Login to registry - logout Log out from the logged in registries + list List signatures of a signed OCI artifact + login Log into OCI registries + logout Log out from the logged in OCI registries plugin Manage plugins - policy Manage trust policy configuration for signature verification - sign Sign artifacts - verify Verify artifacts + policy Manage trust policy configuration for OCI signature verification + sign Sign OCI artifacts + verify Verify OCI artifacts version Show the notation version information Flags: diff --git a/test/e2e/go.mod b/test/e2e/go.mod index c7944444a..450b1352a 100644 --- a/test/e2e/go.mod +++ b/test/e2e/go.mod @@ -3,15 +3,15 @@ module github.com/notaryproject/notation/test/e2e go 1.20 require ( - github.com/notaryproject/notation-core-go v1.0.0 + github.com/notaryproject/notation-core-go v1.0.2 github.com/onsi/ginkgo/v2 v2.11.0 github.com/onsi/gomega v1.27.10 - github.com/opencontainers/image-spec v1.1.0-rc4 - oras.land/oras-go/v2 v2.2.1 + github.com/opencontainers/image-spec v1.1.0-rc6 + oras.land/oras-go/v2 v2.4.0 ) require ( - github.com/fxamacker/cbor/v2 v2.4.0 // indirect + github.com/fxamacker/cbor/v2 v2.5.0 // indirect github.com/go-logr/logr v1.2.4 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/google/go-cmp v0.5.9 // indirect @@ -20,7 +20,7 @@ require ( github.com/veraison/go-cose v1.1.0 // indirect github.com/x448/float16 v0.8.4 // indirect golang.org/x/net v0.17.0 // indirect - golang.org/x/sync v0.3.0 // indirect + golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/tools v0.9.3 // indirect diff --git a/test/e2e/go.sum b/test/e2e/go.sum index a938441dd..930892729 100644 --- a/test/e2e/go.sum +++ b/test/e2e/go.sum @@ -1,8 +1,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= -github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= +github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= +github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= @@ -12,16 +12,16 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20230510103437-eeec1cb781c3 h1:2XF1Vzq06X+inNqgJ9tRnGuw+ZVCB3FazXODD6JE1R8= github.com/google/pprof v0.0.0-20230510103437-eeec1cb781c3/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= -github.com/notaryproject/notation-core-go v1.0.0 h1:FgOAihtFW4XU9JYyTzItg1xW3OaN4eCasw5Bp00Ydu4= -github.com/notaryproject/notation-core-go v1.0.0/go.mod h1:eoHFJ2e6b31GZO9hckCms5kfXvHLTySvJ1QwRLB9ZCk= +github.com/notaryproject/notation-core-go v1.0.2 h1:VEt+mbsgdANd9b4jqgmx2C7U0DmwynOuD2Nhxh3bANw= +github.com/notaryproject/notation-core-go v1.0.2/go.mod h1:2HkQzUwg08B3x9oVIztHsEh7Vil2Rj+tYgxH+JObLX4= github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0= -github.com/opencontainers/image-spec v1.1.0-rc4/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/opencontainers/image-spec v1.1.0-rc6 h1:XDqvyKsJEbRtATzkgItUqBA7QHk58yxX1Ov9HERHNqU= +github.com/opencontainers/image-spec v1.1.0-rc6/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -31,12 +31,12 @@ github.com/veraison/go-cose v1.1.0 h1:AalPS4VGiKavpAzIlBjrn7bhqXiXi4jbMYY/2+UC+4 github.com/veraison/go-cose v1.1.0/go.mod h1:7ziE85vSq4ScFTg6wyoMXjucIGOf4JkFEZi/an96Ct4= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= @@ -49,5 +49,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -oras.land/oras-go/v2 v2.2.1 h1:3VJTYqy5KfelEF9c2jo1MLSpr+TM3mX8K42wzZcd6qE= -oras.land/oras-go/v2 v2.2.1/go.mod h1:GeAwLuC4G/JpNwkd+bSZ6SkDMGaaYglt6YK2WvZP7uQ= +oras.land/oras-go/v2 v2.4.0 h1:i+Wt5oCaMHu99guBD0yuBjdLvX7Lz8ukPbwXdR7uBMs= +oras.land/oras-go/v2 v2.4.0/go.mod h1:osvtg0/ClRq1KkydMAEu/IxFieyjItcsQ4ut4PPF+f8= diff --git a/test/e2e/internal/notation/init.go b/test/e2e/internal/notation/init.go index f323e13b7..110a7dde1 100644 --- a/test/e2e/internal/notation/init.go +++ b/test/e2e/internal/notation/init.go @@ -33,17 +33,19 @@ const ( ) const ( - envKeyRegistryHost = "NOTATION_E2E_REGISTRY_HOST" - envKeyRegistryUsername = "NOTATION_E2E_REGISTRY_USERNAME" - envKeyRegistryPassword = "NOTATION_E2E_REGISTRY_PASSWORD" - envKeyDomainRegistryHost = "NOTATION_E2E_DOMAIN_REGISTRY_HOST" - envKeyNotationBinPath = "NOTATION_E2E_BINARY_PATH" - envKeyNotationOldBinPath = "NOTATION_E2E_OLD_BINARY_PATH" - envKeyNotationPluginPath = "NOTATION_E2E_PLUGIN_PATH" - envKeyNotationConfigPath = "NOTATION_E2E_CONFIG_PATH" - envKeyOCILayoutPath = "NOTATION_E2E_OCI_LAYOUT_PATH" - envKeyTestRepo = "NOTATION_E2E_TEST_REPO" - envKeyTestTag = "NOTATION_E2E_TEST_TAG" + envKeyRegistryHost = "NOTATION_E2E_REGISTRY_HOST" + envKeyRegistryUsername = "NOTATION_E2E_REGISTRY_USERNAME" + envKeyRegistryPassword = "NOTATION_E2E_REGISTRY_PASSWORD" + envKeyDomainRegistryHost = "NOTATION_E2E_DOMAIN_REGISTRY_HOST" + envKeyNotationBinPath = "NOTATION_E2E_BINARY_PATH" + envKeyNotationOldBinPath = "NOTATION_E2E_OLD_BINARY_PATH" + envKeyNotationPluginPath = "NOTATION_E2E_PLUGIN_PATH" + envKeyNotationPluginTarGzPath = "NOTATION_E2E_PLUGIN_TAR_GZ_PATH" + envKeyNotationMaliciouPluginArchivePath = "NOTATION_E2E_MALICIOUS_PLUGIN_ARCHIVE_PATH" + envKeyNotationConfigPath = "NOTATION_E2E_CONFIG_PATH" + envKeyOCILayoutPath = "NOTATION_E2E_OCI_LAYOUT_PATH" + envKeyTestRepo = "NOTATION_E2E_TEST_REPO" + envKeyTestTag = "NOTATION_E2E_TEST_TAG" ) var ( @@ -51,12 +53,14 @@ var ( NotationBinPath string // NotationOldBinPath is the path of an old version notation binary for // testing forward compatibility. - NotationOldBinPath string - NotationE2EPluginPath string - NotationE2EConfigPath string - NotationE2ELocalKeysDir string - NotationE2ETrustPolicyDir string - NotationE2EConfigJsonDir string + NotationOldBinPath string + NotationE2EPluginPath string + NotationE2EPluginTarGzPath string + NotationE2EMaliciousPluginArchivePath string + NotationE2EConfigPath string + NotationE2ELocalKeysDir string + NotationE2ETrustPolicyDir string + NotationE2EConfigJsonDir string ) var ( @@ -90,6 +94,8 @@ func setUpNotationValues() { // set Notation e2e-plugin path setPathValue(envKeyNotationPluginPath, &NotationE2EPluginPath) + setPathValue(envKeyNotationPluginTarGzPath, &NotationE2EPluginTarGzPath) + setPathValue(envKeyNotationMaliciouPluginArchivePath, &NotationE2EMaliciousPluginArchivePath) // set Notation configuration paths setPathValue(envKeyNotationConfigPath, &NotationE2EConfigPath) diff --git a/test/e2e/internal/utils/exec.go b/test/e2e/internal/utils/exec.go index df1e2ca94..32307377a 100644 --- a/test/e2e/internal/utils/exec.go +++ b/test/e2e/internal/utils/exec.go @@ -1,3 +1,16 @@ +// Copyright The Notary Project 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. + // copied and adopted from https://github.com/oras-project/oras with // modification /* diff --git a/test/e2e/plugin/go.mod b/test/e2e/plugin/go.mod index 441b0db62..465f6c3ce 100644 --- a/test/e2e/plugin/go.mod +++ b/test/e2e/plugin/go.mod @@ -4,25 +4,26 @@ go 1.20 require ( github.com/golang-jwt/jwt v3.2.2+incompatible - github.com/notaryproject/notation-core-go v1.0.0 - github.com/notaryproject/notation-go v1.0.0 + github.com/notaryproject/notation-core-go v1.0.2 + github.com/notaryproject/notation-go v1.1.1-0.20240201073933-4606472ebdcb github.com/spf13/cobra v1.7.0 ) require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect - github.com/fxamacker/cbor/v2 v2.4.0 // indirect - github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect - github.com/go-ldap/ldap/v3 v3.4.5 // indirect + github.com/fxamacker/cbor/v2 v2.5.0 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect + github.com/go-ldap/ldap/v3 v3.4.6 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/google/uuid v1.3.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0-rc4 // indirect + github.com/opencontainers/image-spec v1.1.0-rc6 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/veraison/go-cose v1.1.0 // indirect github.com/x448/float16 v0.8.4 // indirect - golang.org/x/crypto v0.11.0 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/sync v0.3.0 // indirect - oras.land/oras-go/v2 v2.2.1 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/sync v0.6.0 // indirect + oras.land/oras-go/v2 v2.4.0 // indirect ) diff --git a/test/e2e/plugin/go.sum b/test/e2e/plugin/go.sum index a2af753f5..2ebbff354 100644 --- a/test/e2e/plugin/go.sum +++ b/test/e2e/plugin/go.sum @@ -6,26 +6,28 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= -github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= -github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= -github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= -github.com/go-ldap/ldap/v3 v3.4.5 h1:ekEKmaDrpvR2yf5Nc/DClsGG9lAmdDixe44mLzlW5r8= -github.com/go-ldap/ldap/v3 v3.4.5/go.mod h1:bMGIq3AGbytbaMwf8wdv5Phdxz0FWHTIYMSzyrYgnQs= +github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= +github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= +github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= +github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A= +github.com/go-ldap/ldap/v3 v3.4.6/go.mod h1:IGMQANNtxpsOzj7uUAMjpGBaOVTC4DYyIy8VsTdxmtc= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/notaryproject/notation-core-go v1.0.0 h1:FgOAihtFW4XU9JYyTzItg1xW3OaN4eCasw5Bp00Ydu4= -github.com/notaryproject/notation-core-go v1.0.0/go.mod h1:eoHFJ2e6b31GZO9hckCms5kfXvHLTySvJ1QwRLB9ZCk= -github.com/notaryproject/notation-go v1.0.0 h1:pH+0NVmZu1IhE8zUhK9Oxna3OlHNdy+crNntnuCiThs= -github.com/notaryproject/notation-go v1.0.0/go.mod h1:NpfUnDt94vLSCJ8fAWplgTbf3fmq3JLSEnjDFl7j16U= +github.com/notaryproject/notation-core-go v1.0.2 h1:VEt+mbsgdANd9b4jqgmx2C7U0DmwynOuD2Nhxh3bANw= +github.com/notaryproject/notation-core-go v1.0.2/go.mod h1:2HkQzUwg08B3x9oVIztHsEh7Vil2Rj+tYgxH+JObLX4= +github.com/notaryproject/notation-go v1.1.1-0.20240201073933-4606472ebdcb h1:OVkHyQD0O8hTsuDPzdpgdteHDN9ormV5M3/pi9ka4II= +github.com/notaryproject/notation-go v1.1.1-0.20240201073933-4606472ebdcb/go.mod h1:v0e8Y7gEzTtx7aNw3tG6da7atr59JRdePVMMkTGNXzA= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0= -github.com/opencontainers/image-spec v1.1.0-rc4/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/opencontainers/image-spec v1.1.0-rc6 h1:XDqvyKsJEbRtATzkgItUqBA7QHk58yxX1Ov9HERHNqU= +github.com/opencontainers/image-spec v1.1.0-rc6/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -45,39 +47,42 @@ github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcY github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -87,5 +92,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -oras.land/oras-go/v2 v2.2.1 h1:3VJTYqy5KfelEF9c2jo1MLSpr+TM3mX8K42wzZcd6qE= -oras.land/oras-go/v2 v2.2.1/go.mod h1:GeAwLuC4G/JpNwkd+bSZ6SkDMGaaYglt6YK2WvZP7uQ= +oras.land/oras-go/v2 v2.4.0 h1:i+Wt5oCaMHu99guBD0yuBjdLvX7Lz8ukPbwXdR7uBMs= +oras.land/oras-go/v2 v2.4.0/go.mod h1:osvtg0/ClRq1KkydMAEu/IxFieyjItcsQ4ut4PPF+f8= diff --git a/test/e2e/run.sh b/test/e2e/run.sh index 7d053aa89..7da050cb4 100755 --- a/test/e2e/run.sh +++ b/test/e2e/run.sh @@ -71,9 +71,9 @@ fi # install dependency go install -mod=mod github.com/onsi/ginkgo/v2/ginkgo@v2.9.5 -# build e2e plugin -PLUGIN_NAME=e2e-plugin -( cd $CWD/plugin && go build -o ./bin/$PLUGIN_NAME . && echo "e2e plugin built." ) +# build e2e plugin and tar.gz +PLUGIN_NAME=notation-e2e-plugin +( cd $CWD/plugin && go build -o ./bin/$PLUGIN_NAME . && echo "e2e plugin built." && tar -czvf ./bin/$PLUGIN_NAME.tar.gz -C ./bin/ $PLUGIN_NAME ) # setup registry case $REGISTRY_NAME in @@ -107,6 +107,8 @@ export NOTATION_E2E_OCI_LAYOUT_PATH=$CWD/testdata/registry/oci_layout export NOTATION_E2E_TEST_REPO=e2e export NOTATION_E2E_TEST_TAG=v1 export NOTATION_E2E_PLUGIN_PATH=$CWD/plugin/bin/$PLUGIN_NAME +export NOTATION_E2E_PLUGIN_TAR_GZ_PATH=$CWD/plugin/bin/$PLUGIN_NAME.tar.gz +export NOTATION_E2E_MALICIOUS_PLUGIN_ARCHIVE_PATH=$CWD/testdata/malicious-plugin # run tests ginkgo -r -p -v \ No newline at end of file diff --git a/test/e2e/suite/command/verify.go b/test/e2e/suite/command/verify.go index d8e794948..7df83ee3d 100644 --- a/test/e2e/suite/command/verify.go +++ b/test/e2e/suite/command/verify.go @@ -155,4 +155,26 @@ var _ = Describe("notation verify", func() { NoMatchErrKeyWords(HTTPSRequest) }) }) + + It("incorrect NOTATION_CONFIG path", func() { + Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + notation.Exec("sign", artifact.ReferenceWithDigest()). + MatchKeyWords(SignSuccessfully) + + vhost.UpdateEnv(map[string]string{"NOTATION_CONFIG": "/not/exist"}) + notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest(), "-v"). + MatchErrKeyWords("trust policy is not present") + }) + }) + + It("correct NOTATION_CONFIG path", func() { + Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + notation.Exec("sign", artifact.ReferenceWithDigest()). + MatchKeyWords(SignSuccessfully) + + vhost.UpdateEnv(map[string]string{"NOTATION_CONFIG": vhost.AbsolutePath(NotationDirName)}) + notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). + MatchKeyWords(VerifySuccessfully) + }) + }) }) diff --git a/test/e2e/suite/plugin/install.go b/test/e2e/suite/plugin/install.go new file mode 100644 index 000000000..363801d44 --- /dev/null +++ b/test/e2e/suite/plugin/install.go @@ -0,0 +1,161 @@ +// Copyright The Notary Project 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. + +package plugin + +import ( + "path/filepath" + + . "github.com/notaryproject/notation/test/e2e/internal/notation" + "github.com/notaryproject/notation/test/e2e/internal/utils" + . "github.com/onsi/ginkgo/v2" +) + +const ( + PluginURL = "https://github.com/notaryproject/notation-action/raw/e2e-test-plugin/tests/plugin_binaries/notation-e2e-test-plugin_0.1.0_linux_amd64.tar.gz" + PluginChecksum = "be8d035024d3a96afb4118af32f2e201f126c7254b02f7bcffb3e3149d744fd2" +) + +var _ = Describe("notation plugin install", func() { + It("with missing file or url flag", func() { + Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("plugin", "install", "."). + MatchErrContent("Error: at least one of the flags in the group [file url] is required\n") + }) + }) + + It("with both file and url flags are set", func() { + Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("plugin", "install", "--file", "--url", "."). + MatchErrContent("Error: if any flags in the group [file url] are set none of the others can be; [file url] were all set\n") + }) + }) + + It("with missing plugin source", func() { + Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("plugin", "install"). + MatchErrContent("Error: missing plugin source location\n") + }) + }) + + It("with missing plugin file path", func() { + Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("plugin", "install", "--file"). + MatchErrContent("Error: missing plugin file path\n") + }) + }) + + It("with missing plugin URL", func() { + Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("plugin", "install", "--url"). + MatchErrContent("Error: missing plugin URL\n") + }) + }) + + It("with zip bomb single file exceeds 256 MiB size limit in zip format", func() { + Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("plugin", "install", "--file", filepath.Join(NotationE2EMaliciousPluginArchivePath, "large_file_zip.zip"), "-v"). + MatchErrContent("Error: plugin installation failed: total file size reached the 256 MiB size limit\n") + }) + }) + + It("with zip bomb single file exceeds 256 MiB size limit in tar.gz format", func() { + Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("plugin", "install", "--file", filepath.Join(NotationE2EMaliciousPluginArchivePath, "large_file_tarGz.tar.gz"), "-v"). + MatchErrContent("Error: plugin installation failed: total file size reached the 256 MiB size limit\n") + }) + }) + + It("with zip bomb total file size exceeds 256 MiB size limit", func() { + Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("plugin", "install", "--file", filepath.Join(NotationE2EMaliciousPluginArchivePath, "zip_bomb.zip"), "-v"). + MatchErrContent("Error: plugin installation failed: total file size reached the 256 MiB size limit\n") + }) + }) + + It("with zip slip", func() { + Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("plugin", "install", "--file", filepath.Join(NotationE2EMaliciousPluginArchivePath, "zip_slip.zip"), "-v"). + MatchErrContent("Error: plugin installation failed: file name in zip cannot contain '..', but found \"../../../../../../../../tmp/evil.txt\"\n") + }) + }) + + It("with valid plugin file path", func() { + Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { + notation.Exec("plugin", "install", "--file", NotationE2EPluginTarGzPath, "-v"). + MatchContent("Successfully installed plugin e2e-plugin, version 1.0.0\n") + }) + }) + + It("with plugin executable file path", func() { + Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { + notation.Exec("plugin", "install", "--file", NotationE2EPluginPath). + MatchContent("Successfully installed plugin e2e-plugin, version 1.0.0\n") + }) + }) + + It("with plugin already installed", func() { + Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { + notation.Exec("plugin", "install", "--file", NotationE2EPluginTarGzPath). + MatchContent("Successfully installed plugin e2e-plugin, version 1.0.0\n") + + notation.ExpectFailure().Exec("plugin", "install", "--file", NotationE2EPluginTarGzPath). + MatchErrContent("Error: plugin installation failed: plugin e2e-plugin with version 1.0.0 already exists\n") + }) + }) + + It("with plugin already installed but force install", func() { + Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { + notation.Exec("plugin", "install", "--file", NotationE2EPluginTarGzPath, "-v"). + MatchContent("Successfully installed plugin e2e-plugin, version 1.0.0\n") + + notation.Exec("plugin", "install", "--file", NotationE2EPluginTarGzPath, "--force"). + MatchContent("Successfully updated plugin e2e-plugin from version 1.0.0 to 1.0.0\n") + }) + }) + + It("with valid plugin URL", func() { + Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { + notation.Exec("plugin", "install", "--url", PluginURL, "--sha256sum", PluginChecksum). + MatchKeyWords("Successfully installed plugin e2e-test-plugin, version 0.1.0\n") + }) + }) + + It("with valid plugin URL but missing checksum", func() { + Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("plugin", "install", "--url", PluginURL). + MatchErrContent("Error: installing from URL requires non-empty SHA256 checksum of the plugin source\n") + }) + }) + + It("with valid plugin URL but mismatched SHA-256 checksum", func() { + Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("plugin", "install", "--url", PluginURL, "--sha256sum", "abcd"). + MatchErrContent("Error: plugin installation failed: plugin SHA-256 checksum does not match user input. Expecting abcd\n") + }) + }) + + It("with invalid plugin URL scheme", func() { + Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("plugin", "install", "--url", "http://invalid", "--sha256sum", "abcd"). + MatchErrContent("Error: failed to download plugin from URL: only the HTTPS scheme is supported, but got http\n") + }) + }) + + It("with invalid plugin URL", func() { + Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("plugin", "install", "--url", "https://invalid", "--sha256sum", "abcd"). + MatchErrKeyWords("failed to download plugin from URL https://invalid") + }) + }) +}) diff --git a/test/e2e/suite/plugin/sign.go b/test/e2e/suite/plugin/sign.go index 3dc118f7e..2f6eea071 100644 --- a/test/e2e/suite/plugin/sign.go +++ b/test/e2e/suite/plugin/sign.go @@ -265,4 +265,38 @@ var _ = Describe("notation plugin sign", func() { Expect(descriptors[0].Annotations).Should(HaveKeyWithValue("k1", "v1")) }) }) + + It("incorrect NOTATION_LIBEXEC path", func() { + Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + // setup incorrect NOTATION_LIBEXEC path + vhost.SetOption(AddPlugin(NotationE2EPluginPath)) + notation.Exec("key", "add", "plugin-key", "--id", "key1", "--plugin", "e2e-plugin", + "--plugin-config", string(CapabilityEnvelopeGenerator)+"=true", + "--plugin-config", TamperAnnotation+"=k1=v1"). + MatchKeyWords("plugin-key") + + vhost.UpdateEnv(map[string]string{"NOTATION_LIBEXEC": "/not/exist"}) + + // run signing + notation.ExpectFailure().Exec("sign", artifact.ReferenceWithDigest(), "--key", "plugin-key", "-d"). + MatchErrKeyWords("no such file or directory") + }) + }) + + It("correct NOTATION_LIBEXEC path", func() { + Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + // setup incorrect NOTATION_LIBEXEC path + vhost.SetOption(AddPlugin(NotationE2EPluginPath)) + notation.Exec("key", "add", "plugin-key", "--id", "key1", "--plugin", "e2e-plugin", + "--plugin-config", string(CapabilityEnvelopeGenerator)+"=true", + "--plugin-config", TamperAnnotation+"=k1=v1"). + MatchKeyWords("plugin-key") + + vhost.UpdateEnv(map[string]string{"NOTATION_LIBEXEC": vhost.AbsolutePath(NotationDirName)}) + + // run signing + notation.Exec("sign", artifact.ReferenceWithDigest(), "--key", "plugin-key", "-d"). + MatchKeyWords("Successfully signed") + }) + }) }) diff --git a/test/e2e/suite/plugin/uninstall.go b/test/e2e/suite/plugin/uninstall.go new file mode 100644 index 000000000..7a4b26ee5 --- /dev/null +++ b/test/e2e/suite/plugin/uninstall.go @@ -0,0 +1,38 @@ +// Copyright The Notary Project 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. + +package plugin + +import ( + . "github.com/notaryproject/notation/test/e2e/internal/notation" + "github.com/notaryproject/notation/test/e2e/internal/utils" + . "github.com/onsi/ginkgo/v2" +) + +var _ = Describe("notation plugin uninstall", func() { + It("with valid plugin name", func() { + Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { + vhost.SetOption(AddPlugin(NotationE2EPluginPath)) + notation.Exec("plugin", "uninstall", "--yes", "e2e-plugin"). + MatchContent("Successfully uninstalled plugin e2e-plugin\n") + }) + }) + + It("with plugin does not exist", func() { + Host(nil, func(notation *utils.ExecOpts, _ *Artifact, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("plugin", "uninstall", "--yes", "non-exist"). + MatchErrContent("Error: unable to find plugin non-exist.\nTo view a list of installed plugins, use `notation plugin list`\n") + }) + }) + +}) diff --git a/test/e2e/suite/trustpolicy/trust_store.go b/test/e2e/suite/trustpolicy/trust_store.go index b918d489e..099801bf3 100644 --- a/test/e2e/suite/trustpolicy/trust_store.go +++ b/test/e2e/suite/trustpolicy/trust_store.go @@ -41,9 +41,7 @@ var _ = Describe("notation trust policy trust store test", func() { artifact := GenerateArtifact("e2e-valid-signature", "") notation.ExpectFailure().Exec("verify", "--allow-referrers-api", artifact.ReferenceWithDigest(), "-v"). - MatchErrKeyWords("authenticity validation failed", - "truststore/x509/ca/invalid_store\\\" does not exist", - VerifyFailed) + MatchErrKeyWords("the trust store \"invalid_store\" of type \"ca\" does not exist") }) }) diff --git a/test/e2e/testdata/malicious-plugin/large_file_tarGz.tar.gz b/test/e2e/testdata/malicious-plugin/large_file_tarGz.tar.gz new file mode 100644 index 000000000..da3db5cbb Binary files /dev/null and b/test/e2e/testdata/malicious-plugin/large_file_tarGz.tar.gz differ diff --git a/test/e2e/testdata/malicious-plugin/large_file_zip.zip b/test/e2e/testdata/malicious-plugin/large_file_zip.zip new file mode 100644 index 000000000..40b2dfc32 Binary files /dev/null and b/test/e2e/testdata/malicious-plugin/large_file_zip.zip differ diff --git a/test/e2e/testdata/malicious-plugin/zip_bomb.zip b/test/e2e/testdata/malicious-plugin/zip_bomb.zip new file mode 100644 index 000000000..b4d00682f Binary files /dev/null and b/test/e2e/testdata/malicious-plugin/zip_bomb.zip differ diff --git a/test/e2e/testdata/malicious-plugin/zip_slip.zip b/test/e2e/testdata/malicious-plugin/zip_slip.zip new file mode 100644 index 000000000..c0fd13b0c Binary files /dev/null and b/test/e2e/testdata/malicious-plugin/zip_slip.zip differ