From ac34df319a4756bc1597e60dc3666094dfd146d3 Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Fri, 4 Jun 2021 15:56:59 -0700 Subject: [PATCH 1/2] docker socket forwarding support - This PR adds a new mount type: `docker.sock` (in addition to `cache` and `tmp`) - It's then able to mount the LOCAL (as in, from the machine running dagger) docker socket inside the container by pretending to be an SSH Agent (hijacking the SSH agent forwarding support of buildkit) Signed-off-by: Andrea Luzzardi --- client/client.go | 6 ++- environment/pipeline.go | 5 +++ solver/dockersocketprovider.go | 62 +++++++++++++++++++++++++++++ solver/solver.go | 6 ++- stdlib/dagger/op/op.cue | 2 +- tests/compute.bats | 8 +++- tests/compute/dockersocket/main.cue | 18 +++++++++ 7 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 solver/dockersocketprovider.go create mode 100644 tests/compute/dockersocket/main.cue diff --git a/client/client.go b/client/client.go index 74e5e723..644c47f2 100644 --- a/client/client.go +++ b/client/client.go @@ -114,7 +114,11 @@ func (c *Client) buildfn(ctx context.Context, st *state.State, env *environment. // Setup solve options opts := bk.SolveOpt{ LocalDirs: localdirs, - Session: []session.Attachable{auth, secrets}, + Session: []session.Attachable{ + auth, + secrets, + solver.NewDockerSocketProvider(), + }, } // Call buildkit solver diff --git a/environment/pipeline.go b/environment/pipeline.go index 08c4f74b..d00793aa 100644 --- a/environment/pipeline.go +++ b/environment/pipeline.go @@ -468,6 +468,11 @@ func (p *Pipeline) mount(ctx context.Context, dest string, mnt *compiler.Value) llb.Scratch(), llb.Tmpfs(), ), nil + case "docker.sock": + return llb.AddSSHSocket( + llb.SSHID(solver.DockerSocketID), + llb.SSHSocketTarget(dest), + ), nil default: return nil, fmt.Errorf("invalid mount source: %q", s) } diff --git a/solver/dockersocketprovider.go b/solver/dockersocketprovider.go new file mode 100644 index 00000000..6eb79091 --- /dev/null +++ b/solver/dockersocketprovider.go @@ -0,0 +1,62 @@ +package solver + +import ( + "context" + "fmt" + "net" + "time" + + "github.com/moby/buildkit/session" + "github.com/moby/buildkit/session/sshforward" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +const ( + DockerSocketID = "docker.sock" + DockerSocketPath = "/var/run/docker.sock" +) + +type DockerSocketProvider struct { +} + +func NewDockerSocketProvider() session.Attachable { + return &DockerSocketProvider{} +} + +func (sp *DockerSocketProvider) Register(server *grpc.Server) { + sshforward.RegisterSSHServer(server, sp) +} + +func (sp *DockerSocketProvider) CheckAgent(ctx context.Context, req *sshforward.CheckAgentRequest) (*sshforward.CheckAgentResponse, error) { + id := sshforward.DefaultID + if req.ID != "" { + id = req.ID + } + if id != DockerSocketID { + return &sshforward.CheckAgentResponse{}, fmt.Errorf("invalid socket forward key %s", id) + } + return &sshforward.CheckAgentResponse{}, nil +} + +func (sp *DockerSocketProvider) ForwardAgent(stream sshforward.SSH_ForwardAgentServer) error { + id := sshforward.DefaultID + + opts, _ := metadata.FromIncomingContext(stream.Context()) // if no metadata continue with empty object + + if v, ok := opts[sshforward.KeySSHID]; ok && len(v) > 0 && v[0] != "" { + id = v[0] + } + + if id != DockerSocketID { + return fmt.Errorf("invalid socket forward key %s", id) + } + + conn, err := net.DialTimeout("unix", DockerSocketPath, time.Second) + if err != nil { + return fmt.Errorf("failed to connect to %s: %w", DockerSocketPath, err) + } + defer conn.Close() + + return sshforward.Copy(context.TODO(), conn, stream, nil) +} diff --git a/solver/solver.go b/solver/solver.go index 7ec67cf8..1df62ac5 100644 --- a/solver/solver.go +++ b/solver/solver.go @@ -157,7 +157,11 @@ func (s Solver) Export(ctx context.Context, st llb.State, img *dockerfile2llb.Im opts := bk.SolveOpt{ Exports: []bk.ExportEntry{output}, - Session: []session.Attachable{s.opts.Auth, s.opts.Secrets}, + Session: []session.Attachable{ + s.opts.Auth, + s.opts.Secrets, + NewDockerSocketProvider(), + }, } ch := make(chan *bk.SolveStatus) diff --git a/stdlib/dagger/op/op.cue b/stdlib/dagger/op/op.cue index aeb94423..1eed447f 100644 --- a/stdlib/dagger/op/op.cue +++ b/stdlib/dagger/op/op.cue @@ -52,7 +52,7 @@ package op // `true` means also ignoring the mount cache volumes always?: true | *false dir: string | *"/" - mount: [string]: "tmpfs" | "cache" | {from: _, path: string | *"/"} | {secret: _} + mount: [string]: "tmpfs" | "cache" | "docker.sock" | {from: _, path: string | *"/"} | {secret: _} // Map of hostnames to ip hosts?: [string]: string // User to exec with (if left empty, will default to the set user in the image) diff --git a/tests/compute.bats b/tests/compute.bats index 27b17ce1..17e45ad7 100644 --- a/tests/compute.bats +++ b/tests/compute.bats @@ -65,11 +65,11 @@ setup() { run "$DAGGER" compute --input-string 'in=foobar' "$TESTDIR"/compute/input/default assert_success assert_line '{"in":"foobar","test":"received: foobar"}' - + run "$DAGGER" compute --input-string=foobar "$TESTDIR"/compute/input/default assert_failure assert_output --partial 'failed to parse input: input-string' - + run "$DAGGER" compute --input-dir=foobar "$TESTDIR"/compute/input/default assert_failure assert_output --partial 'failed to parse input: input-dir' @@ -106,6 +106,10 @@ setup() { assert_output "secret=mySecret" } +@test "compute: docker socket" { + run "$DAGGER" compute "$TESTDIR"/compute/dockersocket +} + @test "compute: exclude" { "$DAGGER" up -w "$TESTDIR"/compute/exclude } diff --git a/tests/compute/dockersocket/main.cue b/tests/compute/dockersocket/main.cue new file mode 100644 index 00000000..7b97f7b2 --- /dev/null +++ b/tests/compute/dockersocket/main.cue @@ -0,0 +1,18 @@ +package main + +import ( + "dagger.io/dagger/op" + "dagger.io/docker" +) + +TestDockerSocket: #up: [ + op.#Load & { + from: docker.#Client + }, + + op.#Exec & { + always: true + mount: "/var/run/docker.sock": "docker.sock" + args: ["docker", "info"] + }, +] From 2671e5f321f0b069cd60f78d3e5ecb6b36c98a83 Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Mon, 7 Jun 2021 13:18:46 -0700 Subject: [PATCH 2/2] disable docker socket support until we have security in place Signed-off-by: Andrea Luzzardi --- environment/pipeline.go | 5 ----- stdlib/dagger/op/op.cue | 2 +- tests/compute.bats | 1 + 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/environment/pipeline.go b/environment/pipeline.go index d00793aa..08c4f74b 100644 --- a/environment/pipeline.go +++ b/environment/pipeline.go @@ -468,11 +468,6 @@ func (p *Pipeline) mount(ctx context.Context, dest string, mnt *compiler.Value) llb.Scratch(), llb.Tmpfs(), ), nil - case "docker.sock": - return llb.AddSSHSocket( - llb.SSHID(solver.DockerSocketID), - llb.SSHSocketTarget(dest), - ), nil default: return nil, fmt.Errorf("invalid mount source: %q", s) } diff --git a/stdlib/dagger/op/op.cue b/stdlib/dagger/op/op.cue index 1eed447f..aeb94423 100644 --- a/stdlib/dagger/op/op.cue +++ b/stdlib/dagger/op/op.cue @@ -52,7 +52,7 @@ package op // `true` means also ignoring the mount cache volumes always?: true | *false dir: string | *"/" - mount: [string]: "tmpfs" | "cache" | "docker.sock" | {from: _, path: string | *"/"} | {secret: _} + mount: [string]: "tmpfs" | "cache" | {from: _, path: string | *"/"} | {secret: _} // Map of hostnames to ip hosts?: [string]: string // User to exec with (if left empty, will default to the set user in the image) diff --git a/tests/compute.bats b/tests/compute.bats index 17e45ad7..e5e70826 100644 --- a/tests/compute.bats +++ b/tests/compute.bats @@ -107,6 +107,7 @@ setup() { } @test "compute: docker socket" { + skip "docker socket support disabled" run "$DAGGER" compute "$TESTDIR"/compute/dockersocket }