diff options
-rw-r--r-- | cmd/podman/create.go | 122 | ||||
-rwxr-xr-x | test/test_podman_baseline.sh | 74 |
2 files changed, 147 insertions, 49 deletions
diff --git a/cmd/podman/create.go b/cmd/podman/create.go index 3429c4d97..95b7a8bed 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -15,6 +15,7 @@ import ( ann "github.com/containers/libpod/pkg/annotations" "github.com/containers/libpod/pkg/apparmor" "github.com/containers/libpod/pkg/inspect" + "github.com/containers/libpod/pkg/rootless" cc "github.com/containers/libpod/pkg/spec" "github.com/containers/libpod/pkg/util" libpodVersion "github.com/containers/libpod/version" @@ -148,52 +149,20 @@ func createCmd(c *cli.Context) error { return nil } -func parseSecurityOpt(config *cc.CreateConfig, securityOpts []string) error { - var ( - labelOpts []string - err error - ) - - if config.PidMode.IsHost() { - labelOpts = append(labelOpts, label.DisableSecOpt()...) - } else if config.PidMode.IsContainer() { - ctr, err := config.Runtime.LookupContainer(config.PidMode.Container()) - if err != nil { - return errors.Wrapf(err, "container %q not found", config.PidMode.Container()) - } - labelOpts = append(labelOpts, label.DupSecOpt(ctr.ProcessLabel())...) - } - - if config.IpcMode.IsHost() { - labelOpts = append(labelOpts, label.DisableSecOpt()...) - } else if config.IpcMode.IsContainer() { - ctr, err := config.Runtime.LookupContainer(config.IpcMode.Container()) - if err != nil { - return errors.Wrapf(err, "container %q not found", config.IpcMode.Container()) - } - labelOpts = append(labelOpts, label.DupSecOpt(ctr.ProcessLabel())...) - } - - for _, opt := range securityOpts { - if opt == "no-new-privileges" { - config.NoNewPrivs = true - } else { - con := strings.SplitN(opt, "=", 2) - if len(con) != 2 { - return fmt.Errorf("Invalid --security-opt 1: %q", opt) - } - - switch con[0] { - case "label": - labelOpts = append(labelOpts, con[1]) - case "apparmor": - config.ApparmorProfile = con[1] - case "seccomp": - config.SeccompProfilePath = con[1] - default: - return fmt.Errorf("Invalid --security-opt 2: %q", opt) - } +// Checks if a user-specified AppArmor profile is loaded, or loads the default profile if +// AppArmor is enabled. +// Any interaction with AppArmor requires root permissions. +func loadAppArmor(config *cc.CreateConfig) error { + if rootless.IsRootless() { + noAAMsg := "AppArmor security is not available in rootless mode" + switch config.ApparmorProfile { + case "": + logrus.Warn(noAAMsg) + case "unconfined": + default: + return fmt.Errorf(noAAMsg) } + return nil } if config.ApparmorProfile == "" && apparmor.IsEnabled() { @@ -233,23 +202,78 @@ func parseSecurityOpt(config *cc.CreateConfig, securityOpts []string) error { } } else if config.ApparmorProfile != "" && config.ApparmorProfile != "unconfined" { if !apparmor.IsEnabled() { - return fmt.Errorf("profile specified but AppArmor is disabled on the host") + return fmt.Errorf("Profile specified but AppArmor is disabled on the host") } isLoaded, err := apparmor.IsLoaded(config.ApparmorProfile) if err != nil { switch err { case apparmor.ErrApparmorUnsupported: - return fmt.Errorf("profile specified but AppArmor is not supported") + return fmt.Errorf("Profile specified but AppArmor is not supported") default: - return fmt.Errorf("error checking if AppArmor profile is loaded: %v", err) + return fmt.Errorf("Error checking if AppArmor profile is loaded: %v", err) } } if !isLoaded { - return fmt.Errorf("specified AppArmor profile '%s' is not loaded", config.ApparmorProfile) + return fmt.Errorf("The specified AppArmor profile '%s' is not loaded", config.ApparmorProfile) } } + return nil +} + +func parseSecurityOpt(config *cc.CreateConfig, securityOpts []string) error { + var ( + labelOpts []string + err error + ) + + if config.PidMode.IsHost() { + labelOpts = append(labelOpts, label.DisableSecOpt()...) + } else if config.PidMode.IsContainer() { + ctr, err := config.Runtime.LookupContainer(config.PidMode.Container()) + if err != nil { + return errors.Wrapf(err, "container %q not found", config.PidMode.Container()) + } + labelOpts = append(labelOpts, label.DupSecOpt(ctr.ProcessLabel())...) + } + + if config.IpcMode.IsHost() { + labelOpts = append(labelOpts, label.DisableSecOpt()...) + } else if config.IpcMode.IsContainer() { + ctr, err := config.Runtime.LookupContainer(config.IpcMode.Container()) + if err != nil { + return errors.Wrapf(err, "container %q not found", config.IpcMode.Container()) + } + labelOpts = append(labelOpts, label.DupSecOpt(ctr.ProcessLabel())...) + } + + for _, opt := range securityOpts { + if opt == "no-new-privileges" { + config.NoNewPrivs = true + } else { + con := strings.SplitN(opt, "=", 2) + if len(con) != 2 { + return fmt.Errorf("Invalid --security-opt 1: %q", opt) + } + + switch con[0] { + case "label": + labelOpts = append(labelOpts, con[1]) + case "apparmor": + config.ApparmorProfile = con[1] + case "seccomp": + config.SeccompProfilePath = con[1] + default: + return fmt.Errorf("Invalid --security-opt 2: %q", opt) + } + } + } + + if err := loadAppArmor(config); err != nil { + return err + } + if config.SeccompProfilePath == "" { if _, err := os.Stat(libpod.SeccompOverridePath); err == nil { config.SeccompProfilePath = libpod.SeccompOverridePath diff --git a/test/test_podman_baseline.sh b/test/test_podman_baseline.sh index a9ade8c7b..74a4398ca 100755 --- a/test/test_podman_baseline.sh +++ b/test/test_podman_baseline.sh @@ -372,3 +372,77 @@ podman run whale-says podman rm --all podman rmi --all rm ./Dockerfile* + +######## +# Run AppArmor rootless tests +######## +if aa-enabled >/dev/null && getent passwd 1000 >/dev/null; then + # Expected to succeed + sudo -u "#1000" podman run alpine echo hello + rc=$? + echo -n "rootless with no AppArmor profile " + if [ $rc == 0 ]; then + echo "passed" + else + echo "failed" + fi + + # Expected to succeed + sudo -u "#1000" podman run --security-opt apparmor=unconfined alpine echo hello + rc=$? + echo -n "rootless with unconfined AppArmor profile " + if [ $rc == 0 ]; then + echo "passed" + else + echo "failed" + fi + + aaFile="/tmp/aaProfile" + aaProfile="aa-demo-profile" + cat > $aaFile << EOF +#include <tunables/global> +profile aa-demo-profile flags=(attach_disconnected,mediate_deleted) { + #include <abstractions/base> + deny mount, + deny /sys/[^f]*/** wklx, + deny /sys/f[^s]*/** wklx, + deny /sys/fs/[^c]*/** wklx, + deny /sys/fs/c[^g]*/** wklx, + deny /sys/fs/cg[^r]*/** wklx, + deny /sys/firmware/efi/efivars/** rwklx, + deny /sys/kernel/security/** rwklx, +} +EOF + + apparmor_parser -Kr $aaFile + + #Expected to pass (as root) + podman run --security-opt apparmor=$aaProfile alpine echo hello + rc=$? + echo -n "root with specified AppArmor profile: " + if [ $rc == 0 ]; then + echo "passed" + else + echo "failed" + fi + + #Expected to fail (as rootless) + sudo -u "#1000" podman run --security-opt apparmor=$aaProfile alpine echo hello + rc=$? + echo -n "rootless with specified AppArmor profile: " + if [ $rc != 0 ]; then + echo "passed" + else + echo "failed" + fi + + ######## + # Clean up Podman and $aaFile + ######## + apparmor_parser -R $aaFile + podman rm --all + podman rmi --all + sudo -u "#1000" podman rm --all + sudo -u "#1000" podman rmi --all + rm -f $aaFile +fi |