Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cosign in compose #1508

Merged
merged 2 commits into from
Nov 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
49 changes: 42 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,45 @@ 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")
}

// if key is given, use key mode, otherwise use keyless mode.
keyRef := ""
if keyVal, ok := ps.Unparsed.Extensions[serviceparser.ComposeCosignPublicKey]; ok {
keyRef = keyVal.(string)
}

ref, err = cosignutil.VerifyCosign(ctx, ref, keyRef, 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`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to check the TTY functionality here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I followed compose run testcases in the same folder. Seems they all use this scenario. Should I change to something else? thanks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just "echo hi" should suffice, unless the cosign stuff relates to tty

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested echo hi but seems have some issue in test(below, maybe due to some unimplemented feature in compose run as mentioned in #1340).

If it's okay I'll keep this test consistent with other compose run testcases (i.e., use unbuffer) in this file.

time="2022-11-17T01:49:58Z" level=fatal msg="error while creating container nerdctl-compose-test2565186661_svc0_run_6af98b73d3b0: exit status 2"

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