diff --git a/cmd/podman/create.go b/cmd/podman/create.go index 3429c4d976..95b7a8bed2 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,6 +149,79 @@ func createCmd(c *cli.Context) error { return nil } +// 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() { + // Unless specified otherwise, make sure that the default AppArmor + // profile is installed. To avoid redundantly loading the profile + // on each invocation, check if it's loaded before installing it. + // Suffix the profile with the current libpod version to allow + // loading the new, potentially updated profile after an update. + profile := fmt.Sprintf("%s-%s", apparmor.DefaultLibpodProfile, libpodVersion.Version) + + loadProfile := func() error { + isLoaded, err := apparmor.IsLoaded(profile) + if err != nil { + return err + } + if !isLoaded { + err = apparmor.InstallDefault(profile) + if err != nil { + return err + } + + } + return nil + } + + if err := loadProfile(); err != nil { + switch err { + case apparmor.ErrApparmorUnsupported: + // do not set the profile when AppArmor isn't supported + logrus.Debugf("AppArmor is not supported: setting empty profile") + default: + return err + } + } else { + logrus.Infof("Sucessfully loaded AppAmor profile '%s'", profile) + config.ApparmorProfile = profile + } + } else if config.ApparmorProfile != "" && config.ApparmorProfile != "unconfined" { + if !apparmor.IsEnabled() { + 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") + default: + return fmt.Errorf("Error checking if AppArmor profile is loaded: %v", err) + } + } + if !isLoaded { + 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 @@ -196,58 +270,8 @@ func parseSecurityOpt(config *cc.CreateConfig, securityOpts []string) error { } } - if config.ApparmorProfile == "" && apparmor.IsEnabled() { - // Unless specified otherwise, make sure that the default AppArmor - // profile is installed. To avoid redundantly loading the profile - // on each invocation, check if it's loaded before installing it. - // Suffix the profile with the current libpod version to allow - // loading the new, potentially updated profile after an update. - profile := fmt.Sprintf("%s-%s", apparmor.DefaultLibpodProfile, libpodVersion.Version) - - loadProfile := func() error { - isLoaded, err := apparmor.IsLoaded(profile) - if err != nil { - return err - } - if !isLoaded { - err = apparmor.InstallDefault(profile) - if err != nil { - return err - } - - } - return nil - } - - if err := loadProfile(); err != nil { - switch err { - case apparmor.ErrApparmorUnsupported: - // do not set the profile when AppArmor isn't supported - logrus.Debugf("AppArmor is not supported: setting empty profile") - default: - return err - } - } else { - logrus.Infof("Sucessfully loaded AppAmor profile '%s'", profile) - config.ApparmorProfile = profile - } - } else if config.ApparmorProfile != "" && config.ApparmorProfile != "unconfined" { - if !apparmor.IsEnabled() { - 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") - default: - 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) - } + if err := loadAppArmor(config); err != nil { + return err } if config.SeccompProfilePath == "" { diff --git a/test/test_podman_baseline.sh b/test/test_podman_baseline.sh index a9ade8c7b4..74a4398cac 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 +profile aa-demo-profile flags=(attach_disconnected,mediate_deleted) { + #include + 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