Skip to content

[workspacekit] Establish IWS conn for proc mounts #5407

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

Merged
merged 2 commits into from
Aug 27, 2021
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
8 changes: 4 additions & 4 deletions components/workspacekit/cmd/rings.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,12 +361,10 @@ var ring1Cmd = &cobra.Command{
log.WithError(err).Error("cannot connect to daemon")
return
}

_, err = client.MountProc(ctx, &daemonapi.MountProcRequest{
Target: procLoc,
Pid: int64(cmd.Process.Pid),
})

client.Close()

if err != nil {
Expand Down Expand Up @@ -442,8 +440,10 @@ var ring1Cmd = &cobra.Command{
log.Warn("received 0 as ring2 seccomp fd - syscall handling is broken")
} else {
handler := &seccomp.InWorkspaceHandler{
FD: scmpfd,
Daemon: client,
FD: scmpfd,
Daemon: func(ctx context.Context) (seccomp.InWorkspaceServiceClient, error) {
return connectToInWorkspaceDaemonService(ctx)
},
Ring2PID: cmd.Process.Pid,
Ring2Rootfs: ring2Root,
BindEvents: make(chan seccomp.BindEvent),
Expand Down
23 changes: 20 additions & 3 deletions components/workspacekit/pkg/seccomp/notify.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package seccomp
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
Expand Down Expand Up @@ -168,10 +169,19 @@ func Errno(err unix.Errno) (val uint64, errno int32, flags uint32) {
return ^uint64(0), int32(errno), 0
}

// IWSClientProvider provides a client to the in-workspace-service.
// Consumers of this provider will close the client after use.
type IWSClientProvider func(ctx context.Context) (InWorkspaceServiceClient, error)

type InWorkspaceServiceClient interface {
daemonapi.InWorkspaceServiceClient
io.Closer
}

// InWorkspaceHandler is the seccomp notification handler that serves a Gitpod workspace
type InWorkspaceHandler struct {
FD libseccomp.ScmpFd
Daemon daemonapi.InWorkspaceServiceClient
Daemon IWSClientProvider
Ring2PID int
Ring2Rootfs string
BindEvents chan<- BindEvent
Expand Down Expand Up @@ -260,9 +270,16 @@ func (h *InWorkspaceHandler) Mount(req *libseccomp.ScmpNotifReq) (val uint64, er

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
call := h.Daemon.MountProc
iws, err := h.Daemon(ctx)
if err != nil {
log.WithField("target", target).WithField("dest", dest).WithField("mode", stat.Mode()).WithError(err).Errorf("cannot get IWS client to mount %s", filesystem)
return Errno(unix.EFAULT)
}
defer iws.Close()

call := iws.MountProc
if filesystem == "sysfs" {
call = h.Daemon.MountSysfs
call = iws.MountSysfs
}
_, err = call(ctx, &daemonapi.MountProcRequest{
Target: dest,
Expand Down
2 changes: 2 additions & 0 deletions test/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ require (
github.com/gitpod-io/gitpod/ws-manager/api v0.0.0-00010101000000-000000000000
github.com/go-sql-driver/mysql v1.5.0
github.com/google/uuid v1.2.0
github.com/prometheus/procfs v0.7.3 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
google.golang.org/grpc v1.39.1
k8s.io/api v0.22.0
Expand Down
2 changes: 2 additions & 0 deletions test/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
Expand Down
4 changes: 3 additions & 1 deletion test/pkg/integration/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ import (
"net/rpc"
"os"
"os/signal"
"strconv"
"syscall"
)

// ServeAgent is the main entrypoint for agents. It establishes flags and starts an RPC server
// on a port passed as flag.
func ServeAgent(rcvr interface{}) {
port := flag.Int("rpc-port", 0, "the port on wich to run the RPC server on")
defaultPort, _ := strconv.Atoi(os.Getenv("AGENT_RPC_PORT"))
port := flag.Int("rpc-port", defaultPort, "the port on wich to run the RPC server on")
flag.Parse()

ta := &testAgent{
Expand Down
48 changes: 48 additions & 0 deletions test/tests/workspace/docker_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.

package workspace_test

import (
"testing"
"time"

"github.com/gitpod-io/gitpod/test/pkg/integration"
agent "github.com/gitpod-io/gitpod/test/tests/workspace/workspace_agent/api"
)

func TestRunDocker(t *testing.T) {
it, _ := integration.NewTest(t, 5*time.Minute)
defer it.Done()

ws := integration.LaunchWorkspaceDirectly(it)
instanceID := ws.Req.Id
defer integration.DeleteWorkspace(it, ws.Req.Id)

rsa, err := it.Instrument(integration.ComponentWorkspace, "workspace", integration.WithInstanceID(instanceID), integration.WithWorkspacekitLift(true))
if err != nil {
t.Error(err)
return
}
defer rsa.Close()

var resp agent.ExecResponse
err = rsa.Call("WorkspaceAgent.Exec", &agent.ExecRequest{
Dir: "/",
Command: "bash",
Args: []string{
"-c",
"docker run --rm alpine:latest",
},
}, &resp)
if err != nil {
t.Errorf("docker run failed: %v\n%s\n%s", err, resp.Stdout, resp.Stderr)
return
}

if resp.ExitCode != 0 {
t.Errorf("docker run failed: %s\n%s", resp.Stdout, resp.Stderr)
return
}
}
71 changes: 71 additions & 0 deletions test/tests/workspace/workspace_agent/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,88 @@ package main
import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"strconv"
"strings"

"github.com/gitpod-io/gitpod/test/pkg/integration"
"github.com/gitpod-io/gitpod/test/tests/workspace/workspace_agent/api"
"github.com/prometheus/procfs"
"golang.org/x/sys/unix"
)

func main() {
err := enterSupervisorNamespaces()
if err != nil {
panic(fmt.Sprintf("enterSupervisorNamespaces: %v", err))
}

integration.ServeAgent(new(WorkspaceAgent))
}

func enterSupervisorNamespaces() error {
if os.Getenv("AGENT_IN_RING2") != "" {
return nil
}

nsenter, err := exec.LookPath("nsenter")
if err != nil {
return fmt.Errorf("cannot find nsenter")
}

// This agent expectes to be called using the workspacekit lift (i.e. in ring1).
// We then enter the PID and mount namespace of supervisor.
// First, we need to find the supervisor process
proc, err := procfs.NewFS("/proc")
if err != nil {
return err
}
procs, err := proc.AllProcs()
if err != nil {
return err
}
var supervisorPID int
for _, p := range procs {
cmd, _ := p.CmdLine()
for _, c := range cmd {
if strings.HasSuffix(c, "supervisor") {
supervisorPID = p.PID
break
}
}
if supervisorPID != 0 {
break
}
}
if supervisorPID == 0 {
return fmt.Errorf("no supervisor process found")
}

// Then we copy ourselves to a location that we can access from the supervisor NS
self, err := os.Executable()
if err != nil {
return err
}
fn := fmt.Sprintf("/proc/%d/root/agent", supervisorPID)
dst, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY, 0755)
if err != nil {
return err
}
selfFD, err := os.Open(self)
if err != nil {
return err
}
defer selfFD.Close()
_, err = io.Copy(dst, selfFD)
if err != nil {
return fmt.Errorf("error copying agent: %w", err)
}

return unix.Exec(nsenter, append([]string{nsenter, "-t", strconv.Itoa(supervisorPID), "-m", "-p", "/agent"}, os.Args[1:]...), append(os.Environ(), "AGENT_IN_RING2=true"))
}

// WorkspaceAgent provides ingteration test services from within a workspace
type WorkspaceAgent struct {
}
Expand Down Expand Up @@ -70,6 +140,7 @@ func (*WorkspaceAgent) Exec(req *api.ExecRequest, resp *api.ExecResponse) (err e
return fmt.Errorf("%s: %w", fullCommand, err)
}
rc = exitError.ExitCode()
err = nil
}

*resp = api.ExecResponse{
Expand Down