Skip to content

Commit b340cf5

Browse files
committed
Adding support for jailer
Adds support for firecracker's jailer. This allows for more secure virtualization through creation of namespaces and cgroups. The jailer can be turned on by the setting the EnableJailer field in the config. This also implements a very naive mapper and will require update to support a smarter one. Signed-off-by: xibz <[email protected]>
1 parent 4b31e1c commit b340cf5

13 files changed

+1125
-57
lines changed

.buildkite/pipeline.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,17 @@ steps:
3131
- 'ln -s /var/lib/fc-ci/vmlinux.bin testdata/vmlinux'
3232
- 'ln -s /usr/local/bin/firecracker testdata/firecracker'
3333
- 'ln -s /usr/local/bin/jailer testdata/jailer'
34-
- "make test EXTRAGOARGS='-v -count=1'"
34+
- 'sudo ip tuntap add fc-test-tap${BUILDKITE_BUILD_NUMBER} mode tap user $(sudo id -u buildkite-agent)'
35+
- "DISABLE_ROOT_TESTS=true FC_TEST_TAP=fc-test-tap${BUILDKITE_BUILD_NUMBER} make test EXTRAGOARGS='-v -count=1'"
36+
- 'sudo ip tuntap del fc-test-tap${BUILDKITE_BUILD_NUMBER} mode tap'
37+
38+
- label: ':hammer: root tests'
39+
commands:
40+
- 'ln -s /var/lib/fc-ci/vmlinux.bin testdata/vmlinux'
41+
- 'ln -s /usr/local/bin/firecracker testdata/firecracker'
42+
- 'ln -s /usr/local/bin/jailer testdata/jailer'
43+
- 'sudo ip tuntap add fc-root-tap${BUILDKITE_BUILD_NUMBER} mode tap user $(sudo id -u buildkite-agent)'
44+
- "sudo FC_TEST_TAP=fc-root-tap${BUILDKITE_BUILD_NUMBER} make test EXTRAGOARGS='-v -count=1'"
45+
- 'sudo ip tuntap del fc-root-tap${BUILDKITE_BUILD_NUMBER} mode tap'
3546
agents:
3647
queue: "${BUILDKITE_AGENT_META_DATA_QUEUE:-default}"

HACKING.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@ Testing
66

77
Tests are written using Go's testing framework and can be run with the standard
88
`go test` tool. If you prefer to use the Makefile, `make test EXTRAGOARGS=-v`
9-
will run tests in verbose mode.
9+
will run tests in verbose mode. By default, the unit tests require root
10+
privileged access. This can be disabled by setting the `DISABLE_ROOT_TESTS`
11+
environment variable.
1012

1113
You need some external resources in order to run the tests, as described below:
1214

