Skip to content

Commit 847e85d

Browse files
authored
Merge pull request #70 from xibz/jailer_clean
adding support for jailer
2 parents 4b31e1c + b340cf5 commit 847e85d

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)