diff --git a/cmd/ocitools/generate.go b/cmd/ocitools/generate.go index fca5d6fcb..59ab70cb3 100644 --- a/cmd/ocitools/generate.go +++ b/cmd/ocitools/generate.go @@ -3,6 +3,7 @@ package main import ( "os" "runtime" + "strings" "github.com/opencontainers/ocitools/generate" rspec "github.com/opencontainers/runtime-spec/specs-go" @@ -48,11 +49,14 @@ var generateFlags = []cli.Flag{ cli.StringSliceFlag{Name: "gidmappings", Usage: "add GIDMappings e.g HostID:ContainerID:Size"}, cli.StringSliceFlag{Name: "sysctl", Usage: "add sysctl settings e.g net.ipv4.forward=1"}, cli.StringFlag{Name: "apparmor", Usage: "specifies the the apparmor profile for the container"}, - cli.StringFlag{Name: "seccomp-default", Usage: "specifies the the defaultaction of Seccomp syscall restrictions"}, - cli.StringSliceFlag{Name: "seccomp-arch", Usage: "specifies Additional architectures permitted to be used for system calls"}, - cli.StringSliceFlag{Name: "seccomp-syscalls", Usage: "specifies Additional architectures permitted to be used for system calls, e.g Name:Action:Arg1_index/Arg1_value/Arg1_valuetwo/Arg1_op, Arg2_index/Arg2_value/Arg2_valuetwo/Arg2_op "}, - cli.StringSliceFlag{Name: "seccomp-allow", Usage: "specifies syscalls to be added to allowed"}, - cli.StringSliceFlag{Name: "seccomp-errno", Usage: "specifies syscalls to be added to list that returns an error"}, + cli.BoolFlag{Name: "seccomp-only", Usage: "specifies to export just a seccomp configuration file"}, + cli.StringFlag{Name: "seccomp-arch", Usage: "specifies additional architectures permitted to be used for system calls"}, + cli.StringFlag{Name: "seccomp-default", Usage: "specifies default action to be used for system calls"}, + cli.StringFlag{Name: "seccomp-allow", Usage: "specifies syscalls to respond with allow"}, + cli.StringFlag{Name: "seccomp-trap", Usage: "specifies syscalls to respond with trap"}, + cli.StringFlag{Name: "seccomp-errno", Usage: "specifies syscalls to respond with errno"}, + cli.StringFlag{Name: "seccomp-trace", Usage: "specifies syscalls to respond with trace"}, + cli.StringFlag{Name: "seccomp-kill", Usage: "specifies syscalls to respond with kill"}, cli.StringFlag{Name: "template", Usage: "base template to use for creating the configuration"}, cli.StringSliceFlag{Name: "label", Usage: "add annotations to the configuration e.g. key=value"}, } @@ -82,13 +86,15 @@ var generateCommand = cli.Command{ if err != nil { return err } + var exportOpts generate.ExportOptions + exportOpts.Seccomp = context.Bool("seccomp-only") if context.IsSet("output") { - output := context.String("output") - err = specgen.SaveToFile(output) + err = specgen.SaveToFile(context.String("output"), exportOpts) } else { - err = specgen.Save(os.Stdout) + err = specgen.Save(os.Stdout, exportOpts) } + if err != nil { return err } @@ -301,65 +307,85 @@ func setupSpec(g generate.Generator, context *cli.Context) error { } } - var sd string - var sa, ss []string + g, err := addSeccomp(g, context) + if err != nil { + return err + } + + return nil +} + +func addSeccomp(g generate.Generator, context *cli.Context) (generate.Generator, error) { + seccompDefault := context.String("seccomp-default") + seccompArch := context.String("seccomp-arch") + seccompKill := context.String("seccomp-kill") + seccompTrace := context.String("seccomp-trace") + seccompErrno := context.String("seccomp-errno") + seccompTrap := context.String("seccomp-trap") + seccompAllow := context.String("seccomp-allow") - if context.IsSet("seccomp-default") { - sd = context.String("seccomp-default") + // Set the DefaultAction of seccomp + if seccompDefault == "" { + seccompDefault = "errno" } - if context.IsSet("seccomp-arch") { - sa = context.StringSlice("seccomp-arch") + err := g.SetDefaultSeccompAction(seccompDefault) + if err != nil { + return g, err } - if context.IsSet("seccomp-syscalls") { - ss = context.StringSlice("seccomp-syscalls") + // Add the additional architectures permitted to be used for system calls + if seccompArch == "" { + seccompArch = "amd64,x86,x32" // Default Architectures } - if sd == "" && len(sa) == 0 && len(ss) == 0 { - return nil + architectureArgs := strings.Split(seccompArch, ",") + err = g.SetSeccompArchitectures(architectureArgs) + if err != nil { + return g, err } - // Set the DefaultAction of seccomp - if context.IsSet("seccomp-default") { - if err := g.SetLinuxSeccompDefault(sd); err != nil { - return err + if seccompKill != "" { + killArgs := strings.Split(seccompKill, ",") + err = g.SetSyscallActions("kill", killArgs) + if err != nil { + return g, err } } - // Add the additional architectures permitted to be used for system calls - if context.IsSet("seccomp-arch") { - for _, arch := range sa { - if err := g.AddLinuxSeccompArch(arch); err != nil { - return err - } + if seccompTrace != "" { + traceArgs := strings.Split(seccompTrace, ",") + err = g.SetSyscallActions("trace", traceArgs) + if err != nil { + return g, err } } - // Set syscall restrict in Seccomp - if context.IsSet("seccomp-syscalls") { - for _, syscall := range ss { - if err := g.AddLinuxSeccompSyscall(syscall); err != nil { - return err - } + if seccompErrno != "" { + errnoArgs := strings.Split(seccompErrno, ",") + err = g.SetSyscallActions("errno", errnoArgs) + if err != nil { + return g, err } } - if context.IsSet("seccomp-allow") { - seccompAllows := context.StringSlice("seccomp-allow") - for _, s := range seccompAllows { - g.AddLinuxSeccompSyscallAllow(s) + if seccompTrap != "" { + trapArgs := strings.Split(seccompTrap, ",") + err = g.SetSyscallActions("trap", trapArgs) + if err != nil { + return g, err } } - if context.IsSet("seccomp-errno") { - seccompErrnos := context.StringSlice("seccomp-errno") - for _, s := range seccompErrnos { - g.AddLinuxSeccompSyscallErrno(s) + if seccompAllow != "" { + allowArgs := strings.Split(seccompAllow, ",") + err = g.SetSyscallActions("allow", allowArgs) + if err != nil { + return g, err } } - return nil + return g, nil } func checkNs(nsMaps map[string]string, nsName string) bool { diff --git a/generate/generate.go b/generate/generate.go index fd5d9c6f9..56282f695 100644 --- a/generate/generate.go +++ b/generate/generate.go @@ -10,6 +10,7 @@ import ( "strconv" "strings" + "github.com/opencontainers/ocitools/generate/seccomp" rspec "github.com/opencontainers/runtime-spec/specs-go" "github.com/syndtr/gocapability/capability" ) @@ -24,6 +25,11 @@ type Generator struct { spec *rspec.Spec } +// ExportOptions has toggles for exporting only certain parts of the specification +type ExportOptions struct { + Seccomp bool // seccomp toggles if only seccomp should be exported +} + // New creates a spec Generator with the default spec. func New() Generator { spec := rspec.Spec{ @@ -136,6 +142,7 @@ func New() Generator { Type: "mount", }, }, + Seccomp: &rspec.Seccomp{}, Devices: []rspec.Device{}, }, } @@ -180,8 +187,14 @@ func (g *Generator) GetSpec() *rspec.Spec { } // Save writes the spec into w. -func (g *Generator) Save(w io.Writer) error { - data, err := json.MarshalIndent(g.spec, "", "\t") +func (g Generator) Save(w io.Writer, exportOpts ExportOptions) (err error) { + var data []byte + + if exportOpts.Seccomp { + data, err = json.MarshalIndent(g.spec.Linux.Seccomp, "", "\t") + } else { + data, err = json.MarshalIndent(g.spec, "", "\t") + } if err != nil { return err } @@ -195,13 +208,13 @@ func (g *Generator) Save(w io.Writer) error { } // SaveToFile writes the spec into a file. -func (g *Generator) SaveToFile(path string) error { +func (g Generator) SaveToFile(path string, exportOpts ExportOptions) error { f, err := os.Create(path) if err != nil { return err } defer f.Close() - return g.Save(f) + return g.Save(f, exportOpts) } // SetVersion sets g.spec.Version. @@ -467,278 +480,6 @@ func (g *Generator) RemoveLinuxSysctl(key string) { delete(g.spec.Linux.Sysctl, key) } -// SetLinuxSeccompDefault sets g.spec.Linux.Seccomp.DefaultAction. -func (g *Generator) SetLinuxSeccompDefault(sdefault string) error { - switch sdefault { - case "": - case "SCMP_ACT_KILL": - case "SCMP_ACT_TRAP": - case "SCMP_ACT_ERRNO": - case "SCMP_ACT_TRACE": - case "SCMP_ACT_ALLOW": - default: - return fmt.Errorf("seccomp-default must be empty or one of " + - "SCMP_ACT_KILL|SCMP_ACT_TRAP|SCMP_ACT_ERRNO|SCMP_ACT_TRACE|" + - "SCMP_ACT_ALLOW") - } - - g.initSpecLinuxSeccomp() - g.spec.Linux.Seccomp.DefaultAction = rspec.Action(sdefault) - return nil -} - -func checkSeccompArch(arch string) error { - switch arch { - case "": - case "SCMP_ARCH_X86": - case "SCMP_ARCH_X86_64": - case "SCMP_ARCH_X32": - case "SCMP_ARCH_ARM": - case "SCMP_ARCH_AARCH64": - case "SCMP_ARCH_MIPS": - case "SCMP_ARCH_MIPS64": - case "SCMP_ARCH_MIPS64N32": - case "SCMP_ARCH_MIPSEL": - case "SCMP_ARCH_MIPSEL64": - case "SCMP_ARCH_MIPSEL64N32": - default: - return fmt.Errorf("seccomp-arch must be empty or one of " + - "SCMP_ARCH_X86|SCMP_ARCH_X86_64|SCMP_ARCH_X32|SCMP_ARCH_ARM|" + - "SCMP_ARCH_AARCH64SCMP_ARCH_MIPS|SCMP_ARCH_MIPS64|" + - "SCMP_ARCH_MIPS64N32|SCMP_ARCH_MIPSEL|SCMP_ARCH_MIPSEL64|" + - "SCMP_ARCH_MIPSEL64N32") - } - return nil -} - -// ClearLinuxSeccompArch clears g.spec.Linux.Seccomp.Architectures. -func (g *Generator) ClearLinuxSeccompArch() { - if g.spec == nil || g.spec.Linux == nil || g.spec.Linux.Seccomp == nil { - return - } - - g.spec.Linux.Seccomp.Architectures = []rspec.Arch{} -} - -// AddLinuxSeccompArch adds sArch into g.spec.Linux.Seccomp.Architectures. -func (g *Generator) AddLinuxSeccompArch(sArch string) error { - if err := checkSeccompArch(sArch); err != nil { - return err - } - - g.initSpecLinuxSeccomp() - g.spec.Linux.Seccomp.Architectures = append(g.spec.Linux.Seccomp.Architectures, rspec.Arch(sArch)) - - return nil -} - -// RemoveSeccompArch removes sArch from g.spec.Linux.Seccomp.Architectures. -func (g *Generator) RemoveSeccompArch(sArch string) error { - if err := checkSeccompArch(sArch); err != nil { - return err - } - - if g.spec == nil || g.spec.Linux == nil || g.spec.Linux.Seccomp == nil { - return nil - } - - for i, arch := range g.spec.Linux.Seccomp.Architectures { - if string(arch) == sArch { - g.spec.Linux.Seccomp.Architectures = append(g.spec.Linux.Seccomp.Architectures[:i], g.spec.Linux.Seccomp.Architectures[i+1:]...) - return nil - } - } - - return nil -} - -func checkSeccompSyscallAction(syscall string) error { - switch syscall { - case "": - case "SCMP_ACT_KILL": - case "SCMP_ACT_TRAP": - case "SCMP_ACT_ERRNO": - case "SCMP_ACT_TRACE": - case "SCMP_ACT_ALLOW": - default: - return fmt.Errorf("seccomp-syscall action must be empty or " + - "one of SCMP_ACT_KILL|SCMP_ACT_TRAP|SCMP_ACT_ERRNO|" + - "SCMP_ACT_TRACE|SCMP_ACT_ALLOW") - } - return nil -} - -func checkSeccompSyscallArg(arg string) error { - switch arg { - case "": - case "SCMP_CMP_NE": - case "SCMP_CMP_LT": - case "SCMP_CMP_LE": - case "SCMP_CMP_EQ": - case "SCMP_CMP_GE": - case "SCMP_CMP_GT": - case "SCMP_CMP_MASKED_EQ": - default: - return fmt.Errorf("seccomp-syscall args must be " + - "empty or one of SCMP_CMP_NE|SCMP_CMP_LT|" + - "SCMP_CMP_LE|SCMP_CMP_EQ|SCMP_CMP_GE|" + - "SCMP_CMP_GT|SCMP_CMP_MASKED_EQ") - } - return nil -} - -func parseSeccompSyscall(s string) (rspec.Syscall, error) { - syscall := strings.Split(s, ":") - if len(syscall) != 3 { - return rspec.Syscall{}, fmt.Errorf("seccomp sysctl must consist of 3 parameters") - } - name := syscall[0] - if err := checkSeccompSyscallAction(syscall[1]); err != nil { - return rspec.Syscall{}, err - } - action := rspec.Action(syscall[1]) - - var Args []rspec.Arg - if strings.EqualFold(syscall[2], "") { - Args = nil - } else { - argsslice := strings.Split(syscall[2], ",") - for _, argsstru := range argsslice { - args := strings.Split(argsstru, "/") - if len(args) == 4 { - index, err := strconv.Atoi(args[0]) - value, err := strconv.Atoi(args[1]) - value2, err := strconv.Atoi(args[2]) - if err != nil { - return rspec.Syscall{}, err - } - if err := checkSeccompSyscallArg(args[3]); err != nil { - return rspec.Syscall{}, err - } - op := rspec.Operator(args[3]) - Arg := rspec.Arg{ - Index: uint(index), - Value: uint64(value), - ValueTwo: uint64(value2), - Op: op, - } - Args = append(Args, Arg) - } else { - return rspec.Syscall{}, fmt.Errorf("seccomp-sysctl args error: %s", argsstru) - } - } - } - - return rspec.Syscall{ - Name: name, - Action: action, - Args: Args, - }, nil -} - -// ClearLinuxSeccompSyscall clears g.spec.Linux.Seccomp.Syscalls. -func (g *Generator) ClearLinuxSeccompSyscall() { - if g.spec == nil || g.spec.Linux == nil || g.spec.Linux.Seccomp == nil { - return - } - - g.spec.Linux.Seccomp.Syscalls = []rspec.Syscall{} -} - -// AddLinuxSeccompSyscall adds sSyscall into g.spec.Linux.Seccomp.Syscalls. -func (g *Generator) AddLinuxSeccompSyscall(sSyscall string) error { - f, err := parseSeccompSyscall(sSyscall) - if err != nil { - return err - } - - g.initSpecLinuxSeccomp() - g.spec.Linux.Seccomp.Syscalls = append(g.spec.Linux.Seccomp.Syscalls, f) - return nil -} - -// AddLinuxSeccompSyscallAllow adds seccompAllow into g.spec.Linux.Seccomp.Syscalls. -func (g *Generator) AddLinuxSeccompSyscallAllow(seccompAllow string) { - syscall := rspec.Syscall{ - Name: seccompAllow, - Action: "SCMP_ACT_ALLOW", - } - - g.initSpecLinuxSeccomp() - g.spec.Linux.Seccomp.Syscalls = append(g.spec.Linux.Seccomp.Syscalls, syscall) -} - -// AddLinuxSeccompSyscallErrno adds seccompErrno into g.spec.Linux.Seccomp.Syscalls. -func (g *Generator) AddLinuxSeccompSyscallErrno(seccompErrno string) { - syscall := rspec.Syscall{ - Name: seccompErrno, - Action: "SCMP_ACT_ERRNO", - } - - g.initSpecLinuxSeccomp() - g.spec.Linux.Seccomp.Syscalls = append(g.spec.Linux.Seccomp.Syscalls, syscall) -} - -// RemoveSeccompSyscallByName removes all the seccomp syscalls with the given -// name from g.spec.Linux.Seccomp.Syscalls. -func (g *Generator) RemoveSeccompSyscallByName(name string) error { - if g.spec == nil || g.spec.Linux == nil || g.spec.Linux.Seccomp == nil { - return nil - } - - var r []rspec.Syscall - for _, syscall := range g.spec.Linux.Seccomp.Syscalls { - if strings.Compare(name, syscall.Name) != 0 { - r = append(r, syscall) - } - } - g.spec.Linux.Seccomp.Syscalls = r - return nil -} - -// RemoveSeccompSyscallByAction removes all the seccomp syscalls with the given -// action from g.spec.Linux.Seccomp.Syscalls. -func (g *Generator) RemoveSeccompSyscallByAction(action string) error { - if g.spec == nil || g.spec.Linux == nil || g.spec.Linux.Seccomp == nil { - return nil - } - - if err := checkSeccompSyscallAction(action); err != nil { - return err - } - - var r []rspec.Syscall - for _, syscall := range g.spec.Linux.Seccomp.Syscalls { - if strings.Compare(action, string(syscall.Action)) != 0 { - r = append(r, syscall) - } - } - g.spec.Linux.Seccomp.Syscalls = r - return nil -} - -// RemoveSeccompSyscall removes all the seccomp syscalls with the given -// name and action from g.spec.Linux.Seccomp.Syscalls. -func (g *Generator) RemoveSeccompSyscall(name string, action string) error { - if g.spec == nil || g.spec.Linux == nil || g.spec.Linux.Seccomp == nil { - return nil - } - - if err := checkSeccompSyscallAction(action); err != nil { - return err - } - - var r []rspec.Syscall - for _, syscall := range g.spec.Linux.Seccomp.Syscalls { - if !(strings.Compare(name, syscall.Name) == 0 && - strings.Compare(action, string(syscall.Action)) == 0) { - r = append(r, syscall) - } - } - g.spec.Linux.Seccomp.Syscalls = r - return nil -} - func parseIDMapping(idms string) (rspec.IDMapping, error) { idm := strings.Split(idms, ":") if len(idm) != 3 { @@ -1096,42 +837,22 @@ func (g *Generator) RemoveLinuxNamespace(ns string) error { // strPtr returns the pointer pointing to the string s. func strPtr(s string) *string { return &s } -// FIXME: this function is not used. -func parseArgs(args2parse string) ([]*rspec.Arg, error) { - var Args []*rspec.Arg - argstrslice := strings.Split(args2parse, ",") - for _, argstr := range argstrslice { - args := strings.Split(argstr, "/") - if len(args) == 4 { - index, err := strconv.Atoi(args[0]) - value, err := strconv.Atoi(args[1]) - value2, err := strconv.Atoi(args[2]) - if err != nil { - return nil, err - } - switch args[3] { - case "": - case "SCMP_CMP_NE": - case "SCMP_CMP_LT": - case "SCMP_CMP_LE": - case "SCMP_CMP_EQ": - case "SCMP_CMP_GE": - case "SCMP_CMP_GT": - case "SCMP_CMP_MASKED_EQ": - default: - return nil, fmt.Errorf("seccomp-sysctl args must be empty or one of SCMP_CMP_NE|SCMP_CMP_LT|SCMP_CMP_LE|SCMP_CMP_EQ|SCMP_CMP_GE|SCMP_CMP_GT|SCMP_CMP_MASKED_EQ") - } - op := rspec.Operator(args[3]) - Arg := rspec.Arg{ - Index: uint(index), - Value: uint64(value), - ValueTwo: uint64(value2), - Op: op, - } - Args = append(Args, &Arg) - } else { - return nil, fmt.Errorf("seccomp-sysctl args error: %s", argstr) - } - } - return Args, nil +// SetSyscallActions adds rules for syscalls with the specified action +func (g Generator) SetSyscallActions(action string, arguments []string) error { + return seccomp.ParseSyscallFlag(action, arguments, g.spec.Linux.Seccomp) +} + +// SetDefaultSeccompAction sets the default action for all syscalls not defined +func (g Generator) SetDefaultSeccompAction(action string) error { + return seccomp.ParseDefaultAction(action, g.spec.Linux.Seccomp) +} + +// SetSeccompArchitectures sets the supported seccomp architectures +func (g Generator) SetSeccompArchitectures(architectures []string) error { + return seccomp.ParseArchitectureFlag(architectures, g.spec.Linux.Seccomp) +} + +// RemoveSeccompRule removes rules for any specified syscalls +func (g Generator) RemoveSeccompRule(arguments string) error { + return seccomp.RemoveAction(arguments, g.spec.Linux.Seccomp) } diff --git a/generate/seccomp/consts.go b/generate/seccomp/consts.go new file mode 100644 index 000000000..f28d8f587 --- /dev/null +++ b/generate/seccomp/consts.go @@ -0,0 +1,7 @@ +package seccomp + +const ( + seccompOverwrite = "overwrite" + seccompAppend = "append" + nothing = "nothing" +) diff --git a/generate/seccomp/doc.go b/generate/seccomp/doc.go deleted file mode 100644 index 481bd7004..000000000 --- a/generate/seccomp/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package seccomp includes functions for configuring seccomp. -package seccomp diff --git a/generate/seccomp/parse_action.go b/generate/seccomp/parse_action.go new file mode 100644 index 000000000..d284aa68a --- /dev/null +++ b/generate/seccomp/parse_action.go @@ -0,0 +1,104 @@ +package seccomp + +import ( + "errors" + "fmt" + "strconv" + "strings" + + "github.com/Sirupsen/logrus" + types "github.com/opencontainers/runtime-spec/specs-go" +) + +// ParseSyscallFlag takes the name of the action, the arguments (syscalls) that were +// passed with it at the command line and a pointer to the config struct. It parses +// the action and syscalls and updates the config accordingly +func ParseSyscallFlag(action string, syscallArgs []string, config *types.Seccomp) error { + + if syscallArgs == nil { + return errors.New("Error: Nil syscall slice") + } + + correctedAction, err := parseAction(action) + if err != nil { + return err + } + + if correctedAction == config.DefaultAction { + logrus.Info("Action is already set as default") + return nil + } + + for _, syscallArg := range syscallArgs { + delimArgs := strings.Split(syscallArg, ":") + argSlice, err := parseArguments(delimArgs) + if err != nil { + return err + } + + newSyscall := newSyscallStruct(delimArgs[0], correctedAction, *argSlice) + descison, err := decideCourseOfAction(&newSyscall, config.Syscalls) + if err != nil { + fmt.Println(err) + return err + } + delimDescison := strings.Split(descison, ":") + + if delimDescison[0] == nothing { + logrus.Info("No action taken: ", newSyscall) + } + + if delimDescison[0] == seccompAppend { + config.Syscalls = append(config.Syscalls, newSyscall) + } + + if delimDescison[0] == seccompOverwrite { + indexForOverwrite, err := strconv.ParseInt(delimDescison[1], 10, 32) + if err != nil { + return err + } + config.Syscalls[indexForOverwrite] = newSyscall + } + } + return nil +} + +var actions = map[string]types.Action{ + "allow": types.ActAllow, + "errno": types.ActErrno, + "kill": types.ActKill, + "trace": types.ActTrace, + "trap": types.ActTrap, +} + +// Take passed action, return the SCMP_ACT_ version of it +func parseAction(action string) (types.Action, error) { + a, ok := actions[action] + if !ok { + return "", fmt.Errorf("Unrecognized action: %s", action) + } + return a, nil +} + +//ParseDefaultAction simply sets the default action of the seccomp configuration +func ParseDefaultAction(action string, config *types.Seccomp) error { + if action == "" { + return nil + } + + defaultAction, err := parseAction(action) + if err != nil { + return err + } + config.DefaultAction = defaultAction + return nil +} + +func newSyscallStruct(name string, action types.Action, args []types.Arg) types.Syscall { + syscallStruct := types.Syscall{ + Name: name, + Action: action, + Args: args, + } + return syscallStruct +} diff --git a/generate/seccomp/parse_architecture.go b/generate/seccomp/parse_architecture.go new file mode 100644 index 000000000..15fb1cb7e --- /dev/null +++ b/generate/seccomp/parse_architecture.go @@ -0,0 +1,57 @@ +package seccomp + +import ( + "fmt" + + types "github.com/opencontainers/runtime-spec/specs-go" +) + +// ParseArchitectureFlag takes the raw string passed with the --arch flag, parses it +// and updates the Seccomp config accordingly +func ParseArchitectureFlag(architectureArgs []string, config *types.Seccomp) error { + + var arches []types.Arch + for _, arg := range architectureArgs { + correctedArch, err := parseArch(arg) + if err != nil { + return err + } + shouldAppend := true + for _, alreadySpecified := range config.Architectures { + if correctedArch == alreadySpecified { + shouldAppend = false + } + } + if shouldAppend { + arches = append(arches, correctedArch) + config.Architectures = arches + } + } + return nil +} + +func parseArch(arch string) (types.Arch, error) { + arches := map[string]types.Arch{ + "x86": types.ArchX86, + "amd64": types.ArchX86_64, + "x32": types.ArchX32, + "arm": types.ArchARM, + "arm64": types.ArchAARCH64, + "mips": types.ArchMIPS, + "mips64": types.ArchMIPS64, + "mips64n32": types.ArchMIPS64N32, + "mipsel": types.ArchMIPSEL, + "mipsel64": types.ArchMIPSEL64, + "mipsel64n32": types.ArchMIPSEL64N32, + "ppc": types.ArchPPC, + "ppc64": types.ArchPPC64, + "ppc64le": types.ArchPPC64LE, + "s390": types.ArchS390, + "s390x": types.ArchS390X, + } + a, ok := arches[arch] + if !ok { + return "", fmt.Errorf("Unrecognized architecutre: %s", arch) + } + return a, nil +} diff --git a/generate/seccomp/parse_arguments.go b/generate/seccomp/parse_arguments.go new file mode 100644 index 000000000..0960bcbc9 --- /dev/null +++ b/generate/seccomp/parse_arguments.go @@ -0,0 +1,71 @@ +package seccomp + +import ( + "errors" + "fmt" + "strconv" + + types "github.com/opencontainers/runtime-spec/specs-go" +) + +// parseArguments takes a list of arguments (delimArgs). It parses and fills out +// the argument information and returns a slice of arg structs +func parseArguments(delimArgs []string) (*[]types.Arg, error) { + nilArgSlice := new([]types.Arg) + + if len(delimArgs) == 1 { + return nilArgSlice, nil + } + + if len(delimArgs) == 5 { + syscallIndex, err := strconv.ParseUint(delimArgs[1], 10, 0) + if err != nil { + return nilArgSlice, err + } + + syscallValue, err := strconv.ParseUint(delimArgs[2], 10, 64) + if err != nil { + return nilArgSlice, err + } + + syscallValueTwo, err := strconv.ParseUint(delimArgs[3], 10, 64) + if err != nil { + return nilArgSlice, err + } + + syscallOp, err := parseOperator(delimArgs[4]) + if err != nil { + return nilArgSlice, err + } + + argStruct := types.Arg{ + Index: uint(syscallIndex), + Value: syscallValue, + ValueTwo: syscallValueTwo, + Op: syscallOp, + } + + argSlice := new([]types.Arg) + *argSlice = append(*argSlice, argStruct) + return argSlice, nil + } + + return nilArgSlice, errors.New("Incorrect number of arguments passed with syscall") +} + +func parseOperator(operator string) (types.Operator, error) { + operators := map[string]types.Operator{ + "NE": types.OpNotEqual, + "LT": types.OpLessThan, + "LE": types.OpLessEqual, + "EQ": types.OpEqualTo, + "GE": types.OpGreaterEqual, + "GT": types.OpGreaterThan, + "ME": types.OpMaskedEqual, + } + o, ok := operators[operator] + if !ok { + return "", fmt.Errorf("Unrecognized operator: %s", operator) + } + return o, nil +} diff --git a/generate/seccomp/parse_remove.go b/generate/seccomp/parse_remove.go new file mode 100644 index 000000000..331528994 --- /dev/null +++ b/generate/seccomp/parse_remove.go @@ -0,0 +1,33 @@ +package seccomp + +import ( + "fmt" + "strings" + + types "github.com/opencontainers/runtime-spec/specs-go" +) + +// RemoveAction takes the argument string that was passed with the --remove flag, +// parses it, and updates the Seccomp config accordingly +func RemoveAction(arguments string, config *types.Seccomp) error { + + if config == nil { + return fmt.Errorf("Cannot remove action from nil Seccomp pointer") + } + + var syscallsToRemove []string + if strings.Contains(arguments, ",") { + syscallsToRemove = strings.Split(arguments, ",") + } else { + syscallsToRemove = append(syscallsToRemove, arguments) + } + + for _, syscall := range syscallsToRemove { + for counter, syscallStruct := range config.Syscalls { + if syscallStruct.Name == syscall { + config.Syscalls = append(config.Syscalls[:counter], config.Syscalls[counter+1:]...) + } + } + } + return nil +} diff --git a/generate/seccomp/syscall_compare.go b/generate/seccomp/syscall_compare.go new file mode 100644 index 000000000..cd20fb815 --- /dev/null +++ b/generate/seccomp/syscall_compare.go @@ -0,0 +1,143 @@ +/******************************************************************************* +This file contains functions for determining the action to be taken for adding + syscalls rules to the seccomp configuration. +*******************************************************************************/ + +package seccomp + +import ( + "fmt" + "reflect" + "strconv" + "strings" + + types "github.com/opencontainers/runtime-spec/specs-go" +) + +func decideCourseOfAction(newSyscall *types.Syscall, syscalls []types.Syscall) (string, error) { + ruleForSyscallAlreadyExists := false + + var sliceOfDeterminedActions []string + for i, syscall := range syscalls { + if syscall.Name == newSyscall.Name { + ruleForSyscallAlreadyExists = true + + if identical(newSyscall, &syscall) { + sliceOfDeterminedActions = append(sliceOfDeterminedActions, nothing) + } + + if sameAction(newSyscall, &syscall) { + if bothHaveArgs(newSyscall, &syscall) { + sliceOfDeterminedActions = append(sliceOfDeterminedActions, seccompAppend) + } + if onlyOneHasArgs(newSyscall, &syscall) { + if firstParamOnlyHasArgs(newSyscall, &syscall) { + sliceOfDeterminedActions = append(sliceOfDeterminedActions, "overwrite:"+strconv.Itoa(i)) + } else { + sliceOfDeterminedActions = append(sliceOfDeterminedActions, nothing) + } + } + } + + if !sameAction(newSyscall, &syscall) { + if bothHaveArgs(newSyscall, &syscall) { + if sameArgs(newSyscall, &syscall) { + sliceOfDeterminedActions = append(sliceOfDeterminedActions, "overwrite:"+strconv.Itoa(i)) + } + if !sameArgs(newSyscall, &syscall) { + sliceOfDeterminedActions = append(sliceOfDeterminedActions, seccompAppend) + } + } + if onlyOneHasArgs(newSyscall, &syscall) { + sliceOfDeterminedActions = append(sliceOfDeterminedActions, seccompAppend) + } + if neitherHasArgs(newSyscall, &syscall) { + sliceOfDeterminedActions = append(sliceOfDeterminedActions, "overwrite:"+strconv.Itoa(i)) + } + } + } + } + + if !ruleForSyscallAlreadyExists { + sliceOfDeterminedActions = append(sliceOfDeterminedActions, seccompAppend) + } + + // Nothing has highest priority + for _, determinedAction := range sliceOfDeterminedActions { + if determinedAction == nothing { + return determinedAction, nil + } + } + + // Overwrite has second highest priority + for _, determinedAction := range sliceOfDeterminedActions { + if strings.Contains(determinedAction, seccompOverwrite) { + return determinedAction, nil + } + } + + // Append has the lowest priority + for _, determinedAction := range sliceOfDeterminedActions { + if determinedAction == seccompAppend { + return determinedAction, nil + } + } + + return "error", fmt.Errorf("Trouble determining action: %s", sliceOfDeterminedActions) +} + +func hasArguments(config *types.Syscall) bool { + nilSyscall := new(types.Syscall) + return !sameArgs(nilSyscall, config) +} + +func identical(config1, config2 *types.Syscall) bool { + return reflect.DeepEqual(config1, config2) +} + +func identicalExceptAction(config1, config2 *types.Syscall) bool { + samename := sameName(config1, config2) + sameAction := sameAction(config1, config2) + sameArgs := sameArgs(config1, config2) + + return samename && !sameAction && sameArgs +} + +func identicalExceptArgs(config1, config2 *types.Syscall) bool { + samename := sameName(config1, config2) + sameAction := sameAction(config1, config2) + sameArgs := sameArgs(config1, config2) + + return samename && sameAction && !sameArgs +} + +func sameName(config1, config2 *types.Syscall) bool { + return config1.Name == config2.Name +} + +func sameAction(config1, config2 *types.Syscall) bool { + return config1.Action == config2.Action +} + +func sameArgs(config1, config2 *types.Syscall) bool { + return reflect.DeepEqual(config1.Args, config2.Args) +} + +func bothHaveArgs(config1, config2 *types.Syscall) bool { + return hasArguments(config1) && hasArguments(config2) +} + +func onlyOneHasArgs(config1, config2 *types.Syscall) bool { + conf1 := hasArguments(config1) + conf2 := hasArguments(config2) + + return (conf1 && !conf2) || (!conf1 && conf2) +} + +func neitherHasArgs(config1, config2 *types.Syscall) bool { + return !hasArguments(config1) && !hasArguments(config2) +} + +func firstParamOnlyHasArgs(config1, config2 *types.Syscall) bool { + return !hasArguments(config1) && hasArguments(config2) +} diff --git a/man/ocitools-generate.1.md b/man/ocitools-generate.1.md index eb79b74ee..3c8bb312b 100644 --- a/man/ocitools-generate.1.md +++ b/man/ocitools-generate.1.md @@ -29,7 +29,7 @@ read the configuration from `config.json`. --args "/usr/bin/httpd" --args "-D" --args "FOREGROUND" -**--bind**=*[[HOST-DIR:CONTAINER-DIR][:OPTIONS]]* +**--bind**=*[[HOST-DIR:CONTAINER-DIR][:OPTIONS]]* Bind mount directories src:dest:(rw,ro) If you specify, ` --bind /HOST-DIR:/CONTAINER-DIR`, runc bind mounts `/HOST-DIR` in the host to `/CONTAINER-DIR` in the OCI container. The `OPTIONS` are a comma @@ -143,7 +143,7 @@ inside of the container. Give extended privileges to this container. The default is *false*. By default, OCI containers are -“unprivileged” (=false) and cannot do some of the things a normal root process can do. +“unprivileged” (=false) and cannot do some of the things a normal root process can do. When the operator executes **ocitools generate --privileged**, OCI will enable access to all devices on the host as well as disable some of the confinement mechanisms like AppArmor, SELinux, and seccomp from blocking access to privileged processes. This gives the container processes nearly all the same access to the host as processes generating outside of a container on the host. @@ -159,26 +159,47 @@ inside of the container. **--rootfs**=ROOTFSPATH Path to the rootfs +**--seccomp-only** + Specifies that only the seccomp configuration should be exported to a file + named config.seccomp. + **--seccomp-arch**=ARCH Specifies Additional architectures permitted to be used for system calls. By default if you turn on seccomp, only the host architecture will be allowed. **--seccomp-default**=ACTION - Specifies the the default action of Seccomp syscall restrictions - Values: KILL,ERRNO,TRACE,ALLOW - -**--seccomp-syscalls**=SYSCALLS - Specifies Additional syscalls permitted to be used for system calls, - e.g Name:Action:Arg1_index/Arg1_value/Arg1_valuetwo/Arg1_op, Arg2_index/Arg2_value/Arg2_valuetwo/Arg2_op - See --seccomp-allow and --seccomp-errno for convenient way to set seccomp syscall options. + Specifies the the defaultaction of Seccomp syscall restrictions + Values: kill,errno,trace,trap,allow -**--seccomp-allow**=SYSCALL +**--seccomp-allow**=SYSCALL,SYSCALL:INDEX:ARG1:ARG2:OP,... Specifies syscalls to be added to the ALLOW list. - See --seccomp-syscalls for setting limits on arguments. + You can specify just the name of the syscall or you can specify arguments by + using a `:` seperated list. You can specify as many as you want by using ',' + e.g Syscall:index:arg1:arg2:Op,Syscall,Syscall,... **--seccomp-errno**=SYSCALL Specifies syscalls to be added to the ERRNO list. - See --seccomp-syscalls for setting limits on arguments. + You can specify just the name of the syscall or you can specify arguments by + using a `:` seperated list. You can specify as many as you want by using ',' + e.g Syscall:index:arg1:arg2:Op,Syscall,Syscall,... + +**--seccomp-trace**=SYSCALL + Specifies syscalls to be added to the TRACE list. + You can specify just the name of the syscall or you can specify arguments by + using a `:` seperated list. You can specify as many as you want by using ',' + e.g Syscall:index:arg1:arg2:Op,Syscall,Syscall,... + +**--seccomp-trap**=SYSCALL + Specifies syscalls to be added to the TRAP list. + You can specify just the name of the syscall or you can specify arguments by + using a `:` seperated list. You can specify as many as you want by using ',' + e.g Syscall:index:arg1:arg2:Op,Syscall,Syscall,... + +**--seccomp-kill**=SYSCALL + Specifies syscalls to be added to the KILL list. + You can specify just the name of the syscall or you can specify arguments by + using a `:` seperated list. You can specify as many as you want by using ',' + e.g Syscall:index:arg1:arg2:Op,Syscall,Syscall,... **--selinux-label**=PROCESSLABEL SELinux Label