mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Plumb seccheck configuration in runsc
Added new flag `--pod-init-config` that allows custom configuration
steps to be specified when a pod is being created. For now, the only
step supported is to configure a seccheck session, but more can be
added in the future, e.g. startup hook.
In seccheck, metadata was added for all Points and sinks (aka Checkers)
so that an external configuration files can reference them. In addition,
infrastructure to read and configure sessions from a file has been
added. Only a single session ("Default") is supported for now.
Updates #4805
PiperOrigin-RevId: 444705708
This commit is contained in:
committed by
gVisor bot
parent
ce91143bdc
commit
a4cb72d1d8
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"trace_session": {
|
||||
"name": "Default",
|
||||
"points": [
|
||||
{
|
||||
"name": "sentry/clone"
|
||||
}
|
||||
],
|
||||
"sinks": [
|
||||
{
|
||||
"name": "remote",
|
||||
"config": {
|
||||
"endpoint": "/tmp/gvisor_events.sock"
|
||||
},
|
||||
"ignore_setup_error": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,10 @@ go_library(
|
||||
name = "seccheck",
|
||||
srcs = [
|
||||
"clone.go",
|
||||
"config.go",
|
||||
"execve.go",
|
||||
"exit.go",
|
||||
"metadata.go",
|
||||
"seccheck.go",
|
||||
"seqatomic_checkerslice_unsafe.go",
|
||||
],
|
||||
@@ -28,17 +30,24 @@ go_library(
|
||||
"//pkg/abi/linux",
|
||||
"//pkg/atomicbitops",
|
||||
"//pkg/context",
|
||||
"//pkg/fd",
|
||||
"//pkg/gohacks",
|
||||
"//pkg/log",
|
||||
"//pkg/sentry/arch",
|
||||
"//pkg/sentry/kernel/time",
|
||||
"//pkg/sentry/seccheck/points:points_go_proto",
|
||||
"//pkg/sync",
|
||||
"@org_golang_google_protobuf//proto:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "seccheck_test",
|
||||
size = "small",
|
||||
srcs = ["seccheck_test.go"],
|
||||
srcs = [
|
||||
"metadata_test.go",
|
||||
"seccheck_test.go",
|
||||
],
|
||||
library = ":seccheck",
|
||||
deps = [
|
||||
"//pkg/context",
|
||||
|
||||
@@ -33,6 +33,14 @@ import (
|
||||
pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto"
|
||||
)
|
||||
|
||||
func init() {
|
||||
seccheck.RegisterSink(seccheck.SinkDesc{
|
||||
Name: "remote",
|
||||
Setup: Setup,
|
||||
New: New,
|
||||
})
|
||||
}
|
||||
|
||||
// Remote sends a serialized point to a remote process asynchronously over a
|
||||
// SOCK_SEQPACKET Unix-domain socket. Each message corresponds to a single
|
||||
// serialized point proto, preceded by a standard header. If the point cannot
|
||||
@@ -46,6 +54,21 @@ type Remote struct {
|
||||
|
||||
var _ seccheck.Checker = (*Remote)(nil)
|
||||
|
||||
// Setup starts the connection to the remote process and returns a file that
|
||||
// can be used to communicate with it. The caller is responsible to close to
|
||||
// file.
|
||||
func Setup(config map[string]interface{}) (*os.File, error) {
|
||||
addrOpaque, ok := config["endpoint"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("endpoint not present in configuration")
|
||||
}
|
||||
addr, ok := addrOpaque.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("endpoint %q is not a string", addrOpaque)
|
||||
}
|
||||
return setup(addr)
|
||||
}
|
||||
|
||||
func setup(path string) (*os.File, error) {
|
||||
log.Debugf("Remote sink connecting to %q", path)
|
||||
socket, err := unix.Socket(unix.AF_UNIX, unix.SOCK_SEQPACKET, 0)
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
// Copyright 2022 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package seccheck
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/fd"
|
||||
"gvisor.dev/gvisor/pkg/log"
|
||||
)
|
||||
|
||||
// SessionConfig describes a new session configuration. A session consists of a
|
||||
// set of points to be enabled and sinks where the points are sent to.
|
||||
type SessionConfig struct {
|
||||
// Name is the unique session name.
|
||||
Name string `json:"name,omitempty"`
|
||||
// Points is the set of points to enable in this session.
|
||||
Points []PointConfig `json:"points,omitempty"`
|
||||
// Sinks are the sinks that will process the points enabled above.
|
||||
Sinks []SinkConfig `json:"sinks,omitempty"`
|
||||
}
|
||||
|
||||
// PointConfig describes a point to be enabled in a given session.
|
||||
type PointConfig struct {
|
||||
// Name is the point to be enabled. The point must exist in the system.
|
||||
Name string `json:"name,omitempty"`
|
||||
// OptionalFields is the list of optional fields to collect from the point.
|
||||
OptionalFields []string `json:"optional_fields,omitempty"`
|
||||
// ContextFields is the list of context fields to collect.
|
||||
ContextFields []string `json:"context_fields,omitempty"`
|
||||
}
|
||||
|
||||
// SinkConfig describes the sink that will process the points in a given
|
||||
// session.
|
||||
type SinkConfig struct {
|
||||
// Name is the sink to be created. The sink must exist in the system.
|
||||
Name string `json:"name,omitempty"`
|
||||
// Config is a opaque json object that is passed to the sink.
|
||||
Config map[string]interface{} `json:"config,omitempty"`
|
||||
// IgnoreSetupError makes errors during sink setup to be ignored. Otherwise,
|
||||
// failures will prevent the container from starting.
|
||||
IgnoreSetupError bool `json:"ignore_setup_error,omitempty"`
|
||||
// FD is the endpoint returned from Setup. It may be nil.
|
||||
FD *fd.FD `json:"-"`
|
||||
}
|
||||
|
||||
// Configure reads the session configuration and applies it to the system.
|
||||
func Configure(conf *SessionConfig) error {
|
||||
log.Debugf("Configuring seccheck: %+v", conf)
|
||||
state, err := findSession(conf.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var reqs []PointReq
|
||||
for _, ptConfig := range conf.Points {
|
||||
desc, err := findPointDesc(ptConfig.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req := PointReq{Pt: desc.ID}
|
||||
|
||||
mask, err := setFields(ptConfig.OptionalFields, desc.OptionalFields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Fields.Local = mask
|
||||
|
||||
mask, err = setFields(ptConfig.ContextFields, desc.ContextFields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Fields.Context = mask
|
||||
|
||||
reqs = append(reqs, req)
|
||||
}
|
||||
|
||||
for _, sinkConfig := range conf.Sinks {
|
||||
sink, err := findSinkDesc(sinkConfig.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
checker, err := sink.New(sinkConfig.Config, sinkConfig.FD)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating event sink: %w", err)
|
||||
}
|
||||
state.AppendChecker(checker, reqs)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetupSink runs the setup step for a given sink.
|
||||
func SetupSink(config SinkConfig) (*os.File, error) {
|
||||
sink, err := findSinkDesc(config.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sink.Setup == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return sink.Setup(config.Config)
|
||||
}
|
||||
|
||||
func findSession(name string) (*State, error) {
|
||||
if name != "Default" {
|
||||
return nil, fmt.Errorf(`only a single "Default" session is supported`)
|
||||
}
|
||||
return &Global, nil
|
||||
}
|
||||
|
||||
func findPointDesc(name string) (PointDesc, error) {
|
||||
if desc, ok := points[name]; ok {
|
||||
return desc, nil
|
||||
}
|
||||
return PointDesc{}, fmt.Errorf("point %q not found", name)
|
||||
}
|
||||
|
||||
func findField(name string, fields []FieldDesc) (FieldDesc, error) {
|
||||
for _, f := range fields {
|
||||
if f.Name == name {
|
||||
return f, nil
|
||||
}
|
||||
}
|
||||
return FieldDesc{}, fmt.Errorf("field %q not found", name)
|
||||
}
|
||||
|
||||
func setFields(names []string, fields []FieldDesc) (FieldMask, error) {
|
||||
fm := FieldMask{}
|
||||
for _, name := range names {
|
||||
desc, err := findField(name, fields)
|
||||
if err != nil {
|
||||
return FieldMask{}, err
|
||||
}
|
||||
fm.Add(desc.ID)
|
||||
}
|
||||
return fm, nil
|
||||
}
|
||||
|
||||
func findSinkDesc(name string) (SinkDesc, error) {
|
||||
if desc, ok := sinks[name]; ok {
|
||||
return desc, nil
|
||||
}
|
||||
return SinkDesc{}, fmt.Errorf("sink %q not found", name)
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
// Copyright 2022 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package seccheck
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/fd"
|
||||
)
|
||||
|
||||
var points = map[string]PointDesc{}
|
||||
var sinks = map[string]SinkDesc{}
|
||||
|
||||
// defaultContextFields are the fields present in most points.
|
||||
var defaultContextFields = []FieldDesc{
|
||||
{
|
||||
ID: FieldCtxtTime,
|
||||
Name: "time",
|
||||
},
|
||||
{
|
||||
ID: FieldCtxtThreadID,
|
||||
Name: "thread_id",
|
||||
},
|
||||
{
|
||||
ID: FieldCtxtThreadStartTime,
|
||||
Name: "task_start_time",
|
||||
},
|
||||
{
|
||||
ID: FieldCtxtThreadGroupID,
|
||||
Name: "group_id",
|
||||
},
|
||||
{
|
||||
ID: FieldCtxtThreadGroupStartTime,
|
||||
Name: "thread_group_start_time",
|
||||
},
|
||||
{
|
||||
ID: FieldCtxtContainerID,
|
||||
Name: "container_id",
|
||||
},
|
||||
{
|
||||
ID: FieldCtxtCredentials,
|
||||
Name: "credentials",
|
||||
},
|
||||
{
|
||||
ID: FieldCtxtCwd,
|
||||
Name: "cwd",
|
||||
},
|
||||
{
|
||||
ID: FieldCtxtProcessName,
|
||||
Name: "process_name",
|
||||
},
|
||||
}
|
||||
|
||||
// SinkDesc describes a sink that is available to be configured.
|
||||
type SinkDesc struct {
|
||||
// Name is a unique identifier for the sink.
|
||||
Name string
|
||||
// Setup is called outside the protection of the sandbox. This is done to
|
||||
// allow the sink to do whatever is necessary to set it up. If it returns a
|
||||
// file, this file is donated to the sandbox and passed to the sink when New
|
||||
// is called. config is an opaque json object passed to the sink.
|
||||
Setup func(config map[string]interface{}) (*os.File, error)
|
||||
// New creates a new sink. config is an opaque json object passed to the sink.
|
||||
// endpoing is a file descriptor to the file returned in Setup. It's set to -1
|
||||
// if Setup returned nil.
|
||||
New func(config map[string]interface{}, endpoint *fd.FD) (Checker, error)
|
||||
}
|
||||
|
||||
// RegisterSink registers a new sink to make it discoverable.
|
||||
func RegisterSink(sink SinkDesc) {
|
||||
if _, ok := sinks[sink.Name]; ok {
|
||||
panic(fmt.Sprintf("Sink %q already registered", sink.Name))
|
||||
}
|
||||
sinks[sink.Name] = sink
|
||||
}
|
||||
|
||||
// PointDesc describes a Point that is available to be configured.
|
||||
// Schema for these points are defined in pkg/sentry/seccheck/points/.
|
||||
type PointDesc struct {
|
||||
// ID is the point unique indentifier.
|
||||
ID Point
|
||||
// Name is the point unique name. Convention is to use the following format:
|
||||
// namespace/name
|
||||
// Examples: container/start, sentry/clone, etc.
|
||||
Name string
|
||||
// OptionalFields is a list of fields that are available in the point, but not
|
||||
// collected unless specified when the Point is configured.
|
||||
// Examples: fd_path, data for read/write Points, etc.
|
||||
OptionalFields []FieldDesc
|
||||
// ContextFields is a list of fields that can be collected from the context,
|
||||
// but are not collected unless specified when the Point is configured.
|
||||
// Examples: container_id, PID, etc.
|
||||
ContextFields []FieldDesc
|
||||
}
|
||||
|
||||
// FieldDesc describes an optional/context field that is available to be
|
||||
// configured.
|
||||
type FieldDesc struct {
|
||||
// ID is the numeric identifier of the field.
|
||||
ID Field
|
||||
// Name is the unique field name.
|
||||
Name string
|
||||
}
|
||||
|
||||
func registerPoint(pt PointDesc) {
|
||||
if _, ok := points[pt.Name]; ok {
|
||||
panic(fmt.Sprintf("Point %q already registered", pt.Name))
|
||||
}
|
||||
if err := validateFields(pt.OptionalFields); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := validateFields(pt.ContextFields); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
points[pt.Name] = pt
|
||||
}
|
||||
|
||||
func validateFields(fields []FieldDesc) error {
|
||||
ids := make(map[Field]FieldDesc)
|
||||
names := make(map[string]FieldDesc)
|
||||
for _, f := range fields {
|
||||
if other, ok := names[f.Name]; ok {
|
||||
return fmt.Errorf("field %q has repeated name with field %q", f.Name, other.Name)
|
||||
}
|
||||
if other, ok := ids[f.ID]; ok {
|
||||
return fmt.Errorf("field %q has repeated ID (%d) with field %q", f.Name, f.ID, other.Name)
|
||||
}
|
||||
names[f.Name] = f
|
||||
ids[f.ID] = f
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// These are all the points available in the system.
|
||||
func init() {
|
||||
// Points from the sentry namespace.
|
||||
registerPoint(PointDesc{
|
||||
ID: PointClone,
|
||||
Name: "sentry/clone",
|
||||
ContextFields: defaultContextFields,
|
||||
})
|
||||
registerPoint(PointDesc{
|
||||
ID: PointExecve,
|
||||
Name: "sentry/execve",
|
||||
OptionalFields: []FieldDesc{
|
||||
{
|
||||
ID: ExecveFieldBinaryInfo,
|
||||
Name: "binary_info",
|
||||
},
|
||||
},
|
||||
ContextFields: defaultContextFields,
|
||||
})
|
||||
registerPoint(PointDesc{
|
||||
ID: PointExitNotifyParent,
|
||||
Name: "sentry/exit_notify_parent",
|
||||
ContextFields: defaultContextFields,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
// Copyright 2022 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package seccheck
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSinkRegistration(t *testing.T) {
|
||||
sink := SinkDesc{Name: "test"}
|
||||
RegisterSink(sink)
|
||||
if _, ok := sinks["test"]; !ok {
|
||||
t.Errorf("sink registration failed")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
RegisterSink(sink)
|
||||
t.Errorf("Registering the same sink twice should panic")
|
||||
}
|
||||
|
||||
func TestPointRegistration(t *testing.T) {
|
||||
point := PointDesc{Name: "test"}
|
||||
registerPoint(point)
|
||||
if _, ok := points["test"]; !ok {
|
||||
t.Errorf("point registration failed")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
registerPoint(point)
|
||||
t.Errorf("Registering the same point twice should panic")
|
||||
}
|
||||
|
||||
func TestPointRegistrationFields(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
point PointDesc
|
||||
}{
|
||||
{
|
||||
name: "optional_name",
|
||||
point: PointDesc{
|
||||
Name: "test",
|
||||
OptionalFields: []FieldDesc{
|
||||
{ID: 123, Name: "field1"},
|
||||
{ID: 456, Name: "field1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "optional_id",
|
||||
point: PointDesc{
|
||||
Name: "test",
|
||||
OptionalFields: []FieldDesc{
|
||||
{ID: 123, Name: "field1"},
|
||||
{ID: 123, Name: "field2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "context_name",
|
||||
point: PointDesc{
|
||||
Name: "test",
|
||||
ContextFields: []FieldDesc{
|
||||
{ID: 123, Name: "field1"},
|
||||
{ID: 456, Name: "field1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "context_id",
|
||||
point: PointDesc{
|
||||
Name: "test",
|
||||
ContextFields: []FieldDesc{
|
||||
{ID: 123, Name: "field1"},
|
||||
{ID: 123, Name: "field2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
registerPoint(tc.point)
|
||||
t.Errorf("Registering the same point twice should panic")
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ go_library(
|
||||
"loader.go",
|
||||
"network.go",
|
||||
"profile.go",
|
||||
"seccheck.go",
|
||||
"strace.go",
|
||||
"vfs.go",
|
||||
],
|
||||
@@ -81,6 +82,8 @@ go_library(
|
||||
"//pkg/sentry/loader",
|
||||
"//pkg/sentry/pgalloc",
|
||||
"//pkg/sentry/platform",
|
||||
"//pkg/sentry/seccheck",
|
||||
"//pkg/sentry/seccheck/checkers/remote",
|
||||
"//pkg/sentry/socket/hostinet",
|
||||
"//pkg/sentry/socket/netfilter",
|
||||
"//pkg/sentry/socket/netlink",
|
||||
|
||||
@@ -225,6 +225,12 @@ type Args struct {
|
||||
// ProductName is the value to show in
|
||||
// /sys/devices/virtual/dmi/id/product_name.
|
||||
ProductName string
|
||||
// PodInitConfigFD is the file descriptor to a file passed in the
|
||||
// --pod-init-config flag
|
||||
PodInitConfigFD int
|
||||
// SinkFDs is an ordered array of file descriptors to be used by seccheck
|
||||
// sinks configured from the --pod-init-config file.
|
||||
SinkFDs []int
|
||||
}
|
||||
|
||||
// make sure stdioFDs are always the same on initial start and on restore
|
||||
@@ -421,6 +427,12 @@ func New(args Args) (*Loader, error) {
|
||||
k.SetHostMount(k.VFS().NewDisconnectedMount(hostFilesystem, nil, &vfs.MountOptions{}))
|
||||
}
|
||||
|
||||
if args.PodInitConfigFD >= 0 {
|
||||
if err := setupSeccheck(args.PodInitConfigFD, args.SinkFDs); err != nil {
|
||||
log.Warningf("unable to configure event session: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
eid := execID{cid: args.ID}
|
||||
l := &Loader{
|
||||
k: k,
|
||||
|
||||
@@ -134,12 +134,13 @@ func createLoader(vfsEnabled bool, spec *specs.Spec) (*Loader, func(), error) {
|
||||
}
|
||||
|
||||
args := Args{
|
||||
ID: "foo",
|
||||
Spec: spec,
|
||||
Conf: conf,
|
||||
ControllerFD: fd,
|
||||
GoferFDs: []int{sandEnd},
|
||||
StdioFDs: stdio,
|
||||
ID: "foo",
|
||||
Spec: spec,
|
||||
Conf: conf,
|
||||
ControllerFD: fd,
|
||||
GoferFDs: []int{sandEnd},
|
||||
StdioFDs: stdio,
|
||||
PodInitConfigFD: -1,
|
||||
}
|
||||
l, err := New(args)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
// Copyright 2021 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package boot
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/fd"
|
||||
"gvisor.dev/gvisor/pkg/log"
|
||||
"gvisor.dev/gvisor/pkg/sentry/seccheck"
|
||||
|
||||
// Register supported of checkers.
|
||||
_ "gvisor.dev/gvisor/pkg/sentry/seccheck/checkers/remote"
|
||||
)
|
||||
|
||||
// InitConfig represents the configuration to apply during pod creation. For
|
||||
// now, it supports setting up an seccheck session.
|
||||
type InitConfig struct {
|
||||
TraceSession seccheck.SessionConfig `json:"trace_session"`
|
||||
}
|
||||
|
||||
func setupSeccheck(configFD int, sinkFDs []int) error {
|
||||
config := fd.New(configFD)
|
||||
defer config.Close()
|
||||
|
||||
initConf, err := loadInitConfig(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return initConf.configure(sinkFDs)
|
||||
}
|
||||
|
||||
// LoadInitConfig loads an InitConfig struct from a json formatted file.
|
||||
func LoadInitConfig(path string) (*InitConfig, error) {
|
||||
config, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer config.Close()
|
||||
return loadInitConfig(config)
|
||||
}
|
||||
|
||||
func loadInitConfig(reader io.Reader) (*InitConfig, error) {
|
||||
decoder := json.NewDecoder(reader)
|
||||
init := &InitConfig{}
|
||||
if err := decoder.Decode(init); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return init, nil
|
||||
}
|
||||
|
||||
// Setup performs the actions defined in the InitConfig, e.g. setup seccheck
|
||||
// session.
|
||||
func (c *InitConfig) Setup() ([]*os.File, error) {
|
||||
var files []*os.File
|
||||
for _, sink := range c.TraceSession.Sinks {
|
||||
sinkFile, err := seccheck.SetupSink(sink)
|
||||
if err != nil {
|
||||
if !sink.IgnoreSetupError {
|
||||
return nil, err
|
||||
}
|
||||
log.Warningf("Ignoring sink setup failure: %v", err)
|
||||
// Ensure sinkFile is nil and append it to the list to ensure the file
|
||||
// order is preserved.
|
||||
sinkFile = nil
|
||||
}
|
||||
files = append(files, sinkFile)
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (c *InitConfig) configure(sinkFDs []int) error {
|
||||
for i, sinkFD := range sinkFDs {
|
||||
if sinkFD >= 0 {
|
||||
c.TraceSession.Sinks[i].FD = fd.New(sinkFD)
|
||||
}
|
||||
}
|
||||
return seccheck.Configure(&c.TraceSession)
|
||||
|
||||
}
|
||||
+24
-16
@@ -101,6 +101,10 @@ type Boot struct {
|
||||
// Valid if >= 0.
|
||||
traceFD int
|
||||
|
||||
podInitConfigFD int
|
||||
|
||||
sinkFDs intFlags
|
||||
|
||||
// pidns is set if the sandbox is in its own pid namespace.
|
||||
pidns bool
|
||||
|
||||
@@ -154,6 +158,8 @@ func (b *Boot) SetFlags(f *flag.FlagSet) {
|
||||
f.IntVar(&b.profileHeapFD, "profile-heap-fd", -1, "file descriptor to write heap profile to. -1 disables profiling.")
|
||||
f.IntVar(&b.profileMutexFD, "profile-mutex-fd", -1, "file descriptor to write mutex profile to. -1 disables profiling.")
|
||||
f.IntVar(&b.traceFD, "trace-fd", -1, "file descriptor to write Go execution trace to. -1 disables tracing.")
|
||||
f.IntVar(&b.podInitConfigFD, "pod-init-config-fd", -1, "file descriptor to the pod init configuration file.")
|
||||
f.Var(&b.sinkFDs, "sink-fds", "ordered list of file descriptors to be used by the sinks defined in --pod-init-config.")
|
||||
}
|
||||
|
||||
// Execute implements subcommands.Command.Execute. It starts a sandbox in a
|
||||
@@ -274,22 +280,24 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
|
||||
|
||||
// Create the loader.
|
||||
bootArgs := boot.Args{
|
||||
ID: f.Arg(0),
|
||||
Spec: spec,
|
||||
Conf: conf,
|
||||
ControllerFD: b.controllerFD,
|
||||
Device: os.NewFile(uintptr(b.deviceFD), "platform device"),
|
||||
GoferFDs: b.ioFDs.GetArray(),
|
||||
StdioFDs: b.stdioFDs.GetArray(),
|
||||
NumCPU: b.cpuNum,
|
||||
TotalMem: b.totalMem,
|
||||
UserLogFD: b.userLogFD,
|
||||
ProfileBlockFD: b.profileBlockFD,
|
||||
ProfileCPUFD: b.profileCPUFD,
|
||||
ProfileHeapFD: b.profileHeapFD,
|
||||
ProfileMutexFD: b.profileMutexFD,
|
||||
TraceFD: b.traceFD,
|
||||
ProductName: b.productName,
|
||||
ID: f.Arg(0),
|
||||
Spec: spec,
|
||||
Conf: conf,
|
||||
ControllerFD: b.controllerFD,
|
||||
Device: os.NewFile(uintptr(b.deviceFD), "platform device"),
|
||||
GoferFDs: b.ioFDs.GetArray(),
|
||||
StdioFDs: b.stdioFDs.GetArray(),
|
||||
NumCPU: b.cpuNum,
|
||||
TotalMem: b.totalMem,
|
||||
UserLogFD: b.userLogFD,
|
||||
ProfileBlockFD: b.profileBlockFD,
|
||||
ProfileCPUFD: b.profileCPUFD,
|
||||
ProfileHeapFD: b.profileHeapFD,
|
||||
ProfileMutexFD: b.profileMutexFD,
|
||||
TraceFD: b.traceFD,
|
||||
ProductName: b.productName,
|
||||
PodInitConfigFD: b.podInitConfigFD,
|
||||
SinkFDs: b.sinkFDs.GetArray(),
|
||||
}
|
||||
l, err := boot.New(bootArgs)
|
||||
if err != nil {
|
||||
|
||||
@@ -229,6 +229,10 @@ type Config struct {
|
||||
// Use systemd to configure cgroups.
|
||||
SystemdCgroup bool `flag:"systemd-cgroup"`
|
||||
|
||||
// PodInitConfig is the path to configuration file with additional steps to
|
||||
// take during pod creation.
|
||||
PodInitConfig string `flag:"pod-init-config"`
|
||||
|
||||
// TestOnlyAllowRunAsCurrentUserWithoutChroot should only be used in
|
||||
// tests. It allows runsc to start the sandbox process as the current
|
||||
// user, and without chrooting the sandbox process. This can be
|
||||
|
||||
@@ -72,6 +72,7 @@ func RegisterFlags(flagSet *flag.FlagSet) {
|
||||
flagSet.Bool("oci-seccomp", false, "Enables loading OCI seccomp filters inside the sandbox.")
|
||||
flagSet.Var(defaultControlConfig(), "controls", "Sentry control endpoints.")
|
||||
flagSet.Bool("enable-core-tags", false, "enables core tagging. Requires host linux kernel >= 5.14.")
|
||||
flagSet.String("pod-init-config", "", "path to configuration file with additional steps to take during pod creation.")
|
||||
|
||||
// Flags that control sandbox runtime behavior: FS related.
|
||||
flagSet.Var(fileAccessTypePtr(FileAccessExclusive), "file-access", "specifies which filesystem validation to use for the root mount: exclusive (default), shared.")
|
||||
|
||||
@@ -45,7 +45,8 @@ type donation struct {
|
||||
|
||||
// Donate sets up the given files to be donated to another process. The FD
|
||||
// in which the new file will appear in the child process is added as a flag to
|
||||
// the child process, e.g. --flag=3.
|
||||
// the child process, e.g. --flag=3. In case the file is nil, -1 is used for the
|
||||
// flag value and no file is donated to the next process.
|
||||
func (f *Agency) Donate(flag string, files ...*os.File) {
|
||||
f.donations = append(f.donations, donation{flag: flag, files: files})
|
||||
}
|
||||
@@ -91,9 +92,13 @@ func (f *Agency) DonateDebugLogFile(flag, logPattern, command, test string) erro
|
||||
func (f *Agency) Transfer(cmd *exec.Cmd, nextFD int) int {
|
||||
for _, d := range f.donations {
|
||||
for _, file := range d.files {
|
||||
cmd.ExtraFiles = append(cmd.ExtraFiles, file)
|
||||
cmd.Args = append(cmd.Args, fmt.Sprintf("--%s=%d", d.flag, nextFD))
|
||||
nextFD++
|
||||
fd := -1
|
||||
if file != nil {
|
||||
cmd.ExtraFiles = append(cmd.ExtraFiles, file)
|
||||
fd = nextFD
|
||||
nextFD++
|
||||
}
|
||||
cmd.Args = append(cmd.Args, fmt.Sprintf("--%s=%d", d.flag, fd))
|
||||
}
|
||||
}
|
||||
// Reset donations made so far in case more transfers are needed.
|
||||
@@ -104,7 +109,9 @@ func (f *Agency) Transfer(cmd *exec.Cmd, nextFD int) int {
|
||||
// Close closes any files the agency has taken ownership over.
|
||||
func (f *Agency) Close() {
|
||||
for _, file := range f.closePending {
|
||||
_ = file.Close()
|
||||
if file != nil {
|
||||
_ = file.Close()
|
||||
}
|
||||
}
|
||||
f.closePending = nil
|
||||
}
|
||||
|
||||
@@ -166,6 +166,10 @@ type Args struct {
|
||||
// Attached indicates that the sandbox lifecycle is attached with the caller.
|
||||
// If the caller exits, the sandbox should exit too.
|
||||
Attached bool
|
||||
|
||||
// SinkFiles is the an ordered array of files to be used by seccheck sinks
|
||||
// configured from the --pod-init-config file.
|
||||
SinkFiles []*os.File
|
||||
}
|
||||
|
||||
// New creates the sandbox process. The caller must call Destroy() on the
|
||||
@@ -189,6 +193,17 @@ func New(conf *config.Config, args *Args) (*Sandbox, error) {
|
||||
})
|
||||
defer c.Clean()
|
||||
|
||||
if len(conf.PodInitConfig) > 0 {
|
||||
initConf, err := boot.LoadInitConfig(conf.PodInitConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args.SinkFiles, err = initConf.Setup()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Create pipe to synchronize when sandbox process has been booted.
|
||||
clientSyncFile, sandboxSyncFile, err := os.Pipe()
|
||||
if err != nil {
|
||||
@@ -530,6 +545,11 @@ func (s *Sandbox) createSandboxProcess(conf *config.Config, args *Args, startSyn
|
||||
}
|
||||
donations.DonateAndClose("spec-fd", specFile)
|
||||
|
||||
if err := donations.OpenAndDonate("pod-init-config-fd", conf.PodInitConfig, os.O_RDONLY); err != nil {
|
||||
return err
|
||||
}
|
||||
donations.DonateAndClose("sink-fds", args.SinkFiles...)
|
||||
|
||||
gPlatform, err := platform.Lookup(conf.Platform)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user