Add support for swarm seccomp and apparmor

And also no-new-privileges

Signed-off-by: Drew Erny <derny@mirantis.com>
This commit is contained in:
Drew Erny 2023-08-30 10:18:51 -05:00
parent bd7b27b5c8
commit 42a51cb285
12 changed files with 1298 additions and 408 deletions

View file

@ -118,4 +118,13 @@ func adjustForAPIVersion(cliVersion string, service *swarm.ServiceSpec) {
service.Mode.ReplicatedJob = nil
service.Mode.GlobalJob = nil
}
if versions.LessThan(cliVersion, "1.44") {
// seccomp, apparmor, and no_new_privs were added in 1.44.
if service.TaskTemplate.ContainerSpec != nil && service.TaskTemplate.ContainerSpec.Privileges != nil {
service.TaskTemplate.ContainerSpec.Privileges.Seccomp = nil
service.TaskTemplate.ContainerSpec.Privileges.AppArmor = nil
service.TaskTemplate.ContainerSpec.Privileges.NoNewPrivileges = false
}
}
}

View file

@ -3553,6 +3553,32 @@ definitions:
Level:
type: "string"
description: "SELinux level label"
Seccomp:
type: "object"
description: "Options for configuring seccomp on the container"
properties:
Mode:
type: "string"
enum:
- "default"
- "unconfined"
- "custom"
Profile:
description: "The custom seccomp profile as a json object"
type: "string"
AppArmor:
type: "object"
description: "Options for configuring AppArmor on the container"
properties:
Mode:
type: "string"
enum:
- "default"
- "disabled"
NoNewPrivileges:
type: "boolean"
description: "Configuration of the no_new_privs bit in the container"
TTY:
description: "Whether a pseudo-TTY should be allocated."
type: "boolean"

View file

