Skip to content

Commit

Permalink
feature: appArmor for both cri manager and container manager
Browse files Browse the repository at this point in the history
Signed-off-by: YaoZengzeng <[email protected]>
  • Loading branch information
YaoZengzeng committed Feb 1, 2018
1 parent d4b0838 commit 9ddb154
Show file tree
Hide file tree
Showing 13 changed files with 181 additions and 6 deletions.
5 changes: 5 additions & 0 deletions cli/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ type container struct {
pidMode string
utsMode string
sysctls []string
<<<<<<< HEAD
network []string
=======
securityOpt []string
>>>>>>> feature: appArmor for both cri manager and container manager
blkioWeight uint16
blkioWeightDevice WeightDevice
blkioDeviceReadBps ThrottleBpsDevice
Expand Down Expand Up @@ -114,6 +118,7 @@ func (c *container) config() (*types.ContainerCreateConfig, error) {
PidMode: c.pidMode,
UTSMode: c.utsMode,
Sysctls: sysctls,
SecurityOpt: c.securityOpt,
},
}

Expand Down
1 change: 1 addition & 0 deletions cli/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func (cc *CreateCommand) addFlags() {
flagSet.StringVar(&cc.utsMode, "uts", "", "UTS namespace to use")
flagSet.StringSliceVar(&cc.sysctls, "sysctl", nil, "Sysctl options")
flagSet.StringSliceVar(&cc.network, "net", nil, "Set networks to container")
flagSet.StringSliceVar(&cc.securityOpt, "security-opt", nil, "Security Options")
flagSet.Uint16Var(&cc.blkioWeight, "blkio-weight", 0, "Block IO (relative weight), between 10 and 1000, or 0 to disable")
flagSet.Var(&cc.blkioWeightDevice, "blkio-weight-device", "Block IO weight (relative device weight)")
flagSet.Var(&cc.blkioDeviceReadBps, "device-read-bps", "Limit read rate (bytes per second) from a device")
Expand Down
1 change: 1 addition & 0 deletions cli/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ func (rc *RunCommand) addFlags() {
flagSet.StringVar(&rc.utsMode, "uts", "", "UTS namespace to use")
flagSet.StringSliceVar(&rc.sysctls, "sysctl", nil, "Sysctl options")
flagSet.StringSliceVar(&rc.network, "net", nil, "Set networks to container")
flagSet.StringSliceVar(&rc.securityOpt, "security-opt", nil, "Security Options")
flagSet.Uint16Var(&rc.blkioWeight, "blkio-weight", 0, "Block IO (relative weight), between 10 and 1000, or 0 to disable")
flagSet.Var(&rc.blkioWeightDevice, "blkio-weight-device", "Block IO weight (relative device weight)")
flagSet.Var(&rc.blkioDeviceReadBps, "device-read-bps", "Limit read rate (bytes per second) from a device")
Expand Down
7 changes: 6 additions & 1 deletion daemon/mgr/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,10 @@ func (mgr *ContainerManager) Create(ctx context.Context, name string, config *ty
meta.Config.NetworkDisabled = true
}

if err := parseSecurityOpt(meta, config.HostConfig); err != nil {
return nil, err
}

// merge image's config into container's meta
if err := meta.merge(func() (v1.ImageConfig, error) {
ociimage, err := mgr.Client.GetOciImage(ctx, config.Image)
Expand Down Expand Up @@ -437,7 +441,8 @@ func (mgr *ContainerManager) Start(ctx context.Context, id, detachKeys string) (
c.meta.State.FinishedAt = time.Now().UTC().Format(utils.TimeLayout)
c.meta.State.Error = err.Error()
c.meta.State.Pid = 0
//TODO get and set exit code
//TODO: make exit code more correct.
c.meta.State.ExitCode = 127

// release io
io.Close()
Expand Down
21 changes: 21 additions & 0 deletions daemon/mgr/container_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package mgr

import (
"fmt"
"strings"

"github.com/alibaba/pouch/apis/types"
"github.com/alibaba/pouch/daemon/meta"
"github.com/alibaba/pouch/pkg/errtypes"
"github.com/alibaba/pouch/pkg/randomid"
Expand Down Expand Up @@ -98,3 +100,22 @@ func (mgr *ContainerManager) generateName(id string) string {
}
return name
}

func parseSecurityOpt(meta *ContainerMeta, config *types.HostConfig) error {
for _, opt := range config.SecurityOpt {
fields := strings.SplitN(opt, "=", 2)
if len(fields) != 2 {
return fmt.Errorf("invalid --security-opt %q: it should be in format of key=value", opt)
}

switch fields[0] {
// TODO: handle other security options.
case "apparmor":
meta.AppArmorProfile = fields[1]
default:
return fmt.Errorf("invalid --security-opt %q: unknown type", opt)
}
}

return nil
}
5 changes: 2 additions & 3 deletions daemon/mgr/cri.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,9 +270,8 @@ func (c *CriManager) CreateContainer(ctx context.Context, r *runtime.CreateConta
}
createConfig := &apitypes.ContainerCreateConfig{
ContainerConfig: apitypes.ContainerConfig{
// TODO: wait for them to be fully supported.
// Entrypoint: config.Command,
// Cmd: config.Args,
// TODO: maybe we should ditinguish cmd and entrypoint more clearly.
Cmd: config.Command,
Env: generateEnvList(config.GetEnvs()),
Image: image,
WorkingDir: config.WorkingDir,
Expand Down
44 changes: 43 additions & 1 deletion daemon/mgr/cri_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,9 +271,51 @@ func modifyContainerNamespaceOptions(nsOpts *runtime.NamespaceOption, podSandbox
hostConfig.UTSMode = sandboxNSMode
}

// getAppArmorSecurityOpts gets appArmor options from container config.
func getAppArmorSecurityOpts(sc *runtime.LinuxContainerSecurityContext) ([]string, error) {
profile := sc.ApparmorProfile
if profile == "" || profile == ProfileRuntimeDefault {
// Pouch should applies the default profile by default.
return nil, nil
}

// Return unconfined profile explicitly.
if profile == ProfileNameUnconfined {
return []string{fmt.Sprintf("apparmor=%s", profile)}, nil
}

if !strings.HasPrefix(profile, ProfileNamePrefix) {
return nil, fmt.Errorf("undefault profile name should prefix with %q", ProfileNamePrefix)
}
profile = strings.TrimPrefix(profile, ProfileNamePrefix)

return []string{fmt.Sprintf("apparmor=%s", profile)}, nil
}

// modifyHostConfig applies security context config to pouch's HostConfig.
func modifyHostConfig(sc *runtime.LinuxContainerSecurityContext, hostConfig *apitypes.HostConfig) error {
if sc == nil {
return nil
}

// TODO: apply other security options.

// Apply appArmor options.
appArmorSecurityOpts, err := getAppArmorSecurityOpts(sc)
if err != nil {
return fmt.Errorf("failed to generate appArmor security options: %v", err)
}
hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, appArmorSecurityOpts...)

return nil
}

// applyContainerSecurityContext updates pouch container options according to security context.
func applyContainerSecurityContext(lc *runtime.LinuxContainerConfig, podSandboxID string, config *apitypes.ContainerConfig, hc *apitypes.HostConfig) error {
// TODO: modify Config and HostConfig.
err := modifyHostConfig(lc.SecurityContext, hc)
if err != nil {
return err
}

modifyContainerNamespaceOptions(lc.SecurityContext.GetNamespaceOptions(), podSandboxID, hc)

Expand Down
2 changes: 2 additions & 0 deletions daemon/mgr/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ var setupFunc = []SetupFunc{

// linux-platform-specifc spec
setupSysctl,
setupAppArmor,

// blkio spec
setupBlkio,
}
Expand Down
45 changes: 45 additions & 0 deletions daemon/mgr/spec_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@ package mgr

import (
"context"
"io/ioutil"
"os"
)

const (
// ProfileNamePrefix is the prefix for loading profiles on a localhost. Eg. localhost/profileName.
ProfileNamePrefix = "localhost/"
// ProfileRuntimeDefault indicates that we should use or create a runtime default profile.
ProfileRuntimeDefault = "runtime/default"
// ProfileNameUnconfined is a string indicating one should run a pod/containerd without a security profile.
ProfileNameUnconfined = "unconfined"
)

// Setup linux-platform-sepecific specification.
Expand All @@ -10,3 +21,37 @@ func setupSysctl(ctx context.Context, meta *ContainerMeta, spec *SpecWrapper) er
spec.s.Linux.Sysctl = meta.HostConfig.Sysctls
return nil
}

// isAppArmorEnabled returns true if apparmor is enabled for the host.
// This function is forked from
// https://github.com/opencontainers/runc/blob/1a81e9ab1f138c091fe5c86d0883f87716088527/libcontainer/apparmor/apparmor.go
// to avoid the libapparmor dependency.
func isAppArmorEnabled() bool {
if _, err := os.Stat("/sys/kernel/security/apparmor"); err == nil && os.Getenv("container") == "" {
if _, err = os.Stat("/sbin/apparmor_parser"); err == nil {
buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled")
return err == nil && len(buf) > 1 && buf[0] == 'Y'
}
}
return false
}

func setupAppArmor(ctx context.Context, meta *ContainerMeta, spec *SpecWrapper) error {
if !isAppArmorEnabled() {
// Return if the apparmor is disabled.
return nil
}

appArmorProfile := meta.AppArmorProfile
switch appArmorProfile {
case ProfileNameUnconfined:
return nil
case ProfileRuntimeDefault, "":
// TODO: handle runtime default case.
return nil
default:
spec.s.Process.ApparmorProfile = appArmorProfile
}

return nil
}
2 changes: 1 addition & 1 deletion hack/cri-test/test-cri.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ POUCH_SOCK="/var/run/pouchcri.sock"

# CRI_FOCUS focuses the test to run.
# With the CRI manager completes its function, we may need to expand this field.
CRI_FOCUS=${CRI_FOCUS:-"PodSandbox|basic operations on container|Runtime info"}
CRI_FOCUS=${CRI_FOCUS:-"PodSandbox|AppArmor|basic operations on container|Runtime info"}

# CRI_SKIP skips the test to skip.
CRI_SKIP=${CRI_SKIP:-""}
Expand Down
27 changes: 27 additions & 0 deletions test/cli_create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,33 @@ func (suite *PouchCreateSuite) TestCreateWithSysctls(c *check.C) {
}
}

// TestCreateWithAppArmor tries to test create a container with security option AppArmor.
func (suite *PouchCreateSuite) TestCreateWithAppArmor(c *check.C) {
appArmor := "apparmor=unconfined"
name := "create-apparmor"

res := command.PouchRun("create", "--name", name, "--security-opt", appArmor, busyboxImage)
res.Assert(c, icmd.Success)

output := command.PouchRun("inspect", name).Stdout()

result := &types.ContainerJSON{}
if err := json.Unmarshal([]byte(output), result); err != nil {
c.Errorf("failed to decode inspect output: %v", err)
}
c.Assert(result.HostConfig.SecurityOpt, check.NotNil)

exist := false
for _, opt := range result.HostConfig.SecurityOpt {
if opt == appArmor {
exist = true
}
}
if !exist {
c.Errorf("failed to set AppArmor in security-opt")
}
}

// TestCreateEnableLxcfs tries to test create a container with lxcfs.
func (suite *PouchCreateSuite) TestCreateEnableLxcfs(c *check.C) {
name := "create-lxcfs"
Expand Down
14 changes: 14 additions & 0 deletions test/cli_run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,20 @@ func (suite *PouchRunSuite) TestRunWithSysctls(c *check.C) {
if !strings.Contains(output, "1") {
c.Fatalf("failed to run a container with sysctls: %s", output)
}
command.PouchRun("rm", "-f", name).Assert(c, icmd.Success)
}

// TestRunWithAppArmor is to verify run container with security option AppArmor.
func (suite *PouchRunSuite) TestRunWithAppArmor(c *check.C) {
appArmor := "apparmor=unconfined"
name := "run-apparmor"

res := command.PouchRun("run", "--name", name, "--security-opt", appArmor, busyboxImage)
res.Assert(c, icmd.Success)

// TODO: do the test more strictly with effective AppArmor profile.

command.PouchRun("rm", "-f", name).Assert(c, icmd.Success)
}

// TestRunWithBlkioWeight is to verify --specific Blkio Weight when running a container.
Expand Down
13 changes: 13 additions & 0 deletions test/cli_start_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,16 @@ func (suite *PouchStartSuite) TestStartWithSysctls(c *check.C) {

command.PouchRun("stop", name).Assert(c, icmd.Success)
}

// TestStartWithAppArmor starts a container with security option AppArmor.
func (suite *PouchStartSuite) TestStartWithAppArmor(c *check.C) {
appArmor := "apparmor=unconfined"
name := "start-apparmor"

command.PouchRun("create", "--name", name, "--security-opt", appArmor, busyboxImage)
command.PouchRun("start", name).Assert(c, icmd.Success)

// TODO: do the test more strictly with effective AppArmor profile.

command.PouchRun("stop", name).Assert(c, icmd.Success)
}

0 comments on commit 9ddb154

Please sign in to comment.