From 1c8e950aa4d78dad6ec699db7d949ea21f2bcbf2 Mon Sep 17 00:00:00 2001 From: Dan Lorenc Date: Wed, 2 Dec 2020 08:56:31 -0600 Subject: [PATCH] Add a "dropNetworking" function and unit tests to the runner package. This will be used for TEP-0025 [Hermekton](https://github.com/tektoncd/community/blob/master/teps/0025-hermekton.md) to drop network access for the process run by the runner. The unit tests here aren't great, but are better than nothing IMO. They can only run on Linux, and test that network access is not available. To do this, they try to make a network request. If the tests were to run in a sandboxed environment, we would miss this. They will be reinforced by e2e tests that properly verify network access exists without this call, and then doesn't exist with this call. --- cmd/entrypoint/namespaces.go | 11 +++++++ cmd/entrypoint/namespaces_linux.go | 50 ++++++++++++++++++++++++++++++ cmd/entrypoint/namespaces_test.go | 41 ++++++++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 cmd/entrypoint/namespaces.go create mode 100644 cmd/entrypoint/namespaces_linux.go create mode 100644 cmd/entrypoint/namespaces_test.go diff --git a/cmd/entrypoint/namespaces.go b/cmd/entrypoint/namespaces.go new file mode 100644 index 00000000000..75774abc057 --- /dev/null +++ b/cmd/entrypoint/namespaces.go @@ -0,0 +1,11 @@ +// +build !linux + +package main + +import "os/exec" + +// The implementation of this currently only works on Linux. +// This is a placeholder for compilation/testing. +func dropNetworking(cmd *exec.Cmd) { + panic("only implemented on linux") +} diff --git a/cmd/entrypoint/namespaces_linux.go b/cmd/entrypoint/namespaces_linux.go new file mode 100644 index 00000000000..abf8f8ef977 --- /dev/null +++ b/cmd/entrypoint/namespaces_linux.go @@ -0,0 +1,50 @@ +package main + +import ( + "os/exec" + "syscall" +) + +// dropNetworking modifies the supplied exec.Cmd to execute in a net set of namespaces that do not +// have network access +func dropNetworking(cmd *exec.Cmd) { + // These flags control the behavior of the new process. + // Documentation for these is available here: https://man7.org/linux/man-pages/man2/clone.2.html + // We mostly want to just create a new network namespace, unattached to any networking devices. + // The other flags are necessary for that to work. + + if cmd.SysProcAttr == nil { + // We build this up piecemeal in case it was already set, to avoid overwriting anything. + cmd.SysProcAttr = &syscall.SysProcAttr{} + } + cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWNS | + syscall.CLONE_NEWPID | // NEWPID creates a new process namespace + syscall.CLONE_NEWNET | // NEWNET creates a new network namespace (this is the one we really care about) + syscall.CLONE_NEWUSER // NEWUSER creates a new user namespace + + // We need to map the existing user IDs into the new namespace. + // Just map everything. + cmd.SysProcAttr.UidMappings = []syscall.SysProcIDMap{ + { + ContainerID: 0, + HostID: 0, + // Map all users + Size: 4294967295, + }, + } + + // This is needed to allow programs to call setgroups when in a new Gid namespace. + // Things like apt-get install require this to work. + cmd.SysProcAttr.GidMappingsEnableSetgroups = true + // We need to map the existing group IDs into the new namespace. + // Just map everything. + cmd.SysProcAttr.GidMappings = []syscall.SysProcIDMap{ + { + ContainerID: 0, + HostID: 0, + + // Map all groups + Size: 4294967295, + }, + } +} diff --git a/cmd/entrypoint/namespaces_test.go b/cmd/entrypoint/namespaces_test.go new file mode 100644 index 00000000000..efea66f43ea --- /dev/null +++ b/cmd/entrypoint/namespaces_test.go @@ -0,0 +1,41 @@ +// +build linux + +package main + +import ( + "os/exec" + "testing" + + "github.com/google/go-cmp/cmp" +) + +// This isn't a great unit test, but it's the best I can think of. +// It attempts to verify there is no network access by making a network +// request. If the test were to run in an offline environment, or an already +// sandboxed environment, the test could pass even if the dropNetworking +// function did nothing. +func TestDropNetworking(t *testing.T) { + cmd := exec.Command("curl", "google.com") + dropNetworking(cmd) + b, err := cmd.CombinedOutput() + if err == nil { + t.Errorf("Expected an error making a network connection. Got %s", string(b)) + } + + // Other things (env, etc.) should all be the same + cmds := []string{"env", "whoami", "pwd", "uname"} + for _, cmd := range cmds { + withNetworking := exec.Command(cmd) + withoutNetworking := exec.Command(cmd) + dropNetworking(withoutNetworking) + + b1, err1 := withNetworking.CombinedOutput() + b2, err2 := withoutNetworking.CombinedOutput() + if err1 != err2 { + t.Errorf("Expected no errors, got %v %v", err1, err2) + } + if diff := cmp.Diff(string(b1), string(b2)); diff != "" { + t.Error(diff) + } + } +}