@ -32,6 +32,42 @@ type SELinuxContext struct {
Level string
}
// SeccompMode is the type used for the enumeration of possible seccomp modes
// in SeccompOpts
type SeccompMode string
const (
SeccompModeDefault SeccompMode = "default"
SeccompModeUnconfined SeccompMode = "unconfined"
SeccompModeCustom SeccompMode = "custom"
)
// SeccompOpts defines the options for configuring seccomp on a swarm-managed
// container.
type SeccompOpts struct {
// Mode is the SeccompMode used for the container.
Mode SeccompMode `json:",omitempty"`
// Profile is the custom seccomp profile as a json object to be used with
// the container. Mode should be set to SeccompModeCustom when using a
// custom profile in this manner.
Profile []byte `json:",omitempty"`
}
// AppArmorMode is type used for the enumeration of possible AppArmor modes in
// AppArmorOpts
type AppArmorMode string
const (
AppArmorModeDefault AppArmorMode = "default"
AppArmorModeDisabled AppArmorMode = "disabled"
)
// AppArmorOpts defines the options for configuring AppArmor on a swarm-managed
// container. Currently, custom AppArmor profiles are not supported.
type AppArmorOpts struct {
Mode AppArmorMode `json:",omitempty"`
}
// CredentialSpec for managed service account (Windows only)
type CredentialSpec struct {
Config string
@ -41,8 +77,11 @@ type CredentialSpec struct {
// Privileges defines the security options for the container.
type Privileges struct {
CredentialSpec *CredentialSpec
SELinuxContext *SELinuxContext
CredentialSpec *CredentialSpec
SELinuxContext *SELinuxContext
Seccomp *SeccompOpts `json:",omitempty"`
AppArmor *AppArmorOpts `json:",omitempty"`
NoNewPrivileges bool
}
// ContainerSpec represents the spec of a container.

View file

@ -69,6 +69,34 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) *types.ContainerSpec {
Level: c.Privileges.SELinuxContext.Level,
}
}
if c.Privileges.Seccomp != nil {
containerSpec.Privileges.Seccomp = &types.SeccompOpts{
Profile: c.Privileges.Seccomp.Profile,
}
switch c.Privileges.Seccomp.Mode {
case swarmapi.Privileges_SeccompOpts_DEFAULT:
containerSpec.Privileges.Seccomp.Mode = types.SeccompModeDefault
case swarmapi.Privileges_SeccompOpts_UNCONFINED:
containerSpec.Privileges.Seccomp.Mode = types.SeccompModeUnconfined
case swarmapi.Privileges_SeccompOpts_CUSTOM:
containerSpec.Privileges.Seccomp.Mode = types.SeccompModeCustom
}
}
if c.Privileges.Apparmor != nil {
containerSpec.Privileges.AppArmor = &types.AppArmorOpts{}
switch c.Privileges.Apparmor.Mode {
case swarmapi.Privileges_AppArmorOpts_DEFAULT:
containerSpec.Privileges.AppArmor.Mode = types.AppArmorModeDefault
case swarmapi.Privileges_AppArmorOpts_DISABLED:
containerSpec.Privileges.AppArmor.Mode = types.AppArmorModeDisabled
}
}
containerSpec.Privileges.NoNewPrivileges = c.Privileges.NoNewPrivileges
}
// Mounts
@ -308,6 +336,34 @@ func containerToGRPC(c *types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
Level: c.Privileges.SELinuxContext.Level,
}
}
if c.Privileges.Seccomp != nil {
containerSpec.Privileges.Seccomp = &swarmapi.Privileges_SeccompOpts{
Profile: c.Privileges.Seccomp.Profile,
}
switch c.Privileges.Seccomp.Mode {
case types.SeccompModeDefault:
containerSpec.Privileges.Seccomp.Mode = swarmapi.Privileges_SeccompOpts_DEFAULT
case types.SeccompModeUnconfined:
containerSpec.Privileges.Seccomp.Mode = swarmapi.Privileges_SeccompOpts_UNCONFINED
case types.SeccompModeCustom:
containerSpec.Privileges.Seccomp.Mode = swarmapi.Privileges_SeccompOpts_CUSTOM
}
}
if c.Privileges.AppArmor != nil {
containerSpec.Privileges.Apparmor = &swarmapi.Privileges_AppArmorOpts{}
switch c.Privileges.AppArmor.Mode {
case types.AppArmorModeDefault:
containerSpec.Privileges.Apparmor.Mode = swarmapi.Privileges_AppArmorOpts_DEFAULT
case types.AppArmorModeDisabled:
containerSpec.Privileges.Apparmor.Mode = swarmapi.Privileges_AppArmorOpts_DISABLED
}
}
containerSpec.Privileges.NoNewPrivileges = c.Privileges.NoNewPrivileges
}
if c.Configs != nil {

View file

@ -698,6 +698,32 @@ func (c *containerConfig) applyPrivileges(hc *enginecontainer.HostConfig) {
hc.SecurityOpt = append(hc.SecurityOpt, "label=type:"+selinux.Type)
}
}
// variable to make the lines shorter and easier to read
if seccomp := privileges.Seccomp; seccomp != nil {
switch seccomp.Mode {
// case api.Privileges_SeccompOpts_DEFAULT:
// if the setting is default, nothing needs to be set here. we leave
// the option empty.
case api.Privileges_SeccompOpts_UNCONFINED:
hc.SecurityOpt = append(hc.SecurityOpt, "seccomp=unconfined")
case api.Privileges_SeccompOpts_CUSTOM:
// Profile is bytes, but those bytes are actually a string. This is
// basically verbatim what happens in the cli after a file is read.
hc.SecurityOpt = append(hc.SecurityOpt, fmt.Sprintf("seccomp=%s", seccomp.Profile))
}
}
// if the setting is DEFAULT, then nothing to be done. If it's DISABLED,
// we set that. Custom not supported yet. When custom *is* supported, make
// it look like the above.
if apparmor := privileges.Apparmor; apparmor != nil && apparmor.Mode == api.Privileges_AppArmorOpts_DISABLED {
hc.SecurityOpt = append(hc.SecurityOpt, "apparmor=unconfined")
}
if privileges.NoNewPrivileges {
hc.SecurityOpt = append(hc.SecurityOpt, "no-new-privileges=true")
}
}
func (c *containerConfig) eventFilter() filters.Args {

View file

@ -52,6 +52,9 @@ keywords: "API, Docker, rcli, REST, documentation"
These endpoints will also return the full set of validation errors they find,
instead of returning only the first one.
Note that this change is _unversioned_ and applies to all API versions.
* `POST /services/create` and `POST /services/{id}/update` now accept `Seccomp`
and `AppArmor` fields in the `ContainerSpec.Privileges` object. This allows
some configuration of Seccomp and AppArmor in Swarm services.
## v1.43 API changes

View file

@ -66,7 +66,7 @@ require (
github.com/moby/locker v1.0.1
github.com/moby/patternmatcher v0.6.0
github.com/moby/pubsub v1.0.0
github.com/moby/swarmkit/v2 v2.0.0-20230815220644-3f2e40b3ed51
github.com/moby/swarmkit/v2 v2.0.0-20230823155524-12f0c246fed0
github.com/moby/sys/mount v0.3.3
github.com/moby/sys/mountinfo v0.6.2
github.com/moby/sys/sequential v0.5.0

View file

@ -917,8 +917,8 @@ github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkV
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/pubsub v1.0.0 h1:jkp/imWsmJz2f6LyFsk7EkVeN2HxR/HTTOY8kHrsxfA=
github.com/moby/pubsub v1.0.0/go.mod h1:bXSO+3h5MNXXCaEG+6/NlAIk7MMZbySZlnB+cUQhKKc=
github.com/moby/swarmkit/v2 v2.0.0-20230815220644-3f2e40b3ed51 h1:Am1RXolTCcQT5zaEUBGczaHZaV069crCKpLHckp9isM=
github.com/moby/swarmkit/v2 v2.0.0-20230815220644-3f2e40b3ed51/go.mod h1:dHTjRFlamMrutFg1Vi0AEZKtVx2qZV/7A/imviPXQiE=
github.com/moby/swarmkit/v2 v2.0.0-20230823155524-12f0c246fed0 h1:DBx1xD69N0nQjoMQskoTQQTb0hpZbo+8Rk9ug0wUMKs=
github.com/moby/swarmkit/v2 v2.0.0-20230823155524-12f0c246fed0/go.mod h1:dHTjRFlamMrutFg1Vi0AEZKtVx2qZV/7A/imviPXQiE=
github.com/moby/sys/mount v0.1.0/go.mod h1:FVQFLDRWwyBjDTBNQXDlWnSFREqOo3OKX9aqhmeoo74=
github.com/moby/sys/mount v0.1.1/go.mod h1:FVQFLDRWwyBjDTBNQXDlWnSFREqOo3OKX9aqhmeoo74=
github.com/moby/sys/mount v0.3.3 h1:fX1SVkXFJ47XWDoeFW4Sq7PdQJnV2QIDZAqjNqgEjUs=

View file

@ -4324,6 +4324,29 @@ file {
}
json_name: "selinuxContext"
}
field {
name: "seccomp"
number: 3
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".docker.swarmkit.v1.Privileges.SeccompOpts"
json_name: "seccomp"
}
field {
name: "apparmor"
number: 4
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".docker.swarmkit.v1.Privileges.AppArmorOpts"
json_name: "apparmor"
}
field {
name: "no_new_privileges"
number: 5
label: LABEL_OPTIONAL
type: TYPE_BOOL
json_name: "noNewPrivileges"
}
nested_type {
name: "CredentialSpec"
field {
@ -4392,6 +4415,61 @@ file {
json_name: "level"
}
}
nested_type {
name: "SeccompOpts"
field {
name: "mode"
number: 1
label: LABEL_OPTIONAL
type: TYPE_ENUM
type_name: ".docker.swarmkit.v1.Privileges.SeccompOpts.SeccompMode"
json_name: "mode"
}
field {
name: "profile"
number: 2
label: LABEL_OPTIONAL
type: TYPE_BYTES
json_name: "profile"
}
enum_type {
name: "SeccompMode"
value {
name: "DEFAULT"
number: 0
}
value {
name: "UNCONFINED"
number: 1
}
value {
name: "CUSTOM"
number: 2
}
}
}
nested_type {
name: "AppArmorOpts"
field {
name: "mode"
number: 1
label: LABEL_OPTIONAL
type: TYPE_ENUM
type_name: ".docker.swarmkit.v1.Privileges.AppArmorOpts.AppArmorMode"
json_name: "mode"
}
enum_type {
name: "AppArmorMode"
value {
name: "DEFAULT"
number: 0
}
value {
name: "DISABLED"
number: 1
}
}
}
}
message_type {
name: "JobStatus"

File diff suppressed because it is too large Load diff

View file

@ -1156,6 +1156,39 @@ message Privileges {
string level = 5;
}
SELinuxContext selinux_context = 2 [(gogoproto.customname) = "SELinuxContext"];
// SeccompOpts contains options for configuring seccomp profiles on the
// container. See https://docs.docker.com/engine/security/seccomp/ for more
// information.
message SeccompOpts {
enum SeccompMode {
DEFAULT = 0;
UNCONFINED = 1;
CUSTOM = 2;
}
SeccompMode mode = 1;
// Profile contains the json definition of the seccomp profile to use,
// if Mode is set to custom.
bytes profile = 2;
}
SeccompOpts seccomp = 3;
// AppArmorOpts contains options for configuring AppArmor profiles on the
// container. Currently, custom profiles are not supported. See
// https://docs.docker.com/engine/security/apparmor/ for more information.
message AppArmorOpts {
enum AppArmorMode {
DEFAULT = 0;
DISABLED = 1;
}
AppArmorMode mode = 1;
}
AppArmorOpts apparmor = 4;
// NoNewPrivileges, if set to true, disables the container from gaining new
// privileges. See https://docs.kernel.org/userspace-api/no_new_privs.html
// for details.
bool no_new_privileges = 5;
}
// JobStatus indicates the status of a Service that is in one of the Job modes.

2
vendor/modules.txt vendored
View file

@ -829,7 +829,7 @@ github.com/moby/patternmatcher/ignorefile
# github.com/moby/pubsub v1.0.0
## explicit; go 1.19
github.com/moby/pubsub
# github.com/moby/swarmkit/v2 v2.0.0-20230815220644-3f2e40b3ed51
# github.com/moby/swarmkit/v2 v2.0.0-20230823155524-12f0c246fed0
## explicit; go 1.18
github.com/moby/swarmkit/v2/agent
github.com/moby/swarmkit/v2/agent/configs