From 36146b18b25eccb2c77898723caa312fa88bb422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Batuhan=20Apayd=C4=B1n?= Date: Tue, 23 Nov 2021 12:26:49 +0300 Subject: [PATCH 1/3] feat: cosign sign use executable avoid deps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Batuhan Apaydın Co-authored-by: Furkan Türkal Signed-off-by: Batuhan Apaydın --- Dockerfile | 2 + README.md | 2 + cmd/nerdctl/pull.go | 95 +++++++++++++++++++++++++- cmd/nerdctl/pull_linux_test.go | 120 +++++++++++++++++++++++++++++++++ cmd/nerdctl/push.go | 76 +++++++++++++++++++++ cmd/nerdctl/run_windows.go | 1 + docs/cosign.md | 74 ++++++++++++++++++++ docs/experimental.md | 1 + pkg/imgutil/imgutil.go | 26 +++++++ 9 files changed, 396 insertions(+), 1 deletion(-) create mode 100644 cmd/nerdctl/pull_linux_test.go create mode 100644 docs/cosign.md diff --git a/Dockerfile b/Dockerfile index 7934e0388e1..e2680fd6755 100644 --- a/Dockerfile +++ b/Dockerfile @@ -239,6 +239,8 @@ COPY . /go/src/github.com/containerd/nerdctl WORKDIR /go/src/github.com/containerd/nerdctl VOLUME /tmp ENV CGO_ENABLED=0 +# copy cosign binary for integration test +COPY --from=gcr.io/projectsigstore/cosign:v1.3.1@sha256:3cd9b3a866579dc2e0cf2fdea547f4c9a27139276cc373165c26842bc594b8bd /ko-app/cosign /usr/local/bin/cosign # enable offline ipfs for integration test COPY ./Dockerfile.d/test-integration-etc_containerd-stargz-grpc_config.toml /etc/containerd-stargz-grpc/config.toml COPY ./Dockerfile.d/test-integration-ipfs-offline.service /usr/local/lib/systemd/system/ diff --git a/README.md b/README.md index fcf9836d44a..fed760cc84b 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ ✅ Supports [P2P image distribution (IPFS)](./docs/ipfs.md) + ✅ Supports [container image signing and verifying (cosign)](./docs/cosign.md) + nerdctl is a **non-core** sub-project of containerd. ## Examples diff --git a/cmd/nerdctl/pull.go b/cmd/nerdctl/pull.go index f2c787232f0..39598347dfb 100644 --- a/cmd/nerdctl/pull.go +++ b/cmd/nerdctl/pull.go @@ -17,7 +17,12 @@ package main import ( + "bufio" + "context" "errors" + "fmt" + "os" + "os/exec" "github.com/containerd/nerdctl/pkg/imgutil" "github.com/containerd/nerdctl/pkg/ipfs" @@ -25,6 +30,7 @@ import ( "github.com/containerd/nerdctl/pkg/referenceutil" "github.com/containerd/nerdctl/pkg/strutil" httpapi "github.com/ipfs/go-ipfs-http-client" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -38,6 +44,9 @@ func newPullCommand() *cobra.Command { SilenceErrors: true, } pullCommand.Flags().String("unpack", "auto", "Unpack the image for the current single platform (auto/true/false)") + pullCommand.Flags().String("cosign-key", "", + "path to the public key file, KMS, URI or Kubernetes Secret") + pullCommand.RegisterFlagCompletionFunc("unpack", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return []string{"auto", "true", "false"}, cobra.ShellCompDirectiveNoFileComp }) @@ -47,6 +56,7 @@ func newPullCommand() *cobra.Command { pullCommand.Flags().StringSlice("platform", nil, "Pull content for a specific platform") pullCommand.RegisterFlagCompletionFunc("platform", shellCompletePlatforms) pullCommand.Flags().Bool("all-platforms", false, "Pull content for all platforms") + pullCommand.Flags().String("verify", "none", "Verify the image with none|cosign. Default none") // #endregion pullCommand.Flags().BoolP("quiet", "q", false, "Suppress verbose output") @@ -57,6 +67,7 @@ func pullAction(cmd *cobra.Command, args []string) error { if len(args) < 1 { return errors.New("image name needs to be specified") } + rawRef := args[0] client, ctx, cancel, err := newClient(cmd) if err != nil { return err @@ -96,7 +107,16 @@ func pullAction(cmd *cobra.Command, args []string) error { return err } + verifier, err := cmd.Flags().GetString("verify") + if err != nil { + return err + } + if scheme, ref, err := referenceutil.ParseIPFSRefWithScheme(args[0]); err == nil { + if verifier != "none" { + return errors.New("--verify flag is not supported on IPFS as of now") + } + ipfsClient, err := httpapi.NewLocalApi() if err != nil { return err @@ -108,5 +128,78 @@ func pullAction(cmd *cobra.Command, args []string) error { _, err = imgutil.EnsureImage(ctx, client, cmd.OutOrStdout(), cmd.ErrOrStderr(), snapshotter, args[0], "always", insecure, ocispecPlatforms, unpack, quiet) - return err + + if err != nil { + return err + } + + switch verifier { + case "cosign": + keyRef, err := cmd.Flags().GetString("cosign-key") + if err != nil { + return err + } + + if err := verifyCosign(ctx, rawRef, keyRef); err != nil { + return err + } + case "none": + logrus.Debugf("verification process skipped") + default: + return fmt.Errorf("no verifier found: %s", verifier) + } + return nil +} + +func verifyCosign(ctx context.Context, rawRef string, keyRef string) error { + digest, err := imgutil.ResolveDigest(ctx, rawRef, false) + rawRef = rawRef + "@" + digest + if err != nil { + logrus.WithError(err).Errorf("unable to resolve digest for an image %s: %v", rawRef, err) + return err + } + + logrus.Debugf("verifying image: %s", rawRef) + + cosignExecutable, err := exec.LookPath("cosign") + if err != nil { + logrus.WithError(err).Error("cosign executable not found in path $PATH") + logrus.Info("you might consider installing cosign from: https://docs.sigstore.dev/cosign/installation") + return err + } + + cosignCmd := exec.Command(cosignExecutable, []string{"verify"}...) + cosignCmd.Env = os.Environ() + + if keyRef != "" { + cosignCmd.Args = append(cosignCmd.Args, "--key", keyRef) + } else { + cosignCmd.Env = append(cosignCmd.Env, "COSIGN_EXPERIMENTAL=true") + } + + cosignCmd.Args = append(cosignCmd.Args, rawRef) + + logrus.Debugf("running %s %v", cosignExecutable, cosignCmd.Args) + + stdout, _ := cosignCmd.StdoutPipe() + stderr, _ := cosignCmd.StderrPipe() + if err := cosignCmd.Start(); err != nil { + return err + } + + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + logrus.Info("cosign: " + scanner.Text()) + } + + errScanner := bufio.NewScanner(stderr) + for errScanner.Scan() { + logrus.Info("cosign: " + errScanner.Text()) + } + + if err := cosignCmd.Wait(); err != nil { + return err + } + + return nil } diff --git a/cmd/nerdctl/pull_linux_test.go b/cmd/nerdctl/pull_linux_test.go new file mode 100644 index 00000000000..64f107226cf --- /dev/null +++ b/cmd/nerdctl/pull_linux_test.go @@ -0,0 +1,120 @@ +/* + Copyright The containerd 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 main + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/containerd/nerdctl/pkg/testutil" + "gotest.tools/v3/assert" +) + +type cosignKeyPair struct { + publicKey string + privateKey string + cleanup func() +} + +func newCosignKeyPair(t testing.TB, path string) *cosignKeyPair { + td, err := os.MkdirTemp(t.TempDir(), path) + assert.NilError(t, err) + + cmd := exec.Command("cosign", "generate-key-pair") + cmd.Dir = td + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("failed to run %v: %v (%q)", cmd.Args, err, string(out)) + } + + publicKey := filepath.Join(td, "cosign.pub") + privateKey := filepath.Join(td, "cosign.key") + + return &cosignKeyPair{ + publicKey: publicKey, + privateKey: privateKey, + cleanup: func() { + _ = os.RemoveAll(td) + }, + } +} + +func TestImageVerifyWithCosign(t *testing.T) { + if _, err := exec.LookPath("cosign"); err != nil { + t.Skip() + } + testutil.DockerIncompatible(t) + t.Setenv("COSIGN_PASSWORD", "1") + keyPair := newCosignKeyPair(t, "cosign-key-pair") + defer keyPair.cleanup() + base := testutil.NewBase(t) + reg := newTestRegistry(base, "test-image-cosign") + defer reg.cleanup() + localhostIP := "127.0.0.1" + t.Logf("localhost IP=%q", localhostIP) + testImageRef := fmt.Sprintf("%s:%d/test-push-signed-image", + localhostIP, reg.listenPort) + t.Logf("testImageRef=%q", testImageRef) + + dockerfile := fmt.Sprintf(`FROM %s +CMD ["echo", "nerdctl-build-test-string"] + `, testutil.CommonImage) + + buildCtx, err := createBuildContext(dockerfile) + assert.NilError(t, err) + defer os.RemoveAll(buildCtx) + + base.Cmd("build", "-t", testImageRef, buildCtx).AssertOK() + base.Cmd("push", testImageRef, "--sign=cosign", "--cosign-key="+keyPair.privateKey).AssertOK() + base.Cmd("pull", testImageRef, "--verify=cosign", "--cosign-key="+keyPair.publicKey).AssertOK() +} + +func TestImageVerifyWithCosignShouldFailWhenKeyIsNotCorrect(t *testing.T) { + if _, err := exec.LookPath("cosign"); err != nil { + t.Skip() + } + testutil.DockerIncompatible(t) + t.Setenv("COSIGN_PASSWORD", "1") + keyPair := newCosignKeyPair(t, "cosign-key-pair") + defer keyPair.cleanup() + base := testutil.NewBase(t) + reg := newTestRegistry(base, "test-image-cosign") + defer reg.cleanup() + localhostIP := "127.0.0.1" + t.Logf("localhost IP=%q", localhostIP) + testImageRef := fmt.Sprintf("%s:%d/test-push-signed-image-wrong", + localhostIP, reg.listenPort) + t.Logf("testImageRef=%q", testImageRef) + + dockerfile := fmt.Sprintf(`FROM %s +CMD ["echo", "nerdctl-build-test-string"] + `, testutil.CommonImage) + + buildCtx, err := createBuildContext(dockerfile) + assert.NilError(t, err) + defer os.RemoveAll(buildCtx) + + base.Cmd("build", "-t", testImageRef, buildCtx).AssertOK() + base.Cmd("push", testImageRef, "--sign=cosign", "--cosign-key="+keyPair.privateKey).AssertOK() + base.Cmd("pull", testImageRef, "--verify=cosign", "--cosign-key="+keyPair.publicKey).AssertOK() + + t.Setenv("COSIGN_PASSWORD", "2") + newKeyPair := newCosignKeyPair(t, "cosign-key-pair-test") + base.Cmd("pull", testImageRef, "--verify=cosign", "--cosign-key="+newKeyPair.publicKey).AssertFail() +} diff --git a/cmd/nerdctl/push.go b/cmd/nerdctl/push.go index 0d1a24fe017..55bc36a8cf4 100644 --- a/cmd/nerdctl/push.go +++ b/cmd/nerdctl/push.go @@ -17,10 +17,13 @@ package main import ( + "bufio" "context" "errors" "fmt" "io" + "os" + "os/exec" "github.com/containerd/containerd/content" "github.com/containerd/containerd/images/converter" @@ -61,6 +64,11 @@ func newPushCommand() *cobra.Command { pushCommand.Flags().Bool("estargz", false, "Convert the image into eStargz") pushCommand.Flags().Bool("ipfs-ensure-image", true, "Ensure the entire contents of the image is locally available before push") + pushCommand.Flags().String("sign", "none", "Sign the image with none|cosign. Default none") + + pushCommand.Flags().String("cosign-key", "", + "path to the private key file, KMS URI or Kubernetes Secret") + return pushCommand } @@ -187,6 +195,30 @@ func pushAction(cmd *cobra.Command, args []string) error { return err } } + + signer, err := cmd.Flags().GetString("sign") + + if err != nil { + return err + } + switch signer { + case "cosign": + keyRef, err := cmd.Flags().GetString("cosign-key") + if err != nil { + return err + } + + err = signCosign(rawRef, keyRef) + if err != nil { + return err + } + case "none": + logrus.Debugf("signing process skipped") + default: + return fmt.Errorf("no signers found: %s", signer) + + } + return nil } @@ -235,3 +267,47 @@ func isReusableESGZ(ctx context.Context, cs content.Store, desc ocispec.Descript } return true } + +func signCosign(rawRef string, keyRef string) error { + cosignExecutable, err := exec.LookPath("cosign") + if err != nil { + logrus.WithError(err).Error("cosign executable not found in path $PATH") + logrus.Info("you might consider installing cosign from: https://docs.sigstore.dev/cosign/installation") + return err + } + + cosignCmd := exec.Command(cosignExecutable, []string{"sign"}...) + cosignCmd.Env = os.Environ() + + if keyRef != "" { + cosignCmd.Args = append(cosignCmd.Args, "--key", keyRef) + } else { + cosignCmd.Env = append(cosignCmd.Env, "COSIGN_EXPERIMENTAL=true") + } + + cosignCmd.Args = append(cosignCmd.Args, rawRef) + + logrus.Debugf("running %s %v", cosignExecutable, cosignCmd.Args) + + stdout, _ := cosignCmd.StdoutPipe() + stderr, _ := cosignCmd.StderrPipe() + if err := cosignCmd.Start(); err != nil { + return err + } + + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + logrus.Info("cosign: " + scanner.Text()) + } + + errScanner := bufio.NewScanner(stderr) + for errScanner.Scan() { + logrus.Info("cosign: " + errScanner.Text()) + } + + if err := cosignCmd.Wait(); err != nil { + return err + } + + return nil +} diff --git a/cmd/nerdctl/run_windows.go b/cmd/nerdctl/run_windows.go index 8aa98570008..409b55af17e 100644 --- a/cmd/nerdctl/run_windows.go +++ b/cmd/nerdctl/run_windows.go @@ -19,6 +19,7 @@ package main import ( "context" "fmt" + "github.com/containerd/containerd/containers" "github.com/containerd/containerd/oci" "github.com/docker/go-units" diff --git a/docs/cosign.md b/docs/cosign.md new file mode 100644 index 00000000000..e443561d6c3 --- /dev/null +++ b/docs/cosign.md @@ -0,0 +1,74 @@ +# Container Image Sign and Verify with cosign tool + +[cosign](https://github.com/sigstore/cosign) is tool that allows you to sign and verify container images with the +public/private key pairs or without them by providing +a [Keyless support](https://github.com/sigstore/cosign/blob/main/KEYLESS.md). + +Keyless uses ephemeral keys and certificates, which are signed automatically by +the [fulcio](https://github.com/sigstore/fulcio) root CA. Signatures are stored in +the [rekor](https://github.com/sigstore/rekor) transparency log, which automatically provides an attestation as to when +the signature was created. + +You can enable container signing and verifying features with `push` and `pull` commands of `nerdctl` by using `cosign` +under the hood with make use of flags `--sign` while pushing the container image, and `--verify` while pulling the +container image. + +> * Ensure cosign executable in your `$PATH`. +> * You can install cosign by following this page: https://docs.sigstore.dev/cosign/installation + +Prepare your environment: + +```shell +# Create a sample Dockerfile +$ cat < Please do not forget, we won't be validating the base images, which is `alpine:latest` in this case, of the container image that was built on, +> we'll only verify the container image itself once we sign it. + +```shell + +# Build the image +$ nerdctl build -t devopps/hello-world -f Dockerfile.dummy . + +# Generate a key-pair: cosign.key and cosign.pub +$ cosign generate-key-pair + +# Export your COSIGN_PASSWORD to prevent CLI prompting +$ export COSIGN_PASSWORD=$COSIGN_PASSWORD +``` + +Sign the container image while pushing: + +``` +# Sign the image with Keyless mode +$ nerdctl push --sign=cosign devopps/hello-world + +# Sign the image and store the signature in the registry +$ nerdctl push --sign=cosign --cosign-key cosign.key devopps/hello-world +``` + +Verify the container image while pulling: + +> REMINDER: Image won't be pulled if there are no matching signatures in case you passed `--verify` flag. + +```shell +# Verify the image with Keyless mode +$ nerdctl pull --verify=cosign devopps/hello-world +INFO[0004] cosign: +INFO[0004] cosign: [{"critical":{"identity":...}] +docker.io/devopps/nginx-new:latest: resolved |++++++++++++++++++++++++++++++++++++++| +manifest-sha256:0910d404e58dd320c3c0c7ea31bf5fbfe7544b26905c5eccaf87c3af7bcf9b88: done |++++++++++++++++++++++++++++++++++++++| +config-sha256:1de1c4fb5122ac8650e349e018fba189c51300cf8800d619e92e595d6ddda40e: done |++++++++++++++++++++++++++++++++++++++| +elapsed: 1.4 s total: 1.3 Ki (928.0 B/s) + +# You can not verify the image if it is not signed +$ nerdctl pull --verify=cosign --cosign-key cosign.pub devopps/hello-world-bad +INFO[0003] cosign: Error: no matching signatures: +INFO[0003] cosign: failed to verify signature +INFO[0003] cosign: main.go:46: error during command execution: no matching signatures: +INFO[0003] cosign: failed to verify signature +``` diff --git a/docs/experimental.md b/docs/experimental.md index f005a90346c..4b42dede8d4 100644 --- a/docs/experimental.md +++ b/docs/experimental.md @@ -7,3 +7,4 @@ The following features are experimental and subject to change: - Importing an external eStargz record JSON file with `nerdctl image convert --estargz-record-in=FILE` . eStargz itself is out of experimental. - [Image Distribution on IPFS](./ipfs.md) +- [Image Sign and Verify (cosign)](./cosign.md) diff --git a/pkg/imgutil/imgutil.go b/pkg/imgutil/imgutil.go index ea597d2ca33..890f676cfd4 100644 --- a/pkg/imgutil/imgutil.go +++ b/pkg/imgutil/imgutil.go @@ -161,6 +161,32 @@ func EnsureImage(ctx context.Context, client *containerd.Client, stdout, stderr return img, nil } +func ResolveDigest(ctx context.Context, rawRef string, insecure bool) (string, error) { + named, err := refdocker.ParseDockerRef(rawRef) + if err != nil { + return "", err + } + ref := named.String() + refDomain := refdocker.Domain(named) + + var dOpts []dockerconfigresolver.Opt + if insecure { + logrus.Warnf("skipping verifying HTTPS certs for %q", refDomain) + dOpts = append(dOpts, dockerconfigresolver.WithSkipVerifyCerts(true)) + } + resolver, err := dockerconfigresolver.New(refDomain, dOpts...) + if err != nil { + return "", err + } + + _, desc, err := resolver.Resolve(ctx, ref) + if err != nil { + return "", err + } + + return desc.Digest.String(), nil +} + // IsErrHTTPResponseToHTTPSClient returns whether err is // "http: server gave HTTP response to HTTPS client" func IsErrHTTPResponseToHTTPSClient(err error) bool { From 7df17e4381dd6b29ee506af50a4b5009dc85fe57 Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Fri, 10 Dec 2021 18:20:28 +0900 Subject: [PATCH 2/3] cosign: verify before pulling Signed-off-by: Akihiro Suda --- cmd/nerdctl/pull.go | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/cmd/nerdctl/pull.go b/cmd/nerdctl/pull.go index 39598347dfb..a28740e2794 100644 --- a/cmd/nerdctl/pull.go +++ b/cmd/nerdctl/pull.go @@ -23,6 +23,7 @@ import ( "fmt" "os" "os/exec" + "strings" "github.com/containerd/nerdctl/pkg/imgutil" "github.com/containerd/nerdctl/pkg/ipfs" @@ -112,7 +113,7 @@ func pullAction(cmd *cobra.Command, args []string) error { return err } - if scheme, ref, err := referenceutil.ParseIPFSRefWithScheme(args[0]); err == nil { + if scheme, ref, err := referenceutil.ParseIPFSRefWithScheme(rawRef); err == nil { if verifier != "none" { return errors.New("--verify flag is not supported on IPFS as of now") } @@ -126,13 +127,7 @@ func pullAction(cmd *cobra.Command, args []string) error { return err } - _, err = imgutil.EnsureImage(ctx, client, cmd.OutOrStdout(), cmd.ErrOrStderr(), snapshotter, args[0], - "always", insecure, ocispecPlatforms, unpack, quiet) - - if err != nil { - return err - } - + ref := rawRef switch verifier { case "cosign": keyRef, err := cmd.Flags().GetString("cosign-key") @@ -140,7 +135,8 @@ func pullAction(cmd *cobra.Command, args []string) error { return err } - if err := verifyCosign(ctx, rawRef, keyRef); err != nil { + ref, err = verifyCosign(ctx, rawRef, keyRef) + if err != nil { return err } case "none": @@ -148,24 +144,35 @@ func pullAction(cmd *cobra.Command, args []string) error { default: return fmt.Errorf("no verifier found: %s", verifier) } + + _, err = imgutil.EnsureImage(ctx, client, cmd.OutOrStdout(), cmd.ErrOrStderr(), snapshotter, ref, + "always", insecure, ocispecPlatforms, unpack, quiet) + + if err != nil { + return err + } + return nil } -func verifyCosign(ctx context.Context, rawRef string, keyRef string) error { +func verifyCosign(ctx context.Context, rawRef string, keyRef string) (string, error) { digest, err := imgutil.ResolveDigest(ctx, rawRef, false) - rawRef = rawRef + "@" + digest if err != nil { logrus.WithError(err).Errorf("unable to resolve digest for an image %s: %v", rawRef, err) - return err + return rawRef, err + } + ref := rawRef + if !strings.Contains(ref, "@") { + ref += "@" + digest } - logrus.Debugf("verifying image: %s", rawRef) + logrus.Debugf("verifying image: %s", ref) cosignExecutable, err := exec.LookPath("cosign") if err != nil { logrus.WithError(err).Error("cosign executable not found in path $PATH") logrus.Info("you might consider installing cosign from: https://docs.sigstore.dev/cosign/installation") - return err + return ref, err } cosignCmd := exec.Command(cosignExecutable, []string{"verify"}...) @@ -177,14 +184,14 @@ func verifyCosign(ctx context.Context, rawRef string, keyRef string) error { cosignCmd.Env = append(cosignCmd.Env, "COSIGN_EXPERIMENTAL=true") } - cosignCmd.Args = append(cosignCmd.Args, rawRef) + cosignCmd.Args = append(cosignCmd.Args, ref) logrus.Debugf("running %s %v", cosignExecutable, cosignCmd.Args) stdout, _ := cosignCmd.StdoutPipe() stderr, _ := cosignCmd.StderrPipe() if err := cosignCmd.Start(); err != nil { - return err + return ref, err } scanner := bufio.NewScanner(stdout) @@ -198,8 +205,8 @@ func verifyCosign(ctx context.Context, rawRef string, keyRef string) error { } if err := cosignCmd.Wait(); err != nil { - return err + return ref, err } - return nil + return ref, nil } From 3dda9f830b23a1b287065ff7cd88531e06b31683 Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Fri, 10 Dec 2021 18:31:15 +0900 Subject: [PATCH 3/3] cosign: fix docs and shell completions Signed-off-by: Akihiro Suda --- README.md | 7 ++++++- cmd/nerdctl/pull.go | 13 +++++++++---- cmd/nerdctl/push.go | 11 +++++++---- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index fed760cc84b..a93c5f3d432 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,7 @@ Major: - [P2P image distribution using IPFS](./docs/ipfs.md): `nerdctl run ipfs://CID` - Recursive read-only (RRO) bind-mount: `nerdctl run -v /mnt:/mnt:rro` (make children such as `/mnt/usb` to be read-only, too). Requires kernel >= 5.12, and crun >= 1.4 or runc >= 1.1 (PR [#3272](https://github.com/opencontainers/runc/pull/3272)). +- [Cosign integration](./docs/cosign.md): `nerdctl pull --verify=cosign` and `nerdctl push --sign=cosign` Minor: - Namespacing: `nerdctl --namespace= ps` . @@ -718,6 +719,8 @@ Flags: - :nerd_face: `--all-platforms`: Pull content for all platforms - :nerd_face: `--unpack`: Unpack the image for the current single platform (auto/true/false) - :whale: `-q, --quiet`: Suppress verbose output +- :nerd_face: `--verify`: Verify the image (none|cosign). See [`docs/cosign.md`](./docs/cosign.md) for details. +- :nerd_face: `--cosign-key`: Path to the public key file, KMS, URI or Kubernetes Secret for `--verify=cosign` Unimplemented `docker pull` flags: `--all-tags`, `--disable-content-trust` (default true) @@ -731,6 +734,8 @@ Usage: `nerdctl push [OPTIONS] NAME[:TAG]` Flags: - :nerd_face: `--platform=(amd64|arm64|...)`: Push content for a specific platform - :nerd_face: `--all-platforms`: Push content for all platforms +- :nerd_face: `--sign`: Sign the image (none|cosign). See [`docs/cosign.md`](./docs/cosign.md) for details. +- :nerd_face: `--cosign-key`: Path to the private key file, KMS, URI or Kubernetes Secret for `--sign=cosign` Unimplemented `docker push` flags: `--all-tags`, `--disable-content-trust` (default true), `--quiet` @@ -1222,7 +1227,7 @@ Image: - `docker image prune` -- `docker trust *` +- `docker trust *` (Instead, nerdctl supports `nerdctl pull --verify=cosign` and `nerdctl push --sign=cosign`. See [`./docs/cosign.md`](docs/cosign.md).) - `docker manifest *` Network management: diff --git a/cmd/nerdctl/pull.go b/cmd/nerdctl/pull.go index a28740e2794..6ffcd9140b6 100644 --- a/cmd/nerdctl/pull.go +++ b/cmd/nerdctl/pull.go @@ -45,9 +45,6 @@ func newPullCommand() *cobra.Command { SilenceErrors: true, } pullCommand.Flags().String("unpack", "auto", "Unpack the image for the current single platform (auto/true/false)") - pullCommand.Flags().String("cosign-key", "", - "path to the public key file, KMS, URI or Kubernetes Secret") - pullCommand.RegisterFlagCompletionFunc("unpack", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return []string{"auto", "true", "false"}, cobra.ShellCompDirectiveNoFileComp }) @@ -57,8 +54,16 @@ func newPullCommand() *cobra.Command { pullCommand.Flags().StringSlice("platform", nil, "Pull content for a specific platform") pullCommand.RegisterFlagCompletionFunc("platform", shellCompletePlatforms) pullCommand.Flags().Bool("all-platforms", false, "Pull content for all platforms") - pullCommand.Flags().String("verify", "none", "Verify the image with none|cosign. Default none") // #endregion + + // #region verify flags + pullCommand.Flags().String("verify", "none", "Verify the image (none|cosign)") + pullCommand.RegisterFlagCompletionFunc("verify", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{"none", "cosign"}, cobra.ShellCompDirectiveNoFileComp + }) + pullCommand.Flags().String("cosign-key", "", "Path to the public key file, KMS, URI or Kubernetes Secret for --verify=cosign") + // #endregion + pullCommand.Flags().BoolP("quiet", "q", false, "Suppress verbose output") return pullCommand diff --git a/cmd/nerdctl/push.go b/cmd/nerdctl/push.go index 55bc36a8cf4..2c8ec4379c1 100644 --- a/cmd/nerdctl/push.go +++ b/cmd/nerdctl/push.go @@ -64,10 +64,13 @@ func newPushCommand() *cobra.Command { pushCommand.Flags().Bool("estargz", false, "Convert the image into eStargz") pushCommand.Flags().Bool("ipfs-ensure-image", true, "Ensure the entire contents of the image is locally available before push") - pushCommand.Flags().String("sign", "none", "Sign the image with none|cosign. Default none") - - pushCommand.Flags().String("cosign-key", "", - "path to the private key file, KMS URI or Kubernetes Secret") + // #region sign flags + pushCommand.Flags().String("sign", "none", "Sign the image (none|cosign") + pushCommand.RegisterFlagCompletionFunc("sign", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{"none", "cosign"}, cobra.ShellCompDirectiveNoFileComp + }) + pushCommand.Flags().String("cosign-key", "", "Path to the private key file, KMS URI or Kubernetes Secret for --sign=cosign") + // #endregion return pushCommand }