-
Notifications
You must be signed in to change notification settings - Fork 146
ace: add Linux-specific seccomp isolator #621
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,6 +51,8 @@ var ( | |
patchMounts string | ||
patchPorts string | ||
patchIsolators string | ||
patchSeccompMode string | ||
patchSeccompSet string | ||
|
||
catPrettyPrint bool | ||
|
||
|
@@ -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]`, | ||
|
@@ -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") | ||
} | ||
|
@@ -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) | ||
|
@@ -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 != "" { | ||
|
@@ -250,6 +264,18 @@ func patchManifest(im *schema.ImageManifest) error { | |
} | ||
} | ||
|
||
// Parse seccomp args and override existing seccomp isolators | ||
if patchSeccompMode != "" { | ||
seccompIsolator, err := parseSeccompArgs(patchSeccompMode, patchSeccompSet) | ||
if err != nil { | ||
return err | ||
} | ||
seccompReps := []types.ACIdentifier{types.LinuxSeccompRemoveSetName, types.LinuxSeccompRetainSetName} | ||
app.Isolators.ReplaceIsolatorsByName(*seccompIsolator, seccompReps) | ||
} else if patchSeccompSet != "" { | ||
return fmt.Errorf("--seccomp-set specified without --seccomp-mode") | ||
} | ||
|
||
if patchIsolators != "" { | ||
isolators := strings.Split(patchIsolators, ":") | ||
for _, is := range isolators { | ||
|
@@ -260,14 +286,16 @@ 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, types.LinuxSeccompRetainSetName: | ||
ok = false | ||
} | ||
|
||
if !ok { | ||
|
@@ -284,6 +312,51 @@ 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.Isolator, 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] | ||
} else { | ||
return nil, fmt.Errorf("invalid seccomp-mode optional argument: %s", a) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. else return error? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Defensively, sounds like a good idea. |
||
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, ",")...) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like errno could be unset in this place if it's not in cmdline parameters. Is this desired? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Other possibility - command line help could be misinterpreted as me as example shows ;) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, docs are more clear in this, so IMO cmdline help is not so clear. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In which way you don't find it clear? Any way I can improve it to be more immediate to grasp? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry for mess. It was unclear for me which errno values could be used in this place. After a break - it's clear for me to comment AFTER reading WHOLE diff... |
||
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) | ||
} | ||
seccompIsolator, err := seccomp.AsIsolator() | ||
if err != nil { | ||
return nil, err | ||
} | ||
return seccompIsolator, 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 { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actool patch-manifest
could return an error when patchSeccompMode == "" but patchSeccompSet != "".There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realized I didn't make it clear and my code is making some assumptions that
set
may be empty. On a second thought, I think I am not sure there are actual usecases for this. Should I make it explicit thatset MUST NOT be empty
and just kill this corner case?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was referring to the other side: if the set is not empty but the mode is not given (e.g. the user called
actool patch-manifest --seccomp-set=chroot
). In this case, the current code inactool
would just ignore the seccomp parameter and not add any isolator. I thinkactool
should return an error in that case instead of silently doing nothing.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, fixed that. Opinions regarding the other question?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm ok with saying
set MUST NOT be empty
. It's easier to change that in that direction if we ever need to allow empty set later.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack, done.