summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/create.go122
-rwxr-xr-xtest/test_podman_baseline.sh74
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