From edb285d17675061832aceaf72021b87aba149438 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Wed, 9 Jan 2019 14:54:58 +0100 Subject: apparmor: apply default profile at container initialization Apply the default AppArmor profile at container initialization to cover all possible code paths (i.e., podman-{start,run}) before executing the runtime. This allows moving most of the logic into pkg/apparmor. Also make the loading and application of the default AppArmor profile versio-indepenent by checking for the `libpod-default-` prefix and over-writing the profile in the run-time spec if needed. The intitial run-time spec of the container differs a bit from the applied one when having started the container, which results in displaying a potentially outdated AppArmor profile when inspecting a container. To fix that, load the container config from the file system if present and use it to display the data. Fixes: #2107 Signed-off-by: Valentin Rothberg --- pkg/apparmor/apparmor.go | 7 +- pkg/apparmor/apparmor_linux.go | 66 +++++++++ pkg/apparmor/apparmor_unsupported.go | 16 ++- pkg/spec/spec.go | 1 + pkg/sysinfo/sysinfo_linux.go~ | 254 ----------------------------------- 5 files changed, 84 insertions(+), 260 deletions(-) delete mode 100644 pkg/sysinfo/sysinfo_linux.go~ (limited to 'pkg') diff --git a/pkg/apparmor/apparmor.go b/pkg/apparmor/apparmor.go index 8b9f99477..45c029c07 100644 --- a/pkg/apparmor/apparmor.go +++ b/pkg/apparmor/apparmor.go @@ -2,11 +2,16 @@ package apparmor import ( "errors" + libpodVersion "github.com/containers/libpod/version" ) var ( + // DefaultLipodProfilePrefix is used for version-independent presence checks. + DefaultLipodProfilePrefix = "libpod-default" + "-" // DefaultLibpodProfile is the name of default libpod AppArmor profile. - DefaultLibpodProfile = "libpod-default" + DefaultLibpodProfile = DefaultLipodProfilePrefix + libpodVersion.Version // ErrApparmorUnsupported indicates that AppArmor support is not supported. ErrApparmorUnsupported = errors.New("AppArmor is not supported") + // ErrApparmorRootless indicates that AppArmor support is not supported in rootless mode. + ErrApparmorRootless = errors.New("AppArmor is not supported in rootless mode") ) diff --git a/pkg/apparmor/apparmor_linux.go b/pkg/apparmor/apparmor_linux.go index b0e3ca0fd..0787b3fa5 100644 --- a/pkg/apparmor/apparmor_linux.go +++ b/pkg/apparmor/apparmor_linux.go @@ -13,7 +13,10 @@ import ( "strings" "text/template" + "github.com/containers/libpod/pkg/rootless" runcaa "github.com/opencontainers/runc/libcontainer/apparmor" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) // profileDirectory is the file store for apparmor profiles and macros. @@ -21,6 +24,9 @@ var profileDirectory = "/etc/apparmor.d" // IsEnabled returns true if AppArmor is enabled on the host. func IsEnabled() bool { + if rootless.IsRootless() { + return false + } return runcaa.IsEnabled() } @@ -71,6 +77,10 @@ func macroExists(m string) bool { // InstallDefault generates a default profile and loads it into the kernel // using 'apparmor_parser'. func InstallDefault(name string) error { + if rootless.IsRootless() { + return ErrApparmorRootless + } + p := profileData{ Name: name, } @@ -97,6 +107,10 @@ func InstallDefault(name string) error { // IsLoaded checks if a profile with the given name has been loaded into the // kernel. func IsLoaded(name string) (bool, error) { + if name != "" && rootless.IsRootless() { + return false, errors.Wrapf(ErrApparmorRootless, "cannot load AppArmor profile %q", name) + } + file, err := os.Open("/sys/kernel/security/apparmor/profiles") if err != nil { if os.IsNotExist(err) { @@ -188,3 +202,55 @@ func parseAAParserVersion(output string) (int, error) { return numericVersion, nil } + +// CheckProfileAndLoadDefault checks if the specified profile is loaded and +// loads the DefaultLibpodProfile if the specified on is prefixed by +// DefaultLipodProfilePrefix. This allows to always load and apply the latest +// default AppArmor profile. Note that AppArmor requires root. If it's a +// default profile, return DefaultLipodProfilePrefix, otherwise the specified +// one. +func CheckProfileAndLoadDefault(name string) (string, error) { + if name == "unconfined" { + return name, nil + } + + if name != "" && rootless.IsRootless() { + return "", errors.Wrapf(ErrApparmorRootless, "cannot load AppArmor profile %q", name) + } + + if name != "" && !runcaa.IsEnabled() { + return "", fmt.Errorf("profile %q specified but AppArmor is disabled on the host", name) + } + + // If the specified name is not empty or is not a default libpod one, + // ignore it and return the name. + if name != "" && !strings.HasPrefix(name, DefaultLipodProfilePrefix) { + isLoaded, err := IsLoaded(name) + if err != nil { + return "", err + } + if !isLoaded { + return "", fmt.Errorf("AppArmor profile %q specified but not loaded") + } + return name, nil + } + + name = DefaultLibpodProfile + // To avoid expensive redundant loads on each invocation, check + // if it's loaded before installing it. + isLoaded, err := IsLoaded(name) + if err != nil { + return "", err + } + if !isLoaded { + err = InstallDefault(name) + if err != nil { + return "", err + } + logrus.Infof("successfully loaded AppAmor profile %q", name) + } else { + logrus.Infof("AppAmor profile %q is already loaded", name) + } + + return name, nil +} diff --git a/pkg/apparmor/apparmor_unsupported.go b/pkg/apparmor/apparmor_unsupported.go index df1336b07..b2b4de5f5 100644 --- a/pkg/apparmor/apparmor_unsupported.go +++ b/pkg/apparmor/apparmor_unsupported.go @@ -2,19 +2,25 @@ package apparmor -// IsEnabled returns true if AppArmor is enabled on the host. +// IsEnabled dummy. func IsEnabled() bool { return false } -// InstallDefault generates a default profile in a temp directory determined by -// os.TempDir(), then loads the profile into the kernel using 'apparmor_parser'. +// InstallDefault dummy. func InstallDefault(name string) error { return ErrApparmorUnsupported } -// IsLoaded checks if a profile with the given name has been loaded into the -// kernel. +// IsLoaded dummy. func IsLoaded(name string) (bool, error) { return false, ErrApparmorUnsupported } + +// CheckProfileAndLoadDefault dummy. +func CheckProfileAndLoadDefault(name string) (string, error) { + if name == "" { + return "", nil + } + return "", ErrApparmorUnsupported +} diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index ffa999730..9ef0223f2 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -252,6 +252,7 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint } // SECURITY OPTS g.SetProcessNoNewPrivileges(config.NoNewPrivs) + g.SetProcessApparmorProfile(config.ApparmorProfile) blockAccessToKernelFilesystems(config, &g) diff --git a/pkg/sysinfo/sysinfo_linux.go~ b/pkg/sysinfo/sysinfo_linux.go~ deleted file mode 100644 index 471f786a7..000000000 --- a/pkg/sysinfo/sysinfo_linux.go~ +++ /dev/null @@ -1,254 +0,0 @@ -package sysinfo - -import ( - "fmt" - "io/ioutil" - "os" - "path" - "strings" - - "github.com/opencontainers/runc/libcontainer/cgroups" - "github.com/sirupsen/logrus" - "golang.org/x/sys/unix" -) - -func findCgroupMountpoints() (map[string]string, error) { - cgMounts, err := cgroups.GetCgroupMounts(false) - if err != nil { - return nil, fmt.Errorf("Failed to parse cgroup information: %v", err) - } - mps := make(map[string]string) - for _, m := range cgMounts { - for _, ss := range m.Subsystems { - mps[ss] = m.Mountpoint - } - } - return mps, nil -} - -// New returns a new SysInfo, using the filesystem to detect which features -// the kernel supports. If `quiet` is `false` warnings are printed in logs -// whenever an error occurs or misconfigurations are present. -func New(quiet bool) *SysInfo { - sysInfo := &SysInfo{} - cgMounts, err := findCgroupMountpoints() - if err != nil { - logrus.Warnf("Failed to parse cgroup information: %v", err) - } else { - sysInfo.cgroupMemInfo = checkCgroupMem(cgMounts, quiet) - sysInfo.cgroupCPUInfo = checkCgroupCPU(cgMounts, quiet) - sysInfo.cgroupBlkioInfo = checkCgroupBlkioInfo(cgMounts, quiet) - sysInfo.cgroupCpusetInfo = checkCgroupCpusetInfo(cgMounts, quiet) - sysInfo.cgroupPids = checkCgroupPids(quiet) - } - - _, ok := cgMounts["devices"] - sysInfo.CgroupDevicesEnabled = ok - - sysInfo.IPv4ForwardingDisabled = !readProcBool("/proc/sys/net/ipv4/ip_forward") - sysInfo.BridgeNFCallIPTablesDisabled = !readProcBool("/proc/sys/net/bridge/bridge-nf-call-iptables") - sysInfo.BridgeNFCallIP6TablesDisabled = !readProcBool("/proc/sys/net/bridge/bridge-nf-call-ip6tables") - - // Check if AppArmor is supported. - if _, err := os.Stat("/sys/kernel/security/apparmor"); !os.IsNotExist(err) { - sysInfo.AppArmor = true - } - - // Check if Seccomp is supported, via CONFIG_SECCOMP. - if err := unix.Prctl(unix.PR_GET_SECCOMP, 0, 0, 0, 0); err != unix.EINVAL { - // Make sure the kernel has CONFIG_SECCOMP_FILTER. - if err := unix.Prctl(unix.PR_SET_SECCOMP, unix.SECCOMP_MODE_FILTER, 0, 0, 0); err != unix.EINVAL { - sysInfo.Seccomp = true - } - } - - return sysInfo -} - -// checkCgroupMem reads the memory information from the memory cgroup mount point. -func checkCgroupMem(cgMounts map[string]string, quiet bool) cgroupMemInfo { - mountPoint, ok := cgMounts["memory"] - if !ok { - if !quiet { - logrus.Warn("Your kernel does not support cgroup memory limit") - } - return cgroupMemInfo{} - } - - swapLimit := cgroupEnabled(mountPoint, "memory.memsw.limit_in_bytes") - if !quiet && !swapLimit { - logrus.Warn("Your kernel does not support swap memory limit") - } - memoryReservation := cgroupEnabled(mountPoint, "memory.soft_limit_in_bytes") - if !quiet && !memoryReservation { - logrus.Warn("Your kernel does not support memory reservation") - } - oomKillDisable := cgroupEnabled(mountPoint, "memory.oom_control") - if !quiet && !oomKillDisable { - logrus.Warn("Your kernel does not support oom control") - } - memorySwappiness := cgroupEnabled(mountPoint, "memory.swappiness") - if !quiet && !memorySwappiness { - logrus.Warn("Your kernel does not support memory swappiness") - } - kernelMemory := cgroupEnabled(mountPoint, "memory.kmem.limit_in_bytes") - if !quiet && !kernelMemory { - logrus.Warn("Your kernel does not support kernel memory limit") - } - - return cgroupMemInfo{ - MemoryLimit: true, - SwapLimit: swapLimit, - MemoryReservation: memoryReservation, - OomKillDisable: oomKillDisable, - MemorySwappiness: memorySwappiness, - KernelMemory: kernelMemory, - } -} - -// checkCgroupCPU reads the cpu information from the cpu cgroup mount point. -func checkCgroupCPU(cgMounts map[string]string, quiet bool) cgroupCPUInfo { - mountPoint, ok := cgMounts["cpu"] - if !ok { - if !quiet { - logrus.Warn("Unable to find cpu cgroup in mounts") - } - return cgroupCPUInfo{} - } - - cpuShares := cgroupEnabled(mountPoint, "cpu.shares") - if !quiet && !cpuShares { - logrus.Warn("Your kernel does not support cgroup cpu shares") - } - - cpuCfsPeriod := cgroupEnabled(mountPoint, "cpu.cfs_period_us") - if !quiet && !cpuCfsPeriod { - logrus.Warn("Your kernel does not support cgroup cfs period") - } - - cpuCfsQuota := cgroupEnabled(mountPoint, "cpu.cfs_quota_us") - if !quiet && !cpuCfsQuota { - logrus.Warn("Your kernel does not support cgroup cfs quotas") - } - - cpuRealtimePeriod := cgroupEnabled(mountPoint, "cpu.rt_period_us") - if !quiet && !cpuRealtimePeriod { - logrus.Warn("Your kernel does not support cgroup rt period") - } - - cpuRealtimeRuntime := cgroupEnabled(mountPoint, "cpu.rt_runtime_us") - if !quiet && !cpuRealtimeRuntime { - logrus.Warn("Your kernel does not support cgroup rt runtime") - } - - return cgroupCPUInfo{ - CPUShares: cpuShares, - CPUCfsPeriod: cpuCfsPeriod, - CPUCfsQuota: cpuCfsQuota, - CPURealtimePeriod: cpuRealtimePeriod, - CPURealtimeRuntime: cpuRealtimeRuntime, - } -} - -// checkCgroupBlkioInfo reads the blkio information from the blkio cgroup mount point. -func checkCgroupBlkioInfo(cgMounts map[string]string, quiet bool) cgroupBlkioInfo { - mountPoint, ok := cgMounts["blkio"] - if !ok { - if !quiet { - logrus.Warn("Unable to find blkio cgroup in mounts") - } - return cgroupBlkioInfo{} - } - - weight := cgroupEnabled(mountPoint, "blkio.weight") - if !quiet && !weight { - logrus.Warn("Your kernel does not support cgroup blkio weight") - } - - weightDevice := cgroupEnabled(mountPoint, "blkio.weight_device") - if !quiet && !weightDevice { - logrus.Warn("Your kernel does not support cgroup blkio weight_device") - } - - readBpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.read_bps_device") - if !quiet && !readBpsDevice { - logrus.Warn("Your kernel does not support cgroup blkio throttle.read_bps_device") - } - - writeBpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.write_bps_device") - if !quiet && !writeBpsDevice { - logrus.Warn("Your kernel does not support cgroup blkio throttle.write_bps_device") - } - readIOpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.read_iops_device") - if !quiet && !readIOpsDevice { - logrus.Warn("Your kernel does not support cgroup blkio throttle.read_iops_device") - } - - writeIOpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.write_iops_device") - if !quiet && !writeIOpsDevice { - logrus.Warn("Your kernel does not support cgroup blkio throttle.write_iops_device") - } - return cgroupBlkioInfo{ - BlkioWeight: weight, - BlkioWeightDevice: weightDevice, - BlkioReadBpsDevice: readBpsDevice, - BlkioWriteBpsDevice: writeBpsDevice, - BlkioReadIOpsDevice: readIOpsDevice, - BlkioWriteIOpsDevice: writeIOpsDevice, - } -} - -// checkCgroupCpusetInfo reads the cpuset information from the cpuset cgroup mount point. -func checkCgroupCpusetInfo(cgMounts map[string]string, quiet bool) cgroupCpusetInfo { - mountPoint, ok := cgMounts["cpuset"] - if !ok { - if !quiet { - logrus.Warn("Unable to find cpuset cgroup in mounts") - } - return cgroupCpusetInfo{} - } - - cpus, err := ioutil.ReadFile(path.Join(mountPoint, "cpuset.cpus")) - if err != nil { - return cgroupCpusetInfo{} - } - - mems, err := ioutil.ReadFile(path.Join(mountPoint, "cpuset.mems")) - if err != nil { - return cgroupCpusetInfo{} - } - - return cgroupCpusetInfo{ - Cpuset: true, - Cpus: strings.TrimSpace(string(cpus)), - Mems: strings.TrimSpace(string(mems)), - } -} - -// checkCgroupPids reads the pids information from the pids cgroup mount point. -func checkCgroupPids(quiet bool) cgroupPids { - _, err := cgroups.FindCgroupMountpoint("pids") - if err != nil { - if !quiet { - logrus.Warn(err) - } - return cgroupPids{} - } - - return cgroupPids{ - PidsLimit: true, - } -} - -func cgroupEnabled(mountPoint, name string) bool { - _, err := os.Stat(path.Join(mountPoint, name)) - return err == nil -} - -func readProcBool(path string) bool { - val, err := ioutil.ReadFile(path) - if err != nil { - return false - } - return strings.TrimSpace(string(val)) == "1" -} -- cgit v1.2.3-54-g00ecf