diff --git a/runsc/boot/seccheck.go b/runsc/boot/seccheck.go index 97ecce594..df4a49ff0 100644 --- a/runsc/boot/seccheck.go +++ b/runsc/boot/seccheck.go @@ -55,6 +55,7 @@ func LoadInitConfig(path string) (*InitConfig, error) { func loadInitConfig(reader io.Reader) (*InitConfig, error) { decoder := json.NewDecoder(reader) + decoder.DisallowUnknownFields() init := &InitConfig{} if err := decoder.Decode(init); err != nil { return nil, err diff --git a/runsc/cmd/trace/BUILD b/runsc/cmd/trace/BUILD index ed67f8aad..9149a3881 100644 --- a/runsc/cmd/trace/BUILD +++ b/runsc/cmd/trace/BUILD @@ -1,4 +1,4 @@ -load("//tools:defs.bzl", "go_library") +load("//tools:defs.bzl", "go_library", "go_test") package(licenses = ["notice"]) @@ -18,6 +18,7 @@ go_library( deps = [ "//pkg/log", "//pkg/sentry/seccheck", + "//runsc/boot", "//runsc/cmd/util", "//runsc/config", "//runsc/container", @@ -25,3 +26,15 @@ go_library( "@com_github_google_subcommands//:go_default_library", ], ) + +go_test( + name = "trace_test", + size = "small", + srcs = ["create_test.go"], + library = ":trace", + deps = [ + "//pkg/sentry/seccheck", + "//pkg/test/testutil", + "//runsc/boot", + ], +) diff --git a/runsc/cmd/trace/create.go b/runsc/cmd/trace/create.go index b779750c6..fdfd440fc 100644 --- a/runsc/cmd/trace/create.go +++ b/runsc/cmd/trace/create.go @@ -17,10 +17,13 @@ package trace import ( "context" "encoding/json" + "fmt" "os" "github.com/google/subcommands" + "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/sentry/seccheck" + "gvisor.dev/gvisor/runsc/boot" "gvisor.dev/gvisor/runsc/cmd/util" "gvisor.dev/gvisor/runsc/config" "gvisor.dev/gvisor/runsc/container" @@ -66,15 +69,9 @@ func (l *create) Execute(_ context.Context, f *flag.FlagSet, args ...interface{} return util.Errorf("missing path to configuration file, please set --config=[path]") } - file, err := os.Open(l.config) + sessionConfig, err := decodeTraceConfig(l.config) if err != nil { - return util.Errorf(err.Error()) - } - defer file.Close() - decoder := json.NewDecoder(file) - sessionConfig := &seccheck.SessionConfig{} - if err := decoder.Decode(sessionConfig); err != nil { - return util.Errorf("invalid configuration file: %v", err) + return util.Errorf("loading config file: %v", err) } id := f.Arg(0) @@ -95,3 +92,33 @@ func (l *create) Execute(_ context.Context, f *flag.FlagSet, args ...interface{} return subcommands.ExitSuccess } + +func decodeTraceConfig(path string) (*seccheck.SessionConfig, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + decoder := json.NewDecoder(file) + decoder.DisallowUnknownFields() + sessionConfig := &seccheck.SessionConfig{} + err = decoder.Decode(sessionConfig) + if err == nil { + // Success, we're done. + return sessionConfig, nil + } + + // If file cannot be decoded as a SessionConfig, try with InitConfig as + // convenience in case the caller wants to reuse a trace session from + // InitConfig file. + log.Debugf("Config file is not a seccheck.SessionConfig, try with boot.InitConfig instead: %v", err) + if _, err := file.Seek(0, 0); err != nil { + return nil, err + } + initConfig := &boot.InitConfig{} + if err := decoder.Decode(initConfig); err != nil { + return nil, fmt.Errorf("invalid configuration file: %w", err) + } + return &initConfig.TraceSession, nil +} diff --git a/runsc/cmd/trace/create_test.go b/runsc/cmd/trace/create_test.go new file mode 100644 index 000000000..301ea9cf7 --- /dev/null +++ b/runsc/cmd/trace/create_test.go @@ -0,0 +1,79 @@ +// 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 trace + +import ( + "encoding/json" + "os" + "reflect" + "strings" + "testing" + + "gvisor.dev/gvisor/pkg/sentry/seccheck" + "gvisor.dev/gvisor/pkg/test/testutil" + "gvisor.dev/gvisor/runsc/boot" +) + +func TestConfigFile(t *testing.T) { + testCfg := seccheck.SessionConfig{ + Name: "Default", + Points: []seccheck.PointConfig{ + {Name: "point-1"}, + }, + Sinks: []seccheck.SinkConfig{{Name: "sink-1"}}, + } + + for _, tc := range []struct { + name string + json interface{} + want seccheck.SessionConfig + err string + }{ + { + name: "SessionConfig", + json: testCfg, + want: testCfg, + }, + { + name: "InitConfig", + json: boot.InitConfig{TraceSession: testCfg}, + want: testCfg, + }, + } { + t.Run(tc.name, func(t *testing.T) { + tmp, err := os.CreateTemp(testutil.TmpDir(), "trace-create") + if err != nil { + t.Fatal(err) + } + defer tmp.Close() + encoder := json.NewEncoder(tmp) + if err := encoder.Encode(tc.json); err != nil { + t.Fatal(err) + } + + config, err := decodeTraceConfig(tmp.Name()) + if len(tc.err) == 0 { + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(&tc.want, config) { + t.Errorf("loaded trace session is different, want: %+v, got: %+v", &tc.want, config) + } + } else if err == nil || !strings.Contains(err.Error(), tc.err) { + t.Errorf("unexpected error, want: %q, got: %v", tc.err, err) + } + }) + } +}