github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/runsc/config/flags.go (about) 1 // Copyright 2020 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package config 16 17 import ( 18 "bytes" 19 "fmt" 20 "os" 21 "path/filepath" 22 "reflect" 23 "sort" 24 "strconv" 25 "strings" 26 "text/template" 27 28 "github.com/nicocha30/gvisor-ligolo/pkg/log" 29 "github.com/nicocha30/gvisor-ligolo/pkg/refs" 30 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/watchdog" 31 "github.com/nicocha30/gvisor-ligolo/runsc/flag" 32 ) 33 34 // RegisterFlags registers flags used to populate Config. 35 func RegisterFlags(flagSet *flag.FlagSet) { 36 // Although these flags are not part of the OCI spec, they are used by 37 // Docker, and thus should not be changed. 38 flagSet.String("root", "", "root directory for storage of container state.") 39 flagSet.String("log", "", "file path where internal debug information is written, default is stdout.") 40 flagSet.String("log-format", "text", "log format: text (default), json, or json-k8s.") 41 flagSet.Bool("debug", false, "enable debug logging.") 42 flagSet.Bool("systemd-cgroup", false, "EXPERIMENTAL. Use systemd for cgroups.") 43 44 // These flags are unique to runsc, and are used to configure parts of the 45 // system that are not covered by the runtime spec. 46 47 // Debugging flags. 48 flagSet.String("debug-log", "", "additional location for logs. If it ends with '/', log files are created inside the directory with default names. The following variables are available: %TIMESTAMP%, %COMMAND%.") 49 flagSet.String("debug-command", "", `comma-separated list of commands to be debugged if --debug-log is also set. Empty means debug all. "!" negates the expression. E.g. "create,start" or "!boot,events"`) 50 flagSet.String("panic-log", "", "file path where panic reports and other Go's runtime messages are written.") 51 flagSet.String("coverage-report", "", "file path where Go coverage reports are written. Reports will only be generated if runsc is built with --collect_code_coverage and --instrumentation_filter Bazel flags.") 52 flagSet.Bool("log-packets", false, "enable network packet logging.") 53 flagSet.String("pcap-log", "", "location of PCAP log file.") 54 flagSet.String("debug-log-format", "text", "log format: text (default), json, or json-k8s.") 55 // Only register -alsologtostderr flag if it is not already defined on this flagSet. 56 if flagSet.Lookup("alsologtostderr") == nil { 57 flagSet.Bool("alsologtostderr", false, "send log messages to stderr.") 58 } 59 flagSet.Bool("allow-flag-override", false, "allow OCI annotations (dev.gvisor.flag.<name>) to override flags for debugging.") 60 flagSet.String("traceback", "system", "golang runtime's traceback level") 61 62 // Metrics flags. 63 flagSet.String("metric-server", "", "if set, export metrics on this address. This may either be 1) 'addr:port' to export metrics on a specific network interface address, 2) ':port' for exporting metrics on all interfaces, or 3) an absolute path to a Unix Domain Socket. The substring '%ID%' will be replaced by the container ID, and '%RUNTIME_ROOT%' by the root. This flag must be specified in both `runsc metric-server` and `runsc create`, and their values must match.") 64 65 // Debugging flags: strace related 66 flagSet.Bool("strace", false, "enable strace.") 67 flagSet.String("strace-syscalls", "", "comma-separated list of syscalls to trace. If --strace is true and this list is empty, then all syscalls will be traced.") 68 flagSet.Uint("strace-log-size", 1024, "default size (in bytes) to log data argument blobs.") 69 flagSet.Bool("strace-event", false, "send strace to event.") 70 71 // Flags that control sandbox runtime behavior. 72 flagSet.String("platform", "systrap", "specifies which platform to use: systrap (default), ptrace, kvm.") 73 flagSet.String("platform_device_path", "", "path to a platform-specific device file (e.g. /dev/kvm for KVM platform). If unset, will use a sane platform-specific default.") 74 flagSet.Var(watchdogActionPtr(watchdog.LogWarning), "watchdog-action", "sets what action the watchdog takes when triggered: log (default), panic.") 75 flagSet.Int("panic-signal", -1, "register signal handling that panics. Usually set to SIGUSR2(12) to troubleshoot hangs. -1 disables it.") 76 flagSet.Bool("profile", false, "prepares the sandbox to use Golang profiler. Note that enabling profiler loosens the seccomp protection added to the sandbox (DO NOT USE IN PRODUCTION).") 77 flagSet.String("profile-block", "", "collects a block profile to this file path for the duration of the container execution. Requires -profile=true.") 78 flagSet.String("profile-cpu", "", "collects a CPU profile to this file path for the duration of the container execution. Requires -profile=true.") 79 flagSet.String("profile-heap", "", "collects a heap profile to this file path for the duration of the container execution. Requires -profile=true.") 80 flagSet.String("profile-mutex", "", "collects a mutex profile to this file path for the duration of the container execution. Requires -profile=true.") 81 flagSet.String("trace", "", "collects a Go runtime execution trace to this file path for the duration of the container execution.") 82 flagSet.Bool("rootless", false, "it allows the sandbox to be started with a user that is not root. Sandbox and Gofer processes may run with same privileges as current user.") 83 flagSet.Var(leakModePtr(refs.NoLeakChecking), "ref-leak-mode", "sets reference leak check mode: disabled (default), log-names, log-traces.") 84 flagSet.Bool("cpu-num-from-quota", false, "set cpu number to cpu quota (least integer greater or equal to quota value, but not less than 2)") 85 flagSet.Bool("oci-seccomp", false, "Enables loading OCI seccomp filters inside the sandbox.") 86 flagSet.Bool("enable-core-tags", false, "enables core tagging. Requires host linux kernel >= 5.14.") 87 flagSet.String("pod-init-config", "", "path to configuration file with additional steps to take during pod creation.") 88 89 // Flags that control sandbox runtime behavior: FS related. 90 flagSet.Var(fileAccessTypePtr(FileAccessExclusive), "file-access", "specifies which filesystem validation to use for the root mount: exclusive (default), shared.") 91 flagSet.Var(fileAccessTypePtr(FileAccessShared), "file-access-mounts", "specifies which filesystem validation to use for volumes other than the root mount: shared (default), exclusive.") 92 flagSet.Bool("overlay", false, "DEPRECATED: use --overlay2=all:memory to achieve the same effect") 93 flagSet.Var(defaultOverlay2(), "overlay2", "wrap mounts with overlayfs. Format is {mount}:{medium}, where 'mount' can be 'root' or 'all' and medium can be 'memory', 'self' or 'dir=/abs/dir/path' in which filestore will be created. 'none' will turn overlay mode off.") 94 flagSet.Bool("fsgofer-host-uds", false, "DEPRECATED: use host-uds=all") 95 flagSet.Var(hostUDSPtr(HostUDSNone), "host-uds", "controls permission to access host Unix-domain sockets. Values: none|open|create|all, default: none") 96 flagSet.Var(hostFifoPtr(HostFifoNone), "host-fifo", "controls permission to access host FIFOs (or named pipes). Values: none|open, default: none") 97 98 flagSet.Bool("vfs2", true, "DEPRECATED: this flag has no effect.") 99 flagSet.Bool("fuse", true, "DEPRECATED: this flag has no effect.") 100 flagSet.Bool("lisafs", true, "DEPRECATED: this flag has no effect.") 101 flagSet.Bool("cgroupfs", false, "Automatically mount cgroupfs.") 102 flagSet.Bool("ignore-cgroups", false, "don't configure cgroups.") 103 flagSet.Int("fdlimit", -1, "Specifies a limit on the number of host file descriptors that can be open. Applies separately to the sentry and gofer. Note: each file in the sandbox holds more than one host FD open.") 104 flagSet.Int("dcache", -1, "Set the global dentry cache size. This acts as a coarse-grained control on the number of host FDs simultaneously open by the sentry. If negative, per-mount caches are used.") 105 flagSet.Bool("iouring", false, "TEST ONLY; Enables io_uring syscalls in the sentry. Support is experimental and very limited.") 106 flagSet.Bool("directfs", true, "directly access the container filesystems from the sentry. Sentry runs with higher privileges.") 107 108 // Flags that control sandbox runtime behavior: network related. 109 flagSet.Var(networkTypePtr(NetworkSandbox), "network", "specifies which network to use: sandbox (default), host, none. Using network inside the sandbox is more secure because it's isolated from the host network.") 110 flagSet.Bool("net-raw", false, "enable raw sockets. When false, raw sockets are disabled by removing CAP_NET_RAW from containers (`runsc exec` will still be able to utilize raw sockets). Raw sockets allow malicious containers to craft packets and potentially attack the network.") 111 flagSet.Bool("gso", true, "enable host segmentation offload if it is supported by a network device.") 112 flagSet.Bool("software-gso", true, "enable gVisor segmentation offload when host offload can't be enabled.") 113 flagSet.Duration("gvisor-gro", 0, "(e.g. \"20000ns\" or \"1ms\") sets gVisor's generic receive offload timeout. Zero bypasses GRO.") 114 flagSet.Bool("tx-checksum-offload", false, "enable TX checksum offload.") 115 flagSet.Bool("rx-checksum-offload", true, "enable RX checksum offload.") 116 flagSet.Var(queueingDisciplinePtr(QDiscFIFO), "qdisc", "specifies which queueing discipline to apply by default to the non loopback nics used by the sandbox.") 117 flagSet.Int("num-network-channels", 1, "number of underlying channels(FDs) to use for network link endpoints.") 118 flagSet.Bool("buffer-pooling", true, "enable allocation of buffers from a shared pool instead of the heap.") 119 flagSet.Bool("EXPERIMENTAL-afxdp", false, "EXPERIMENTAL. Use an AF_XDP socket to receive packets.") 120 121 // Flags that control sandbox runtime behavior: accelerator related. 122 flagSet.Bool("nvproxy", false, "EXPERIMENTAL: enable support for Nvidia GPUs") 123 flagSet.Bool("nvproxy-docker", false, "Expose GPUs to containers based on NVIDIA_VISIBLE_DEVICES, as requested by the container or set by `docker --gpus`. Allows containers to self-serve GPU access and thus disabled by default for security. libnvidia-container must be installed on the host. No effect unless --nvproxy is enabled.") 124 flagSet.Bool("tpuproxy", false, "EXPERIMENTAL: enable support for TPU device passthrough.") 125 126 // Test flags, not to be used outside tests, ever. 127 flagSet.Bool("TESTONLY-unsafe-nonroot", false, "TEST ONLY; do not ever use! This skips many security measures that isolate the host from the sandbox.") 128 flagSet.String("TESTONLY-test-name-env", "", "TEST ONLY; do not ever use! Used for automated tests to improve logging.") 129 flagSet.Bool("TESTONLY-allow-packet-endpoint-write", false, "TEST ONLY; do not ever use! Used for tests to allow writes on packet sockets.") 130 flagSet.Bool("TESTONLY-afs-syscall-panic", false, "TEST ONLY; do not ever use! Used for tests exercising gVisor panic reporting.") 131 } 132 133 // overrideAllowlist lists all flags that can be changed using OCI 134 // annotations without an administrator setting `--allow-flag-override` on the 135 // runtime. Flags in this list can be set by container authors and should not 136 // make the sandbox less secure. 137 var overrideAllowlist = map[string]struct { 138 check func(name string, value string) error 139 }{ 140 "debug": {}, 141 "strace": {}, 142 "strace-syscalls": {}, 143 "strace-log-size": {}, 144 "host-uds": {}, 145 146 "oci-seccomp": {check: checkOciSeccomp}, 147 } 148 149 // checkOciSeccomp ensures that seccomp can be enabled but not disabled. 150 func checkOciSeccomp(name string, value string) error { 151 enable, err := strconv.ParseBool(value) 152 if err != nil { 153 return err 154 } 155 if !enable { 156 return fmt.Errorf("disabling %q requires flag %q to be enabled", name, "allow-flag-override") 157 } 158 return nil 159 } 160 161 // isFlagExplicitlySet returns whether the given flag name is explicitly set. 162 // Doesn't check for flag existence; returns `false` for flags that don't exist. 163 func isFlagExplicitlySet(flagSet *flag.FlagSet, name string) bool { 164 explicit := false 165 166 // The FlagSet.Visit function only visits flags that are explicitly set, as opposed to VisitAll. 167 flagSet.Visit(func(fl *flag.Flag) { 168 explicit = explicit || fl.Name == name 169 }) 170 171 return explicit 172 } 173 174 // NewFromFlags creates a new Config with values coming from command line flags. 175 func NewFromFlags(flagSet *flag.FlagSet) (*Config, error) { 176 conf := &Config{explicitlySet: map[string]struct{}{}} 177 178 obj := reflect.ValueOf(conf).Elem() 179 st := obj.Type() 180 for i := 0; i < st.NumField(); i++ { 181 f := st.Field(i) 182 name, ok := f.Tag.Lookup("flag") 183 if !ok { 184 // No flag set for this field. 185 continue 186 } 187 fl := flagSet.Lookup(name) 188 if fl == nil { 189 panic(fmt.Sprintf("Flag %q not found", name)) 190 } 191 x := reflect.ValueOf(flag.Get(fl.Value)) 192 obj.Field(i).Set(x) 193 if isFlagExplicitlySet(flagSet, name) { 194 conf.explicitlySet[name] = struct{}{} 195 } 196 } 197 198 if len(conf.RootDir) == 0 { 199 // If not set, set default root dir to something (hopefully) user-writeable. 200 conf.RootDir = "/var/run/runsc" 201 // NOTE: empty values for XDG_RUNTIME_DIR should be ignored. 202 if runtimeDir := os.Getenv("XDG_RUNTIME_DIR"); runtimeDir != "" { 203 conf.RootDir = filepath.Join(runtimeDir, "runsc") 204 } 205 } 206 207 if err := conf.validate(); err != nil { 208 return nil, err 209 } 210 return conf, nil 211 } 212 213 // NewFromBundle makes a new config from a Bundle. 214 func NewFromBundle(bundle Bundle) (*Config, error) { 215 if err := bundle.Validate(); err != nil { 216 return nil, err 217 } 218 flagSet := flag.NewFlagSet("tmp", flag.ContinueOnError) 219 RegisterFlags(flagSet) 220 conf := &Config{explicitlySet: map[string]struct{}{}} 221 222 obj := reflect.ValueOf(conf).Elem() 223 st := obj.Type() 224 for i := 0; i < st.NumField(); i++ { 225 f := st.Field(i) 226 name, ok := f.Tag.Lookup("flag") 227 if !ok { 228 continue 229 } 230 fl := flagSet.Lookup(name) 231 if fl == nil { 232 return nil, fmt.Errorf("flag %q not found", name) 233 } 234 val, ok := bundle[name] 235 if !ok { 236 continue 237 } 238 if err := flagSet.Set(name, val); err != nil { 239 return nil, fmt.Errorf("error setting flag %s=%q: %w", name, val, err) 240 } 241 conf.Override(flagSet, name, val, true) 242 243 conf.explicitlySet[name] = struct{}{} 244 } 245 return conf, nil 246 } 247 248 // ToFlags returns a slice of flags that correspond to the given Config. 249 func (c *Config) ToFlags() []string { 250 flagSet := flag.NewFlagSet("tmp", flag.ContinueOnError) 251 RegisterFlags(flagSet) 252 253 var rv []string 254 keyVals := c.keyVals(flagSet, false /*onlyIfSet*/) 255 for name, val := range keyVals { 256 rv = append(rv, fmt.Sprintf("--%s=%s", name, val)) 257 } 258 259 // Construct a temporary set for default plumbing. 260 return rv 261 } 262 263 // KeyVal is a key value pair. It is used so ToContainerdConfigTOML returns 264 // predictable ordering for runsc flags. 265 type KeyVal struct { 266 Key string 267 Val string 268 } 269 270 // ContainerdConfigOptions contains arguments for ToContainerdConfigTOML. 271 type ContainerdConfigOptions struct { 272 BinaryPath string 273 RootPath string 274 Options map[string]string 275 RunscFlags []KeyVal 276 } 277 278 // ToContainerdConfigTOML turns a given config into a format for a k8s containerd config.toml file. 279 // See: https://gvisor.dev/docs/user_guide/containerd/quick_start/ 280 func (c *Config) ToContainerdConfigTOML(opts ContainerdConfigOptions) (string, error) { 281 flagSet := flag.NewFlagSet("tmp", flag.ContinueOnError) 282 RegisterFlags(flagSet) 283 keyVals := c.keyVals(flagSet, true /*onlyIfSet*/) 284 keys := []string{} 285 for k := range keyVals { 286 keys = append(keys, k) 287 } 288 289 sort.Strings(keys) 290 291 for _, k := range keys { 292 opts.RunscFlags = append(opts.RunscFlags, KeyVal{k, keyVals[k]}) 293 } 294 295 const temp = `{{if .BinaryPath}}binary_name = "{{.BinaryPath}}"{{end}} 296 {{if .RootPath}}root = "{{.RootPath}}"{{end}} 297 {{if .Options}}{{ range $key, $value := .Options}}{{$key}} = "{{$value}}" 298 {{end}}{{end}}{{if .RunscFlags}}[runsc_config] 299 {{ range $fl:= .RunscFlags}} {{$fl.Key}} = "{{$fl.Val}}" 300 {{end}}{{end}}` 301 302 t := template.New("temp") 303 t, err := t.Parse(temp) 304 if err != nil { 305 return "", err 306 } 307 var buf bytes.Buffer 308 if err := t.Execute(&buf, opts); err != nil { 309 return "", err 310 } 311 return buf.String(), nil 312 } 313 314 func (c *Config) keyVals(flagSet *flag.FlagSet, onlyIfSet bool) map[string]string { 315 keyVals := make(map[string]string) 316 317 obj := reflect.ValueOf(c).Elem() 318 st := obj.Type() 319 for i := 0; i < st.NumField(); i++ { 320 f := st.Field(i) 321 name, ok := f.Tag.Lookup("flag") 322 if !ok { 323 // No flag set for this field. 324 continue 325 } 326 val := getVal(obj.Field(i)) 327 328 fl := flagSet.Lookup(name) 329 if fl == nil { 330 panic(fmt.Sprintf("Flag %q not found", name)) 331 } 332 if val == fl.DefValue || onlyIfSet { 333 // If this config wasn't populated from a FlagSet, don't plumb through default flags. 334 if c.explicitlySet == nil { 335 continue 336 } 337 // If this config was populated from a FlagSet, plumb through only default flags which were 338 // explicitly specified. 339 if _, explicit := c.explicitlySet[name]; !explicit { 340 continue 341 } 342 } 343 keyVals[fl.Name] = val 344 } 345 return keyVals 346 } 347 348 // Override writes a new value to a flag. 349 func (c *Config) Override(flagSet *flag.FlagSet, name string, value string, force bool) error { 350 obj := reflect.ValueOf(c).Elem() 351 st := obj.Type() 352 for i := 0; i < st.NumField(); i++ { 353 f := st.Field(i) 354 fieldName, ok := f.Tag.Lookup("flag") 355 if !ok || fieldName != name { 356 // Not a flag field, or flag name doesn't match. 357 continue 358 } 359 fl := flagSet.Lookup(name) 360 if fl == nil { 361 // Flag must exist if there is a field match above. 362 panic(fmt.Sprintf("Flag %q not found", name)) 363 } 364 if !force { 365 if err := c.isOverrideAllowed(name, value); err != nil { 366 return fmt.Errorf("error setting flag %s=%q: %w", name, value, err) 367 } 368 } 369 370 // Use flag to convert the string value to the underlying flag type, using 371 // the same rules as the command-line for consistency. 372 if err := fl.Value.Set(value); err != nil { 373 return fmt.Errorf("error setting flag %s=%q: %w", name, value, err) 374 } 375 x := reflect.ValueOf(flag.Get(fl.Value)) 376 obj.Field(i).Set(x) 377 378 // Validates the config again to ensure it's left in a consistent state. 379 return c.validate() 380 } 381 return fmt.Errorf("flag %q not found. Cannot set it to %q", name, value) 382 } 383 384 func (c *Config) isOverrideAllowed(name string, value string) error { 385 if c.AllowFlagOverride { 386 return nil 387 } 388 // If the global override flag is not enabled, check if the individual flag is 389 // safe to apply. 390 if allow, ok := overrideAllowlist[name]; ok { 391 if allow.check != nil { 392 if err := allow.check(name, value); err != nil { 393 return err 394 } 395 } 396 return nil 397 } 398 return fmt.Errorf("flag override disabled, use --allow-flag-override to enable it") 399 } 400 401 // ApplyBundles applies the given bundles by name. 402 // It returns an error if a bundle doesn't exist, or if the given 403 // bundles have conflicting flag values. 404 // Config values which are already specified prior to calling ApplyBundles are overridden. 405 func (c *Config) ApplyBundles(flagSet *flag.FlagSet, bundleNames ...BundleName) error { 406 // Populate a map from flag name to flag value to bundle name. 407 flagToValueToBundleName := make(map[string]map[string]BundleName) 408 for _, bundleName := range bundleNames { 409 b := Bundles[bundleName] 410 if b == nil { 411 return fmt.Errorf("no such bundle: %q", bundleName) 412 } 413 for flagName, val := range b { 414 valueToBundleName := flagToValueToBundleName[flagName] 415 if valueToBundleName == nil { 416 valueToBundleName = make(map[string]BundleName) 417 flagToValueToBundleName[flagName] = valueToBundleName 418 } 419 valueToBundleName[val] = bundleName 420 } 421 } 422 // Check for conflicting flag values between the bundles. 423 for flagName, valueToBundleName := range flagToValueToBundleName { 424 if len(valueToBundleName) == 1 { 425 continue 426 } 427 bundleNameToValue := make(map[string]string) 428 for val, bundleName := range valueToBundleName { 429 bundleNameToValue[string(bundleName)] = val 430 } 431 var sb strings.Builder 432 first := true 433 for _, bundleName := range bundleNames { 434 if val, ok := bundleNameToValue[string(bundleName)]; ok { 435 if !first { 436 sb.WriteString(", ") 437 } 438 sb.WriteString(fmt.Sprintf("bundle %q sets --%s=%q", bundleName, flagName, val)) 439 first = false 440 } 441 } 442 return fmt.Errorf("flag --%s is specified by multiple bundles: %s", flagName, sb.String()) 443 } 444 445 // Actually apply flag values. 446 for flagName, valueToBundleName := range flagToValueToBundleName { 447 fl := flagSet.Lookup(flagName) 448 if fl == nil { 449 return fmt.Errorf("flag --%s not found", flagName) 450 } 451 prevValue := fl.Value.String() 452 // Note: We verified earlier that valueToBundleName has length 1, 453 // so this loop executes exactly once per flag. 454 for val, bundleName := range valueToBundleName { 455 if prevValue == val { 456 continue 457 } 458 if isFlagExplicitlySet(flagSet, flagName) { 459 log.Infof("Flag --%s has explicitly-set value %q, but bundle %s takes precedence and is overriding its value to --%s=%q.", flagName, prevValue, bundleName, flagName, val) 460 } else { 461 log.Infof("Overriding flag --%s=%q from applying bundle %s.", flagName, val, bundleName) 462 } 463 if err := c.Override(flagSet, flagName, val /* force= */, true); err != nil { 464 return err 465 } 466 } 467 } 468 469 return c.validate() 470 } 471 472 func getVal(field reflect.Value) string { 473 if str, ok := field.Addr().Interface().(fmt.Stringer); ok { 474 return str.String() 475 } 476 switch field.Kind() { 477 case reflect.Bool: 478 return strconv.FormatBool(field.Bool()) 479 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 480 return strconv.FormatInt(field.Int(), 10) 481 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 482 return strconv.FormatUint(field.Uint(), 10) 483 case reflect.String: 484 return field.String() 485 default: 486 panic("unknown type " + field.Kind().String()) 487 } 488 }