Skip to content

Commit

Permalink
Add cosign in compose run, up, pull, push
Browse files Browse the repository at this point in the history
Signed-off-by: Jin Dong <[email protected]>

Add compose cosign test

Signed-off-by: Jin Dong <[email protected]>

Fix naming bug

Signed-off-by: Jin Dong <[email protected]>

Add compose cosign test [pass]

Signed-off-by: Jin Dong <[email protected]>

Add compose experimental flag

Signed-off-by: Jin Dong <[email protected]>

Refactor cosign func

Signed-off-by: Jin Dong <[email protected]>

Add compose cosign doc

Signed-off-by: Jin Dong <[email protected]>
  • Loading branch information
djdongjin committed Nov 16, 2022
1 parent e29e3e2 commit 2a47031
Show file tree
Hide file tree
Showing 12 changed files with 420 additions and 128 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ Major:
P2P image distribution (IPFS) is completely optional. Your host is NOT connected to any P2P network, unless you opt in to [install and run IPFS daemon](https://docs.ipfs.io/install/).
- 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`
- [Cosign integration](./docs/cosign.md): `nerdctl pull --verify=cosign` and `nerdctl push --sign=cosign`, and [in Compose](./docs/cosign.md#cosign-in-compose)
- [Accelerated rootless containers using bypass4netns](./docs/rootless.md): `nerdctl run --label nerdctl/bypass4netns=true`

Minor:
Expand Down
47 changes: 40 additions & 7 deletions cmd/nerdctl/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,21 @@ package main
import (
"context"
"errors"
"fmt"

"github.com/containerd/containerd"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/platforms"
"github.com/containerd/nerdctl/pkg/composer"
"github.com/containerd/nerdctl/pkg/composer/serviceparser"
"github.com/containerd/nerdctl/pkg/cosignutil"
"github.com/containerd/nerdctl/pkg/imgutil"
"github.com/containerd/nerdctl/pkg/ipfs"
"github.com/containerd/nerdctl/pkg/netutil"
"github.com/containerd/nerdctl/pkg/referenceutil"
httpapi "github.com/ipfs/go-ipfs-http-client"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"

"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -109,6 +113,10 @@ func getComposer(cmd *cobra.Command, client *containerd.Client) (*composer.Compo
if err != nil {
return nil, err
}
experimental, err := cmd.Flags().GetBool("experimental")
if err != nil {
return nil, err
}

o := composer.Options{
Project: projectName,
Expand All @@ -118,6 +126,7 @@ func getComposer(cmd *cobra.Command, client *containerd.Client) (*composer.Compo
NerdctlCmd: nerdctlCmd,
NerdctlArgs: nerdctlArgs,
DebugPrintFull: debugFull,
Experimental: experimental,
}

cniEnv, err := netutil.NewCNIEnv(cniPath, cniNetconfpath)
Expand Down Expand Up @@ -164,7 +173,7 @@ func getComposer(cmd *cobra.Command, client *containerd.Client) (*composer.Compo
return true, nil
}

o.EnsureImage = func(ctx context.Context, imageName, pullMode, platform string, quiet bool) error {
o.EnsureImage = func(ctx context.Context, imageName, pullMode, platform string, ps *serviceparser.Service, quiet bool) error {
ocispecPlatforms := []ocispec.Platform{platforms.DefaultSpec()}
if platform != "" {
parsed, err := platforms.Parse(platform)
Expand All @@ -173,19 +182,43 @@ func getComposer(cmd *cobra.Command, client *containerd.Client) (*composer.Compo
}
ocispecPlatforms = []ocispec.Platform{parsed} // no append
}
var imgErr error

// IPFS reference
if scheme, ref, err := referenceutil.ParseIPFSRefWithScheme(imageName); err == nil {
ipfsClient, err := httpapi.NewLocalApi()
if err != nil {
return err
}
_, imgErr = ipfs.EnsureImage(ctx, client, ipfsClient, cmd.OutOrStdout(), cmd.ErrOrStderr(), snapshotter, scheme, ref,
_, err = ipfs.EnsureImage(ctx, client, ipfsClient, cmd.OutOrStdout(), cmd.ErrOrStderr(), snapshotter, scheme, ref,
pullMode, ocispecPlatforms, nil, quiet)
} else {
_, imgErr = imgutil.EnsureImage(ctx, client, cmd.OutOrStdout(), cmd.ErrOrStderr(), snapshotter, imageName,
pullMode, insecure, hostsDirs, ocispecPlatforms, nil, quiet)
return err
}

ref := imageName
if verifier, ok := ps.Unparsed.Extensions[serviceparser.ComposeVerify]; ok {
switch verifier {
case "cosign":
if !o.Experimental {
return fmt.Errorf("cosign only work with enable experimental feature")
}
keyRef, ok := ps.Unparsed.Extensions[serviceparser.ComposeCosignPublicKey]
if !ok {
return fmt.Errorf("no cosign public key, service: %s", ps.Unparsed.Name)
}

ref, err = cosignutil.VerifyCosign(ctx, ref, keyRef.(string), hostsDirs)
if err != nil {
return err
}
case "none":
logrus.Debugf("verification process skipped")
default:
return fmt.Errorf("no verifier found: %s", verifier)
}
}
return imgErr
_, err = imgutil.EnsureImage(ctx, client, cmd.OutOrStdout(), cmd.ErrOrStderr(), snapshotter, ref,
pullMode, insecure, hostsDirs, ocispecPlatforms, nil, quiet)
return err
}

return composer.New(o, client)
Expand Down
89 changes: 89 additions & 0 deletions cmd/nerdctl/compose_run_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ package main
import (
"fmt"
"io"
"os/exec"
"strings"
"testing"
"time"

"github.com/containerd/nerdctl/pkg/testutil"
"github.com/containerd/nerdctl/pkg/testutil/nettestutil"
"github.com/containerd/nerdctl/pkg/testutil/testregistry"
"github.com/sirupsen/logrus"
"gotest.tools/v3/assert"
)
Expand Down Expand Up @@ -430,3 +432,90 @@ services:
assert.Assert(t, container.Mounts[0].Source == tmpDir, errMsg)
assert.Assert(t, container.Mounts[0].Destination == destinationDir, errMsg)
}

func TestComposePushAndPullWithCosignVerify(t *testing.T) {
if _, err := exec.LookPath("cosign"); err != nil {
t.Skip()
}
testutil.DockerIncompatible(t)
testutil.RequiresBuild(t)
base := testutil.NewBase(t)
defer base.Cmd("builder", "prune").Run()

// set up cosign and local registry
t.Setenv("COSIGN_PASSWORD", "1")
keyPair := newCosignKeyPair(t, "cosign-key-pair")
defer keyPair.cleanup()

reg := testregistry.NewPlainHTTP(base, 5000)
defer reg.Cleanup()
localhostIP := "127.0.0.1"
t.Logf("localhost IP=%q", localhostIP)
testImageRefPrefix := fmt.Sprintf("%s:%d/",
localhostIP, reg.ListenPort)
t.Logf("testImageRefPrefix=%q", testImageRefPrefix)

var (
imageSvc0 = testImageRefPrefix + "composebuild_svc0"
imageSvc1 = testImageRefPrefix + "composebuild_svc1"
imageSvc2 = testImageRefPrefix + "composebuild_svc2"
)

dockerComposeYAML := fmt.Sprintf(`
services:
svc0:
build: .
image: %s
x-nerdctl-verify: cosign
x-nerdctl-cosign-public-key: %s
x-nerdctl-sign: cosign
x-nerdctl-cosign-private-key: %s
entrypoint:
- stty
svc1:
build: .
image: %s
x-nerdctl-verify: cosign
x-nerdctl-cosign-public-key: dummy_pub_key
x-nerdctl-sign: cosign
x-nerdctl-cosign-private-key: %s
entrypoint:
- stty
svc2:
build: .
image: %s
x-nerdctl-verify: none
x-nerdctl-sign: none
entrypoint:
- stty
`, imageSvc0, keyPair.publicKey, keyPair.privateKey,
imageSvc1, keyPair.privateKey, imageSvc2)

dockerfile := fmt.Sprintf(`FROM %s`, testutil.AlpineImage)

comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()
comp.WriteFile("Dockerfile", dockerfile)

// 1. build both services/images
base.ComposeCmd("-f", comp.YAMLFullPath(), "build").AssertOK()
defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run()
// 2. compose push with cosign for svc0/svc1, (and none for svc2)
base.ComposeCmd("-f", comp.YAMLFullPath(), "push").AssertOK()
// 3. compose pull with cosign
base.ComposeCmd("-f", comp.YAMLFullPath(), "pull", "svc0").AssertOK() // key match
base.ComposeCmd("-f", comp.YAMLFullPath(), "pull", "svc1").AssertFail() // key mismatch
base.ComposeCmd("-f", comp.YAMLFullPath(), "pull", "svc2").AssertOK() // verify passed
// 4. compose run
const sttyPartialOutput = "speed 38400 baud"
// unbuffer(1) emulates tty, which is required by `nerdctl run -t`.
// unbuffer(1) can be installed with `apt-get install expect`.
unbuffer := []string{"unbuffer"}
base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), "run", "svc0").AssertOutContains(sttyPartialOutput) // key match
base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), "run", "svc1").AssertFail() // key mismatch
base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), "run", "svc2").AssertOutContains(sttyPartialOutput) // verify passed
// 5. compose up
base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "svc0").AssertOK() // key match
base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "svc1").AssertFail() // key mismatch
base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "svc2").AssertOK() // verify passed
}
63 changes: 2 additions & 61 deletions cmd/nerdctl/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,12 @@
package main

import (
"bufio"
"context"
"errors"
"fmt"
"os"
"os/exec"
"strings"

"github.com/containerd/containerd"
"github.com/containerd/nerdctl/pkg/cosignutil"
"github.com/containerd/nerdctl/pkg/imgutil"
"github.com/containerd/nerdctl/pkg/ipfs"
"github.com/containerd/nerdctl/pkg/platformutil"
Expand Down Expand Up @@ -168,7 +165,7 @@ func ensureImage(cmd *cobra.Command, ctx context.Context, client *containerd.Cli
return nil, err
}

ref, err = verifyCosign(ctx, rawRef, keyRef, hostsDirs)
ref, err = cosignutil.VerifyCosign(ctx, rawRef, keyRef, hostsDirs)
if err != nil {
return nil, err
}
Expand All @@ -185,59 +182,3 @@ func ensureImage(cmd *cobra.Command, ctx context.Context, client *containerd.Cli
}
return ensured, err
}

func verifyCosign(ctx context.Context, rawRef string, keyRef string, hostsDirs []string) (string, error) {
digest, err := imgutil.ResolveDigest(ctx, rawRef, false, hostsDirs)
if err != nil {
logrus.WithError(err).Errorf("unable to resolve digest for an image %s: %v", rawRef, err)
return rawRef, err
}
ref := rawRef
if !strings.Contains(ref, "@") {
ref += "@" + digest
}

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 ref, 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, ref)

logrus.Debugf("running %s %v", cosignExecutable, cosignCmd.Args)

stdout, _ := cosignCmd.StdoutPipe()
stderr, _ := cosignCmd.StderrPipe()
if err := cosignCmd.Start(); err != nil {
return ref, 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 ref, err
}

return ref, nil
}
50 changes: 2 additions & 48 deletions cmd/nerdctl/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,16 @@
package main

import (
"bufio"
"context"
"fmt"
"io"
"os"
"os/exec"

"github.com/containerd/containerd/content"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/images/converter"
refdocker "github.com/containerd/containerd/reference/docker"
"github.com/containerd/containerd/remotes"
"github.com/containerd/nerdctl/pkg/cosignutil"
"github.com/containerd/nerdctl/pkg/errutil"
"github.com/containerd/nerdctl/pkg/imgutil/dockerconfigresolver"
"github.com/containerd/nerdctl/pkg/imgutil/push"
Expand Down Expand Up @@ -254,7 +252,7 @@ func pushAction(cmd *cobra.Command, args []string) error {
return err
}

err = signCosign(rawRef, keyRef)
err = cosignutil.SignCosign(rawRef, keyRef)
if err != nil {
return err
}
Expand Down Expand Up @@ -313,47 +311,3 @@ 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
}
Loading

0 comments on commit 2a47031

Please sign in to comment.