From 04e03dd30a11c5f3f9fe1f83598ba175ffe755a2 Mon Sep 17 00:00:00 2001 From: Seth Hoenig Date: Sat, 14 Jan 2023 10:20:16 -0600 Subject: [PATCH] agent: sandbox filesystem on linux (usin go-landlock) (#25) * agent: sandbox filesystem on linux (usin go-landlock) * agent: add test for readable * agent: test case for Lockdown --- agent/lockdown.go | 25 ++++++++++++++++ agent/lockdown_default.go | 9 ++++++ agent/lockdown_linux.go | 11 +++++++ agent/lockdown_test.go | 61 +++++++++++++++++++++++++++++++++++++++ go.mod | 4 ++- go.sum | 8 +++-- main.go | 3 ++ 7 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 agent/lockdown.go create mode 100644 agent/lockdown_default.go create mode 100644 agent/lockdown_linux.go create mode 100644 agent/lockdown_test.go diff --git a/agent/lockdown.go b/agent/lockdown.go new file mode 100644 index 0000000..a59b819 --- /dev/null +++ b/agent/lockdown.go @@ -0,0 +1,25 @@ +package agent + +import "github.com/shoenig/go-landlock" + +func Lockdown(cc *CoreConfig) error { + paths := make([]*landlock.Path, 0, 4) + paths = append(paths, sysPaths...) + paths = append(paths, readable(cc)...) + locker := landlock.New(paths...) + return locker.Lock(landlock.OnlySupported) +} + +func readable(cc *CoreConfig) []*landlock.Path { + var paths []*landlock.Path + if cc.AllowFile != "" { + paths = append(paths, landlock.File(cc.AllowFile, "r")) + } + if cc.BlockFile != "" { + paths = append(paths, landlock.File(cc.BlockFile, "r")) + } + if cc.SuffixFile != "" { + paths = append(paths, landlock.File(cc.SuffixFile, "r")) + } + return paths +} diff --git a/agent/lockdown_default.go b/agent/lockdown_default.go new file mode 100644 index 0000000..1feb33d --- /dev/null +++ b/agent/lockdown_default.go @@ -0,0 +1,9 @@ +//go:build !linux + +package agent + +import ( + "github.com/shoenig/go-landlock" +) + +var sysPaths []*landlock.Path diff --git a/agent/lockdown_linux.go b/agent/lockdown_linux.go new file mode 100644 index 0000000..f8f1e59 --- /dev/null +++ b/agent/lockdown_linux.go @@ -0,0 +1,11 @@ +//go:build linux + +package agent + +import ( + "github.com/shoenig/go-landlock" +) + +var sysPaths = []*landlock.Path{ + landlock.Certs(), +} diff --git a/agent/lockdown_test.go b/agent/lockdown_test.go new file mode 100644 index 0000000..a40783f --- /dev/null +++ b/agent/lockdown_test.go @@ -0,0 +1,61 @@ +package agent + +import ( + "testing" + + "github.com/shoenig/go-landlock" + "github.com/shoenig/test/must" +) + +func Test_readable(t *testing.T) { + cases := []struct { + name string + cc *CoreConfig + exp []*landlock.Path + }{ + { + name: "none", + cc: new(CoreConfig), + exp: nil, + }, + { + name: "partial", + cc: &CoreConfig{BlockFile: "/opt/blocks.txt"}, + exp: []*landlock.Path{landlock.File("/opt/blocks.txt", "r")}, + }, + { + name: "all", + cc: &CoreConfig{ + AllowFile: "/opt/allows.txt", + BlockFile: "/opt/blocks.txt", + SuffixFile: "/opt/suffix.txt", + }, + exp: []*landlock.Path{ + landlock.File("/opt/blocks.txt", "r"), + landlock.File("/opt/allows.txt", "r"), + landlock.File("/opt/suffix.txt", "r"), + }, + }, + } + + for _, tc := range cases { + result := readable(tc.cc) + must.SliceContainsAll(t, tc.exp, result) + } +} + +func Test_Lockdown(t *testing.T) { + t.Run("ok", func(t *testing.T) { + err := Lockdown(&CoreConfig{ + BlockFile: "../hack/social-media.list", + }) + must.NoError(t, err) + }) + + t.Run("does not exist", func(t *testing.T) { + err := Lockdown(&CoreConfig{ + BlockFile: "/does/not/exist", + }) + must.ErrorContains(t, err, "no such file") + }) +} diff --git a/go.mod b/go.mod index e08da0d..79331d9 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/hashicorp/go-set v0.1.7 github.com/miekg/dns v1.1.50 github.com/shoenig/extractors v0.3.0 + github.com/shoenig/go-landlock v0.1.4 github.com/shoenig/ignore v0.4.0 github.com/shoenig/test v0.6.0 ) @@ -37,10 +38,11 @@ require ( github.com/stretchr/testify v1.8.0 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect - golang.org/x/sys v0.0.0-20220804214406-8e32c043e418 // indirect + golang.org/x/sys v0.3.0 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/tools v0.1.12 // indirect google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f // indirect google.golang.org/grpc v1.49.0 // indirect google.golang.org/protobuf v1.28.1 // indirect + kernel.org/pub/linux/libs/security/libcap/psx v1.2.66 // indirect ) diff --git a/go.sum b/go.sum index 3f6f462..d86a9ce 100644 --- a/go.sum +++ b/go.sum @@ -235,6 +235,8 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/shoenig/extractors v0.3.0 h1:B3nfz509DA74fGH/2w3Lgi1/sjuzJcadPhiiQp8+GJk= github.com/shoenig/extractors v0.3.0/go.mod h1:pZoq4jZOFDIHBqD/SufEKTqZNni1urCN2JcgRgzW5ZU= +github.com/shoenig/go-landlock v0.1.4 h1:E5I0QhMlY/gwmhRPMVLlboJTW2mcMZG7g8NfATF97bY= +github.com/shoenig/go-landlock v0.1.4/go.mod h1:4r9IOhhNbzc9yVANPYUervTsamQCPWC/e5Tg7WgR9Ss= github.com/shoenig/ignore v0.4.0 h1:qPOWs0slbPMtenC0H3cKvu5Kn3hQFTE3yK6YJvyNDlA= github.com/shoenig/ignore v0.4.0/go.mod h1:VF91FoiYAwXq4KinOq6zP5xfFw/Ib6MfftaGKYTpmwo= github.com/shoenig/test v0.6.0 h1:rU0ymLHmCRqz14gABce/DzYryKU+uaWqobCBvAY6DtU= @@ -402,8 +404,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220804214406-8e32c043e418 h1:9vYwv7OjYaky/tlAeD7C4oC9EsPTlaFl1H2jS++V+ME= -golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -579,6 +581,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +kernel.org/pub/linux/libs/security/libcap/psx v1.2.66 h1:ikIhPzfkSSAEwBOU+2DWhoF+xnGUhvlMTfQjBVhvzQY= +kernel.org/pub/linux/libs/security/libcap/psx v1.2.66/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/main.go b/main.go index a5e06e8..0885c66 100644 --- a/main.go +++ b/main.go @@ -48,6 +48,9 @@ func setupCC() { // get core config from environment cc := getCC() + // sandbox donutdns from filesystem (Linux landlock) + agent.Lockdown(cc) + // set plugin core config dnsserver.Port = strconv.Itoa(cc.Port) dnsserver.Directives = directives