diff --git a/Makefile b/Makefile index 5a78eaf04..b336159f6 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ PKG := github.com/genuinetools/$(NAME) CGO_ENABLED := 1 # Set any default go build tags -BUILDTAGS ?= seccomp osusergo +BUILDTAGS ?= seccomp osusergo dfrunmount dfsecrets dfssh include basic.mk diff --git a/build.go b/build.go index 45e4ba235..639201aa1 100644 --- a/build.go +++ b/build.go @@ -30,6 +30,7 @@ import ( "github.com/moby/buildkit/identity" "github.com/moby/buildkit/session" "github.com/moby/buildkit/session/filesync" + "github.com/moby/buildkit/session/sshforward/sshprovider" "github.com/moby/buildkit/util/appcontext" "github.com/moby/buildkit/util/progress/progressui" "github.com/spf13/cobra" @@ -45,6 +46,8 @@ func newBuildCommand() *cobra.Command { tags: newListValue().WithValidator(validateTag), buildArgs: newListValue(), labels: newListValue(), + secrets: newListValue(), + ssh: newListValue(), platforms: newListValue(), cacheFrom: newListValue(), cacheTo: newListValue(), @@ -70,6 +73,8 @@ func newBuildCommand() *cobra.Command { fs.Var(build.platforms, "platform", "Set platforms for which the image should be built") fs.Var(build.buildArgs, "build-arg", "Set build-time variables") fs.Var(build.labels, "label", "Set metadata for an image") + fs.Var(build.secrets, "secret", "Secret value exposed to the build. Format id=secretname,src=filepath") + fs.Var(build.ssh, "ssh", "Allow forwarding SSH agent to the builder. Format default|[=|[,]]") fs.BoolVar(&build.noConsole, "no-console", false, "Use non-console progress UI") fs.BoolVar(&build.noCache, "no-cache", false, "Do not use cache when building the image") fs.StringVarP(&build.output, "output", "o", "", "BuildKit output specification (e.g. type=tar,dest=build.tar)") @@ -82,6 +87,8 @@ type buildCommand struct { buildArgs *listValue dockerfilePath string labels *listValue + secrets *listValue + ssh *listValue target string tags *listValue platforms *listValue @@ -257,6 +264,27 @@ func (cmd *buildCommand) Run(args []string) (err error) { } } + // parse secrets (--secret) + if cmd.secrets.Len() > 0 { + secretProvider, err := build.ParseSecret(cmd.secrets.GetAll()) + if err != nil { + return fmt.Errorf("could not parse secrets: %v", err) + } + sess.Allow(secretProvider) + } + // parse ssh (--ssh) + if cmd.ssh.Len() > 0 { + configs, err := build.ParseSSH(cmd.ssh.GetAll()) + if err != nil { + return fmt.Errorf("could not parse ssh: %v", err) + } + sp, err := sshprovider.NewSSHAgentProvider(configs) + if err != nil { + return err + } + sess.Allow(sp) + } + ch := make(chan *controlapi.StatusResponse) eg.Go(func() error { return sess.Run(ctx, sessDialer) diff --git a/build_test.go b/build_test.go index bd5fd0a4a..d9a836cd1 100644 --- a/build_test.go +++ b/build_test.go @@ -3,6 +3,10 @@ package main import ( "archive/tar" "bytes" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" "fmt" "io" "io/ioutil" @@ -134,6 +138,93 @@ func TestBuildLabels(t *testing.T) { } } +func TestBuildMultipleSecrets(t *testing.T) { + name := "testbuildmustiplesecrets" + + args := []string{"build", "-t", name, "--secret", "id=s1,src=/dev/null", "--secret", "id=s2,src=/dev/null", "-"} + _, err := doRun(args, withDockerfile(` + FROM alpine + RUN --mount=type=secret,id=s1,dst=/tmp/secret1 \ + --mount=type=secret,id=s2,dst=/tmp/secret2 \ + cat /tmp/secret1 /tmp/secret2 + `)) + + if err != nil { + t.Logf("img %v failed unexpectedly: %v", args, err) + t.FailNow() + } +} + +// generatePrivateKey creates a RSA Private Key of specified byte size in PEM format +func generatePrivateKeyPEM(bitSize int) ([]byte, error) { + // Private Key generation + privateKey, err := rsa.GenerateKey(rand.Reader, bitSize) + if err != nil { + return nil, err + } + + // Validate Private Key + err = privateKey.Validate() + if err != nil { + return nil, err + } + + // Get ASN.1 DER format + privDER := x509.MarshalPKCS1PrivateKey(privateKey) + + // pem.Block + privBlock := pem.Block{ + Type: "RSA PRIVATE KEY", + Headers: nil, + Bytes: privDER, + } + + // Private key in PEM format + privatePEM := pem.EncodeToMemory(&privBlock) + + return privatePEM, nil +} + +func TestBuildSsh(t *testing.T) { + name := "testbuildssh" + + tmpf, err := ioutil.TempFile("", "id_rsa_test") + if err != nil { + t.Fatalf("creating temporary file for ssh private key failed: %v", err) + } + + defer os.Remove(tmpf.Name()) + + err = tmpf.Chmod(0600) + if err != nil { + t.Fatalf("changing file mode failed: %v", err) + } + + privatePEM, err := generatePrivateKeyPEM(4096) + if err != nil { + t.Fatalf("generating private key failed: %v", err) + } + + _, err = tmpf.Write(privatePEM) + if err != nil { + t.Fatalf("writing private key to temporary file failed: %v", err) + } + + args := []string{"build", "-t", name, "--ssh", fmt.Sprintf("key=%s", tmpf.Name()), "-"} + _, err = doRun(args, withDockerfile(` + FROM alpine + RUN apk add openssh-client + RUN test -z "$SSH_AUTH_SOCK" && echo "Socket is absent as expected" + RUN --mount=type=ssh,id=absent ssh-add -l; test 0 -ne "$?" + RUN --mount=type=ssh,id=key ssh-add -l; test 0 -eq "$?" + `)) + + if err != nil { + t.Logf("img %v failed unexpectedly: %v", args, err) + t.FailNow() + } +} + func TestBuildMultipleTags(t *testing.T) { names := []string{"testbuildmultipletags", "testbuildmultipletags:v1", "testbuildmultipletagsv1"} args := []string{"build"} diff --git a/main_test.go b/main_test.go index c9fa1eb93..6c55ed618 100644 --- a/main_test.go +++ b/main_test.go @@ -32,7 +32,7 @@ func init() { // deletes it after the tests have been run. func TestMain(m *testing.M) { os.Unsetenv("IMG_RUNNING_TESTS") - args := []string{"build", "-o", "testimg" + exeSuffix} + args := []string{"build", "-tags", "dfrunmount dfsecrets dfssh", "-o", "testimg" + exeSuffix} out, err := exec.Command("go", args...).CombinedOutput() if err != nil { fmt.Fprintf(os.Stderr, "building testimg failed: %v\n%s\n", err, out)