Skip to content
This repository has been archived by the owner on Aug 14, 2020. It is now read-only.

[WIP] ace: initial seccomp proposal #620

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 78 additions & 4 deletions actool/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ var (
patchMounts string
patchPorts string
patchIsolators string
patchSeccompMode string
patchSeccompSet string

catPrettyPrint bool

Expand All @@ -69,6 +71,8 @@ var (
[--ports=query,protocol=tcp,port=8080[:query2,...]]
[--supplementary-groups=gid1,gid2,...]
[--isolators=resource/cpu,request=50m,limit=100m[:resource/memory,...]]
[--seccomp-mode=remove|retain[,errno=EPERM]]
[--seccomp-set=syscall1,syscall2,...]]
[--replace]
INPUT_ACI_FILE
[OUTPUT_ACI_FILE]`,
Expand Down Expand Up @@ -99,6 +103,8 @@ func init() {
cmdPatchManifest.Flags.StringVar(&patchMounts, "mounts", "", "Replace mount points")
cmdPatchManifest.Flags.StringVar(&patchPorts, "ports", "", "Replace ports")
cmdPatchManifest.Flags.StringVar(&patchIsolators, "isolators", "", "Replace isolators")
cmdPatchManifest.Flags.StringVar(&patchSeccompMode, "seccomp-mode", "", "Enable and configure seccomp isolator")
cmdPatchManifest.Flags.StringVar(&patchSeccompSet, "seccomp-set", "", "Set of syscalls for seccomp isolator enforcing")

cmdCatManifest.Flags.BoolVar(&catPrettyPrint, "pretty-print", false, "Print with better style")
}
Expand Down Expand Up @@ -211,7 +217,11 @@ func patchManifest(im *schema.ImageManifest) error {
if err != nil {
return fmt.Errorf("cannot parse capability %q: %v", patchCaps, err)
}
app.Isolators = append(app.Isolators, caps.AsIsolator())
isolator, err = caps.AsIsolator()
if err == nil {
return err
}
app.Isolators = append(app.Isolators, *isolator)
}
if patchRevokeCaps != "" {
isolator := app.Isolators.GetByName(types.LinuxCapabilitiesRevokeSetName)
Expand All @@ -225,7 +235,11 @@ func patchManifest(im *schema.ImageManifest) error {
if err != nil {
return fmt.Errorf("cannot parse capability %q: %v", patchRevokeCaps, err)
}
app.Isolators = append(app.Isolators, caps.AsIsolator())
isolator, err = caps.AsIsolator()
if err == nil {
return err
}
app.Isolators = append(app.Isolators, *isolator)
}

if patchMounts != "" {
Expand All @@ -250,6 +264,23 @@ func patchManifest(im *schema.ImageManifest) error {
}
}

if patchSeccompMode != "" {
retainIsolator := app.Isolators.GetByName(types.LinuxSeccompRetainSetName)
removeIsolator := app.Isolators.GetByName(types.LinuxSeccompRemoveSetName)
if removeIsolator != nil || retainIsolator != nil {
return fmt.Errorf("a seccomp isolator already exists")
}
seccomp, err := parseSeccompArgs(patchSeccompMode, patchSeccompSet)
if err != nil {
return err
}
seccompIsolator, err := seccomp.AsIsolator()
if err != nil {
return err
}
app.Isolators = append(app.Isolators, *seccompIsolator)
}

if patchIsolators != "" {
isolators := strings.Split(patchIsolators, ":")
for _, is := range isolators {
Expand All @@ -260,14 +291,18 @@ func patchManifest(im *schema.ImageManifest) error {

_, ok := types.ResourceIsolatorNames[name]

if name == types.LinuxNoNewPrivilegesName {
switch name {
case types.LinuxNoNewPrivilegesName:
ok = true
kv := strings.Split(is, ",")
if len(kv) != 2 {
return fmt.Errorf("isolator %s: invalid format", name)
}

isolatorStr = fmt.Sprintf(`{ "name": "%s", "value": %s }`, name, kv[1])
case types.LinuxSeccompRemoveSetName:
fallthrough
case types.LinuxSeccompRetainSetName:
ok = false
}

if !ok {
Expand All @@ -284,6 +319,45 @@ func patchManifest(im *schema.ImageManifest) error {
return nil
}

// parseSeccompArgs parses seccomp mode and set CLI flags, preparing an
// appropriate seccomp isolator.
func parseSeccompArgs(patchSeccompMode string, patchSeccompSet string) (types.AsIsolator, error) {
// Parse mode flag and additional keyed arguments.
var errno, mode string
args := strings.Split(patchSeccompMode, ",")
for _, a := range args {
kv := strings.Split(a, "=")
switch len(kv) {
case 1:
// mode, either "remove" or "retain"
mode = kv[0]
case 2:
// k=v argument, only "errno" allowed for now
if kv[0] == "errno" {
errno = kv[1]
}
default:
return nil, fmt.Errorf("cannot parse seccomp-mode argument: %s", a)
}
}

// Instantiate an Isolator with the content specified by the --seccomp-set parameter.
var err error
var seccomp types.AsIsolator
switch mode {
case "remove":
seccomp, err = types.NewLinuxSeccompRemoveSet(errno, strings.Split(patchSeccompSet, ",")...)
case "retain":
seccomp, err = types.NewLinuxSeccompRetainSet(errno, strings.Split(patchSeccompSet, ",")...)
default:
err = fmt.Errorf("Unknown seccomp mode %s", mode)
}
if err != nil {
return nil, fmt.Errorf("cannot parse seccomp isolator: %s", err)
}
return seccomp, nil
}

// extractManifest iterates over the tar reader and locate the manifest. Once
// located, the manifest can be printed, replaced or patched.
func extractManifest(tr *tar.Reader, tw *tar.Writer, printManifest bool, newManifest []byte) error {
Expand Down
4 changes: 4 additions & 0 deletions examples/image.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@
{
"name": "os/linux/no-new-privileges",
"value": true
},
{
"name": "os/linux/seccomp-remove-set",
"value": {"errno": "EACCESS", "set": ["clock_settime", "clock_adjtime", "reboot"]}
}
],
"mountPoints": [
Expand Down
141 changes: 134 additions & 7 deletions schema/types/isolator_linux_specific.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@ package types
import (
"encoding/json"
"errors"
"unicode"
)

const (
LinuxCapabilitiesRetainSetName = "os/linux/capabilities-retain-set"
LinuxCapabilitiesRevokeSetName = "os/linux/capabilities-remove-set"
LinuxNoNewPrivilegesName = "os/linux/no-new-privileges"
LinuxSeccompRemoveSetName = "os/linux/seccomp-remove-set"
LinuxSeccompRetainSetName = "os/linux/seccomp-retain-set"
)

var LinuxIsolatorNames = make(map[ACIdentifier]struct{})
Expand All @@ -32,6 +35,8 @@ func init() {
LinuxCapabilitiesRevokeSetName: func() IsolatorValue { return &LinuxCapabilitiesRevokeSet{} },
LinuxCapabilitiesRetainSetName: func() IsolatorValue { return &LinuxCapabilitiesRetainSet{} },
LinuxNoNewPrivilegesName: func() IsolatorValue { v := LinuxNoNewPrivileges(false); return &v },
LinuxSeccompRemoveSetName: func() IsolatorValue { return &LinuxSeccompRemoveSet{} },
LinuxSeccompRetainSetName: func() IsolatorValue { return &LinuxSeccompRetainSet{} },
} {
AddIsolatorName(name, LinuxIsolatorNames)
AddIsolatorValueConstructor(name, con)
Expand All @@ -56,6 +61,10 @@ func (l *LinuxNoNewPrivileges) UnmarshalJSON(b []byte) error {
return nil
}

type AsIsolator interface {
AsIsolator() (*Isolator, error)
}

type LinuxCapabilitiesSet interface {
Set() []LinuxCapability
AssertValid() error
Expand Down Expand Up @@ -115,17 +124,17 @@ func NewLinuxCapabilitiesRetainSet(caps ...string) (*LinuxCapabilitiesRetainSet,
return &l, nil
}

func (l LinuxCapabilitiesRetainSet) AsIsolator() Isolator {
func (l LinuxCapabilitiesRetainSet) AsIsolator() (*Isolator, error) {
b, err := json.Marshal(l.linuxCapabilitiesSetBase.val)
if err != nil {
panic(err)
return nil, err
}
rm := json.RawMessage(b)
return Isolator{
return &Isolator{
Name: LinuxCapabilitiesRetainSetName,
ValueRaw: &rm,
value: &l,
}
}, nil
}

type LinuxCapabilitiesRevokeSet struct {
Expand All @@ -149,15 +158,133 @@ func NewLinuxCapabilitiesRevokeSet(caps ...string) (*LinuxCapabilitiesRevokeSet,
return &l, nil
}

func (l LinuxCapabilitiesRevokeSet) AsIsolator() Isolator {
func (l LinuxCapabilitiesRevokeSet) AsIsolator() (*Isolator, error) {
b, err := json.Marshal(l.linuxCapabilitiesSetBase.val)
if err != nil {
panic(err)
return nil, err
}
rm := json.RawMessage(b)
return Isolator{
return &Isolator{
Name: LinuxCapabilitiesRevokeSetName,
ValueRaw: &rm,
value: &l,
}, nil
}

type LinuxSeccompSet interface {
Set() []LinuxSeccompEntry
Errno() LinuxSeccompErrno
AssertValid() error
}

type LinuxSeccompEntry string
type LinuxSeccompErrno string

type linuxSeccompValue struct {
Set []LinuxSeccompEntry `json:"set"`
Errno LinuxSeccompErrno `json:"errno"`
}

type linuxSeccompBase struct {
val linuxSeccompValue
}

func (l linuxSeccompBase) AssertValid() error {
if l.val.Errno == "" {
return nil
}
for _, c := range l.val.Errno {
if !unicode.IsUpper(c) {
return errors.New("invalid errno")
}
}
return nil
}

func (l *linuxSeccompBase) UnmarshalJSON(b []byte) error {
var v linuxSeccompValue
err := json.Unmarshal(b, &v)
if err != nil {
return err
}
l.val = v
return nil
}

func (l linuxSeccompBase) Set() []LinuxSeccompEntry {
return l.val.Set
}

func (l linuxSeccompBase) Errno() LinuxSeccompErrno {
return l.val.Errno
}

type LinuxSeccompRetainSet struct {
linuxSeccompBase
}

func NewLinuxSeccompRetainSet(errno string, syscall ...string) (*LinuxSeccompRetainSet, error) {
l := LinuxSeccompRetainSet{
linuxSeccompBase{
linuxSeccompValue{
make([]LinuxSeccompEntry, len(syscall)),
LinuxSeccompErrno(errno),
},
},
}
for i, c := range syscall {
l.linuxSeccompBase.val.Set[i] = LinuxSeccompEntry(c)
}
if err := l.AssertValid(); err != nil {
return nil, err
}
return &l, nil
}

func (l LinuxSeccompRetainSet) AsIsolator() (*Isolator, error) {
b, err := json.Marshal(l.linuxSeccompBase.val)
if err != nil {
return nil, err
}
rm := json.RawMessage(b)
return &Isolator{
Name: LinuxSeccompRetainSetName,
ValueRaw: &rm,
value: &l,
}, nil
}

type LinuxSeccompRemoveSet struct {
linuxSeccompBase
}

func NewLinuxSeccompRemoveSet(errno string, syscall ...string) (*LinuxSeccompRemoveSet, error) {
l := LinuxSeccompRemoveSet{
linuxSeccompBase{
linuxSeccompValue{
make([]LinuxSeccompEntry, len(syscall)),
LinuxSeccompErrno(errno),
},
},
}
for i, c := range syscall {
l.linuxSeccompBase.val.Set[i] = LinuxSeccompEntry(c)
}
if err := l.AssertValid(); err != nil {
return nil, err
}
return &l, nil
}

func (l LinuxSeccompRemoveSet) AsIsolator() (*Isolator, error) {
b, err := json.Marshal(l.linuxSeccompBase.val)
if err != nil {
return nil, err
}
rm := json.RawMessage(b)
return &Isolator{
Name: LinuxSeccompRemoveSetName,
ValueRaw: &rm,
value: &l,
}, nil
}
Loading