From ccd3f6e9918a19ec7d0a61d93bace39432ada7f7 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Wed, 21 Sep 2016 20:54:22 -0700 Subject: [PATCH] sanitize: Add a config-sanitization command Based on image-spec, which currently exposes [1]: * User (user and group by ID or name) Represented in runtime-spec by process.user.*, although I'm clearing additionalGids for now because the old formats gave no way to represent that. * Memory (limit) Represented in runtime-spec by linux.resources.memory.limit and solaris.cappedMemory.physical. * MemorySwap (memory + swap limit) Represented in runtime-spec by linux.resources.memory.swap and solaris.cappedMemory.physical + solaris.cappedMemory.swap. * CpuShares (relative weight vs. other containers) Represented in runtime-spec by linux.resources.cpu.shares. Solaris has an ncpus property, but no shares analog. * ExposedPorts (a set of port/protocol entries) This is not covered by runtime-spec, but image-spec was planning on stuffing it into annotations [2]. * Env (array of environment variables) Represented in runtime-spec by process.env. * Entrypoint (array of positional arguments) Represented in runtime-spec by process.cmd. * Cmd (array of positional arguments) Represented in runtime-spec by process.cmd. * Volumes (set of directories which should have data volumes mounted on them) Represented in runtime-spec by mounts, although Volumes doesn't include source locations. * WorkingDir (initial working directory of container process) Represented in runtime-spec by process.cwd. Entrypoint and Cmd are slightly different and have a few different forms each [3,4]. Both are represented in the runtime-spec config by process.args. This commit sets all other config settings to their default values, and it clears mounts[].source to mimic the Volumes information. image-spec currently sets up source-less bind-mounts when translating Volumes [5]. With the sanitization command, *any* runtime configuration can be sanitized to be just as safe and limited as the current image-spec config. [1]: https://github.com/opencontainers/image-spec/blob/v0.5.0/serialization.md#container-runconfig-field-descriptions [2]: https://github.com/opencontainers/image-spec/issues/87#issuecomment-224185157 Subject: Convert a serialization config JSON to a OCI runtime configuration. [3]: https://docs.docker.com/engine/reference/builder/#entrypoint [4]: https://docs.docker.com/engine/reference/builder/#cmd [5]: https://github.com/opencontainers/image-spec/blob/v0.5.0/image/config.go#L127-L136 Signed-off-by: W. Trevor King --- cmd/oci-runtime-tool/main.go | 1 + cmd/oci-runtime-tool/sanitize.go | 67 ++++++++++++++++++++++++++++ sanitize/sanitize.go | 76 ++++++++++++++++++++++++++++++++ 3 files changed, 144 insertions(+) create mode 100644 cmd/oci-runtime-tool/sanitize.go create mode 100644 sanitize/sanitize.go diff --git a/cmd/oci-runtime-tool/main.go b/cmd/oci-runtime-tool/main.go index 7ced60de2..f3b4b0b72 100644 --- a/cmd/oci-runtime-tool/main.go +++ b/cmd/oci-runtime-tool/main.go @@ -27,6 +27,7 @@ func main() { app.Commands = []cli.Command{ generateCommand, bundleValidateCommand, + sanitizeCommand, } if err := app.Run(os.Args); err != nil { diff --git a/cmd/oci-runtime-tool/sanitize.go b/cmd/oci-runtime-tool/sanitize.go new file mode 100644 index 000000000..158a805ce --- /dev/null +++ b/cmd/oci-runtime-tool/sanitize.go @@ -0,0 +1,67 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "os" + + rspec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/sanitize" + "github.com/urfave/cli" +) + +var sanitizeFlags = []cli.Flag{ + cli.StringFlag{Name: "output", Usage: "output file (defaults to stdout)"}, +} + +var sanitizeCommand = cli.Command{ + Name: "sanitize", + Usage: "sanitize an OCI runtime configuration file", + Flags: sanitizeFlags, + Before: before, + Action: func(context *cli.Context) (err error) { + var reader io.ReadCloser + if context.NArg() == 0 { + reader = os.Stdin + } else if context.NArg() == 1 { + reader, err = os.Open(context.Args().First()) + if err != nil { + return err + } + defer reader.Close() + } else { + return fmt.Errorf("too many arguments (%d > 1)", context.NArg()) + } + + var config rspec.Spec + err = json.NewDecoder(reader).Decode(&config) + if err != nil { + return err + } + + err = reader.Close() + if err != nil { + return err + } + + err = sanitize.Sanitize(&config) + if err != nil { + return err + } + + var writer io.WriteCloser + if context.IsSet("output") { + writer, err = os.OpenFile(context.String("output"), os.O_WRONLY | os.O_TRUNC, 0) + if err != nil { + return err + } + defer writer.Close() + } else { + writer = os.Stdout + } + + encoder := json.NewEncoder(writer) + return encoder.Encode(&config) + }, +} diff --git a/sanitize/sanitize.go b/sanitize/sanitize.go new file mode 100644 index 000000000..4814fc026 --- /dev/null +++ b/sanitize/sanitize.go @@ -0,0 +1,76 @@ +// Package sanitize removes dangerous and questionably-portable properties from container configurations. +package sanitize + +import ( + rspec "github.com/opencontainers/runtime-spec/specs-go" +) + +// Santize removes dangerous and questionably-portable properties from container configurations. +func Sanitize(config *rspec.Spec) (err error) { + config.Process.Terminal = false + //config.Process.ConsoleSize = nil // needs runtime-spec#581 + config.Process.User.AdditionalGids = []uint32{} + config.Process.Capabilities = []string{} + config.Process.Rlimits = []rspec.Rlimit{} + config.Process.NoNewPrivileges = false + config.Process.ApparmorProfile = "" + config.Process.SelinuxLabel = "" + config.Root = rspec.Root{ + Path: "rootfs", + } + config.Hostname = "" + //config.Hooks = nil // needs runtime-spec#427 + + for i, _ := range config.Mounts { + config.Mounts[i].Source = "" + } + + if config.Linux != nil { + config.Linux.UIDMappings = []rspec.IDMapping{} + config.Linux.GIDMappings = []rspec.IDMapping{} + config.Linux.Sysctl = map[string]string{} + config.Linux.CgroupsPath = nil + config.Linux.Namespaces = []rspec.Namespace{} + config.Linux.Devices = []rspec.Device{} + config.Linux.Seccomp = nil + config.Linux.RootfsPropagation = "" + config.Linux.MaskedPaths = []string{} + config.Linux.MaskedPaths = []string{} + config.Linux.MountLabel = "" + + if config.Linux.Resources != nil { + config.Linux.Resources.Devices = []rspec.DeviceCgroup{} + config.Linux.Resources.DisableOOMKiller = nil + config.Linux.Resources.OOMScoreAdj= nil + config.Linux.Resources.Pids = nil + config.Linux.Resources.BlockIO = nil + config.Linux.Resources.HugepageLimits = []rspec.HugepageLimit{} + config.Linux.Resources.Network = nil + + if config.Linux.Resources.Memory != nil { + config.Linux.Resources.Memory.Kernel = nil + config.Linux.Resources.Memory.KernelTCP = nil + config.Linux.Resources.Memory.Swappiness = nil + } + + if config.Linux.Resources.CPU != nil { + config.Linux.Resources.CPU.Quota = nil + config.Linux.Resources.CPU.Period = nil + config.Linux.Resources.CPU.RealtimeRuntime = nil + config.Linux.Resources.CPU.Period = nil + config.Linux.Resources.CPU.Cpus = nil + config.Linux.Resources.CPU.Mems = nil + } + } + } + + if config.Solaris != nil { + config.Solaris.Milestone = "" + config.Solaris.LimitPriv = "" + config.Solaris.MaxShmMemory = "" + config.Solaris.Anet = []rspec.Anet{} + config.Solaris.CappedCPU = nil + } + + return nil +}