13-
1. A firecracker binary (tested with 0.10.1), but any recent version should
14-
work. Must either be installed as `./testdata/firecracker` or the path must
15-
be specified through the `FC_TEST_BIN` environment variable.
15+
1. A firecracker and jailer binary (tested with 0.12.0) must either be
16+
installed as `./testdata/firecracker` or the path must be specified through
17+
the `FC_TEST_BIN` environment variable. The jailer requires go test to be
18+
run with sudo and can also be turned off by setting the `DISABLE_ROOT_TESTS`
19+
env flag.
1620
2. Access to hardware virtualization via `/dev/kvm` and `/dev/vhost-vsock`
1721
(ensure you have mode `+rw`!)
1822
3. An uncompressed Linux kernel binary that can boot in Firecracker VM (Must be

command_builder.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ import (
2020
"os/exec"
2121
)
2222

23+
const defaultFcBin = "firecracker"
24+
2325
var defaultFirecrackerVMMCommandBuilder = VMCommandBuilder{}.
24-
WithBin("firecracker").
26+
WithBin(defaultFcBin).
2527
WithStdin(os.Stdin).
2628
WithStdout(os.Stdout).
2729
WithStderr(os.Stderr)
@@ -55,8 +57,13 @@ func (b VMCommandBuilder) AddArgs(args ...string) VMCommandBuilder {
5557
return b
5658
}
5759

58-
// Bin return the bin that was set
60+
// Bin returns the bin that was set. If bin had not been set, then the default
61+
// will be returned.
5962
func (b VMCommandBuilder) Bin() string {
63+
if len(b.bin) == 0 {
64+
return defaultFcBin
65+
}
66+
6067
return b.bin
6168
}
6269

command_builder_test.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,13 @@ func TestVMCommandBuilder(t *testing.T) {
2929
func testVMCommandBuilderImmutable(t *testing.T) {
3030
b := VMCommandBuilder{}
3131
b.WithSocketPath("foo").
32-
WithBin("bar").
3332
WithArgs([]string{"baz", "qux"}).
3433
AddArgs("moo", "cow")
3534

3635
if e, a := []string(nil), b.SocketPath(); !reflect.DeepEqual(e, a) {
3736
t.Errorf("expected immutable builder, but socket path was set: %q", a)
3837
}
3938

40-
if e, a := "", b.Bin(); e != a {
41-
t.Errorf("bin had been set with %q, but should have been empty", a)
42-
}
43-
4439
if e, a := ([]string)(nil), b.Args(); !reflect.DeepEqual(e, a) {
4540
t.Errorf("args should have been empty, but received %v", a)
4641
}

example_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ func ExampleWithProcessRunner_logging() {
1919
MachineCfg: models.MachineConfiguration{
2020
VcpuCount: 1,
2121
},
22+
JailerCfg: firecracker.JailerConfig{
23+
GID: firecracker.Int(100),
24+
UID: firecracker.Int(100),
25+
ID: "my-micro-vm",
26+
NumaNode: firecracker.Int(0),
27+
ExecFile: "/path/to/firecracker",
28+
},
2229
}
2330

2431
// stdout will be directed to this file
@@ -212,3 +219,52 @@ func ExampleNetworkInterface_rateLimiting() {
212219
panic(err)
213220
}
214221
}
222+
223+
func ExampleJailerCommandBuilder() {
224+
ctx := context.Background()
225+
// Creates a jailer command using the JailerCommandBuilder.
226+
b := firecracker.NewJailerCommandBuilder().
227+
WithID("my-test-id").
228+
WithUID(123).
229+
WithGID(100).
230+
WithNumaNode(0).
231+
WithExecFile("/usr/local/bin/firecracker").
232+
WithChrootBaseDir("/tmp").
233+
WithStdout(os.Stdout).
234+
WithStderr(os.Stderr)
235+
236+
const socketPath = "/tmp/firecracker/my-test-id/api.socket"
237+
cfg := firecracker.Config{
238+
SocketPath: socketPath,
239+
KernelImagePath: "./vmlinux",
240+
Drives: []models.Drive{
241+
models.Drive{
242+
DriveID: firecracker.String("1"),
243+
IsRootDevice: firecracker.Bool(true),
244+
IsReadOnly: firecracker.Bool(false),
245+
PathOnHost: firecracker.String("/path/to/root/drive"),
246+
},
247+
},
248+
MachineCfg: models.MachineConfiguration{
249+
VcpuCount: 1,
250+
},
251+
DisableValidation: true,
252+
}
253+
254+
// Passes the custom jailer command into the constructor
255+
m, err := firecracker.NewMachine(ctx, cfg, firecracker.WithProcessRunner(b.Build(ctx)))
256+
if err != nil {
257+
panic(fmt.Errorf("failed to create new machine: %v", err))
258+
}
259+
260+
// This does not copy any of the files over to the rootfs since a process
261+
// runner was specified. This examples assumes that the files have been
262+
// properly mounted.
263+
if err := m.Start(ctx); err != nil {
264+
panic(err)
265+
}
266+
267+
tCtx, cancelFn := context.WithTimeout(ctx, time.Minute)
268+
defer cancelFn()
269+
m.Wait(tCtx)
270+
}

fctesting/utils.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package fctesting
15+
16+
import (
17+
"os"
18+
"testing"
19+
)
20+
21+
const rootDisableEnvName = "DISABLE_ROOT_TESTS"
22+
23+
var rootDisabled bool
24+
25+
func init() {
26+
if v := os.Getenv(rootDisableEnvName); len(v) != 0 {
27+
rootDisabled = true
28+
}
29+
}
30+
31+
// RequiresRoot will ensure that tests that require root access are actually
32+
// root. In addition, this will skip root tests if the DISABLE_ROOT_TESTS is
33+
// set to true
34+
func RequiresRoot(t testing.TB) {
35+
if rootDisabled {
36+
t.Skip("skipping test that requires root")
37+
}
38+
39+
if e, a := 0, os.Getuid(); e != a {
40+
t.Fatalf("This test must be run as root. "+
41+
"To disable tests that require root, "+
42+
"run the tests with the %s environment variable set.",
43+
rootDisableEnvName)
44+
}
45+
}

handlers.go

Lines changed: 104 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
1+
// Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
114
package firecracker
215

316
import (
417
"context"
18+
"fmt"
519
)
620

721
// Handler name constants
@@ -14,10 +28,76 @@ const (
1428
CreateNetworkInterfacesHandlerName = "fcinit.CreateNetworkInterfaces"
1529
AddVsocksHandlerName = "fcinit.AddVsocks"
1630
SetMetadataHandlerName = "fcinit.SetMetadata"
31+
LinkFilesToRootFSHandlerName = "fcinit.LinkFilesToRootFS"
1732

18-
ValidateCfgHandlerName = "validate.Cfg"
33+
ValidateCfgHandlerName = "validate.Cfg"
34+
ValidateJailerCfgHandlerName = "validate.JailerCfg"
1935
)
2036

37+
// HandlersAdapter is an interface used to modify a given set of handlers.
38+
type HandlersAdapter interface {
39+
AdaptHandlers(*Handlers) error
40+
}
41+
42+
// ConfigValidationHandler is used to validate that required fields are
43+
// present. This validator is to be used when the jailer is turned off.
44+
var ConfigValidationHandler = Handler{
45+
Name: ValidateCfgHandlerName,
46+
Fn: func(ctx context.Context, m *Machine) error {
47+
// ensure that the configuration is valid for the FcInit handlers.
48+
return m.cfg.Validate()
49+
},
50+
}
51+
52+
// JailerConfigValidationHandler is used to validate that required fields are
53+
// present.
54+
var JailerConfigValidationHandler = Handler{
55+
Name: ValidateJailerCfgHandlerName,
56+
Fn: func(ctx context.Context, m *Machine) error {
57+
if !m.cfg.EnableJailer {
58+
return nil
59+
}
60+
61+
hasRoot := false
62+
for _, drive := range m.cfg.Drives {
63+
if BoolValue(drive.IsRootDevice) {
64+
hasRoot = true
65+
break
66+
}
67+
}
68+
69+
if !hasRoot {
70+
return fmt.Errorf("A root drive must be present in the drive list")
71+
}
72+
73+
if m.cfg.JailerCfg.ChrootStrategy == nil {
74+
return fmt.Errorf("ChrootStrategy cannot be nil")
75+
}
76+
77+
if len(m.cfg.JailerCfg.ExecFile) == 0 {
78+
return fmt.Errorf("exec file must be specified when using jailer mode")
79+
}
80+
81+
if len(m.cfg.JailerCfg.ID) == 0 {
82+
return fmt.Errorf("id must be specified when using jailer mode")
83+
}
84+
85+
if m.cfg.JailerCfg.GID == nil {
86+
return fmt.Errorf("GID must be specified when using jailer mode")
87+
}
88+
89+
if m.cfg.JailerCfg.UID == nil {
90+
return fmt.Errorf("UID must be specified when using jailer mode")
91+
}
92+
93+
if m.cfg.JailerCfg.NumaNode == nil {
94+
return fmt.Errorf("ID must be specified when using jailer mode")
95+
}
96+
97+
return nil
98+
},
99+
}
100+
21101
// StartVMMHandler is a named handler that will handle starting of the VMM.
22102
// This handler will also set the exit channel on completion.
23103
var StartVMMHandler = Handler{
@@ -98,17 +178,6 @@ func NewSetMetadataHandler(metadata interface{}) Handler {
98178
}
99179
}
100180

101-
var defaultValidationHandlerList = HandlerList{}.Append(
102-
Handler{
103-
Name: ValidateCfgHandlerName,
104-
Fn: func(ctx context.Context, m *Machine) error {
105-
// ensure that the configuration is valid for the
106-
// FcInit handlers.
107-
return m.cfg.Validate()
108-
},
109-
},
110-
)
111-
112181
var defaultFcInitHandlerList = HandlerList{}.Append(
113182
StartVMMHandler,
114183
BootstrapLoggingHandler,
@@ -120,8 +189,7 @@ var defaultFcInitHandlerList = HandlerList{}.Append(
120189
)
121190

122191
var defaultHandlers = Handlers{
123-
Validation: defaultValidationHandlerList,
124-
FcInit: defaultFcInitHandlerList,
192+
FcInit: defaultFcInitHandlerList,
125193
}
126194

127195
// Handler represents a named handler that contains a name and a function which
@@ -140,9 +208,12 @@ type Handlers struct {
140208
// Run will execute all handlers in the Handlers object by flattening the lists
141209
// into a single list and running.
142210
func (h Handlers) Run(ctx context.Context, m *Machine) error {
143-
l := HandlerList{}.Append(
144-
h.Validation.list...,
145-
).Append(
211+
l := HandlerList{}
212+
if !m.cfg.DisableValidation {
213+
l = l.Append(h.Validation.list...)
214+
}
215+
216+
l = l.Append(
146217
h.FcInit.list...,
147218
)
148219

@@ -162,6 +233,21 @@ func (l HandlerList) Append(handlers ...Handler) HandlerList {
162233
return l
163234
}
164235

236+
// AppendAfter will append a given handler after the specified handler.
237+
func (l HandlerList) AppendAfter(name string, handler Handler) HandlerList {
238+
newList := HandlerList{}
239+
for _, h := range l.list {
240+
if h.Name == name {
241+
newList = newList.Append(h, handler)
242+
continue
243+
}
244+
245+
newList = newList.Append(h)
246+
}
247+
248+
return newList
249+
}
250+
165251
// Len return the length of the given handler list
166252
func (l HandlerList) Len() int {
167253
return len(l.list)
@@ -229,6 +315,7 @@ func (l HandlerList) Run(ctx context.Context, m *Machine) error {
229315
for _, handler := range l.list {
230316
m.logger.Debugf("Running handler %s", handler.Name)
231317
if err := handler.Fn(ctx, m); err != nil {
318+
m.logger.Warnf("Failed handler %q: %v", handler.Name, err)
232319
return err
233320
}
234321
}

0 commit comments

Comments
 (0)