mirror of https://github.com/containers/podman.git
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 <rothberg@redhat.com>
This commit is contained in:
parent
c37f731596
commit
edb285d176
|
@ -15,13 +15,11 @@ import (
|
||||||
"github.com/containers/libpod/libpod"
|
"github.com/containers/libpod/libpod"
|
||||||
"github.com/containers/libpod/libpod/image"
|
"github.com/containers/libpod/libpod/image"
|
||||||
ann "github.com/containers/libpod/pkg/annotations"
|
ann "github.com/containers/libpod/pkg/annotations"
|
||||||
"github.com/containers/libpod/pkg/apparmor"
|
|
||||||
"github.com/containers/libpod/pkg/inspect"
|
"github.com/containers/libpod/pkg/inspect"
|
||||||
ns "github.com/containers/libpod/pkg/namespaces"
|
ns "github.com/containers/libpod/pkg/namespaces"
|
||||||
"github.com/containers/libpod/pkg/rootless"
|
"github.com/containers/libpod/pkg/rootless"
|
||||||
cc "github.com/containers/libpod/pkg/spec"
|
cc "github.com/containers/libpod/pkg/spec"
|
||||||
"github.com/containers/libpod/pkg/util"
|
"github.com/containers/libpod/pkg/util"
|
||||||
libpodVersion "github.com/containers/libpod/version"
|
|
||||||
"github.com/docker/docker/pkg/signal"
|
"github.com/docker/docker/pkg/signal"
|
||||||
"github.com/docker/go-connections/nat"
|
"github.com/docker/go-connections/nat"
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
|
@ -162,83 +160,9 @@ func createContainer(c *cli.Context, runtime *libpod.Runtime) (*libpod.Container
|
||||||
return ctr, createConfig, nil
|
return ctr, createConfig, 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 {
|
func parseSecurityOpt(config *cc.CreateConfig, securityOpts []string) error {
|
||||||
var (
|
var (
|
||||||
labelOpts []string
|
labelOpts []string
|
||||||
err error
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if config.PidMode.IsHost() {
|
if config.PidMode.IsHost() {
|
||||||
|
@ -283,10 +207,6 @@ func parseSecurityOpt(config *cc.CreateConfig, securityOpts []string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := loadAppArmor(config); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.SeccompProfilePath == "" {
|
if config.SeccompProfilePath == "" {
|
||||||
if _, err := os.Stat(libpod.SeccompOverridePath); err == nil {
|
if _, err := os.Stat(libpod.SeccompOverridePath); err == nil {
|
||||||
config.SeccompProfilePath = libpod.SeccompOverridePath
|
config.SeccompProfilePath = libpod.SeccompOverridePath
|
||||||
|
@ -304,7 +224,7 @@ func parseSecurityOpt(config *cc.CreateConfig, securityOpts []string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
config.LabelOpts = labelOpts
|
config.LabelOpts = labelOpts
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// isPortInPortBindings determines if an exposed host port is in user
|
// isPortInPortBindings determines if an exposed host port is in user
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package libpod
|
package libpod
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -407,6 +409,30 @@ func (c *Container) Spec() *spec.Spec {
|
||||||
return returnSpec
|
return returnSpec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// specFromState returns the unmarshalled json config of the container. If the
|
||||||
|
// config does not exist (e.g., because the container was never started) return
|
||||||
|
// the spec from the config.
|
||||||
|
func (c *Container) specFromState() (*spec.Spec, error) {
|
||||||
|
spec := c.config.Spec
|
||||||
|
|
||||||
|
if f, err := os.Open(c.state.ConfigPath); err == nil {
|
||||||
|
content, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "error reading container config")
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal([]byte(content), &spec); err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "error unmarshalling container config")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ignore when the file does not exist
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return nil, errors.Wrapf(err, "error opening container config")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return spec, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ID returns the container's ID
|
// ID returns the container's ID
|
||||||
func (c *Container) ID() string {
|
func (c *Container) ID() string {
|
||||||
return c.config.ID
|
return c.config.ID
|
||||||
|
|
|
@ -12,7 +12,10 @@ import (
|
||||||
func (c *Container) getContainerInspectData(size bool, driverData *inspect.Data) (*inspect.ContainerInspectData, error) {
|
func (c *Container) getContainerInspectData(size bool, driverData *inspect.Data) (*inspect.ContainerInspectData, error) {
|
||||||
config := c.config
|
config := c.config
|
||||||
runtimeInfo := c.state
|
runtimeInfo := c.state
|
||||||
spec := c.config.Spec
|
spec, err := c.specFromState()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// Process is allowed to be nil in the spec
|
// Process is allowed to be nil in the spec
|
||||||
args := []string{}
|
args := []string{}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
cnitypes "github.com/containernetworking/cni/pkg/types/current"
|
cnitypes "github.com/containernetworking/cni/pkg/types/current"
|
||||||
"github.com/containernetworking/plugins/pkg/ns"
|
"github.com/containernetworking/plugins/pkg/ns"
|
||||||
crioAnnotations "github.com/containers/libpod/pkg/annotations"
|
crioAnnotations "github.com/containers/libpod/pkg/annotations"
|
||||||
|
"github.com/containers/libpod/pkg/apparmor"
|
||||||
"github.com/containers/libpod/pkg/criu"
|
"github.com/containers/libpod/pkg/criu"
|
||||||
"github.com/containers/libpod/pkg/lookup"
|
"github.com/containers/libpod/pkg/lookup"
|
||||||
"github.com/containers/libpod/pkg/resolvconf"
|
"github.com/containers/libpod/pkg/resolvconf"
|
||||||
|
@ -185,6 +186,13 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply AppArmor checks and load the default profile if needed.
|
||||||
|
updatedProfile, err := apparmor.CheckProfileAndLoadDefault(c.config.Spec.Process.ApparmorProfile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
g.SetProcessApparmorProfile(updatedProfile)
|
||||||
|
|
||||||
if err := c.makeBindMounts(); err != nil {
|
if err := c.makeBindMounts(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,16 @@ package apparmor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
libpodVersion "github.com/containers/libpod/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
// DefaultLipodProfilePrefix is used for version-independent presence checks.
|
||||||
|
DefaultLipodProfilePrefix = "libpod-default" + "-"
|
||||||
// DefaultLibpodProfile is the name of default libpod AppArmor profile.
|
// 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 indicates that AppArmor support is not supported.
|
||||||
ErrApparmorUnsupported = errors.New("AppArmor 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")
|
||||||
)
|
)
|
||||||
|
|
|
@ -13,7 +13,10 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/containers/libpod/pkg/rootless"
|
||||||
runcaa "github.com/opencontainers/runc/libcontainer/apparmor"
|
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.
|
// 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.
|
// IsEnabled returns true if AppArmor is enabled on the host.
|
||||||
func IsEnabled() bool {
|
func IsEnabled() bool {
|
||||||
|
if rootless.IsRootless() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return runcaa.IsEnabled()
|
return runcaa.IsEnabled()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,6 +77,10 @@ func macroExists(m string) bool {
|
||||||
// InstallDefault generates a default profile and loads it into the kernel
|
// InstallDefault generates a default profile and loads it into the kernel
|
||||||
// using 'apparmor_parser'.
|
// using 'apparmor_parser'.
|
||||||
func InstallDefault(name string) error {
|
func InstallDefault(name string) error {
|
||||||
|
if rootless.IsRootless() {
|
||||||
|
return ErrApparmorRootless
|
||||||
|
}
|
||||||
|
|
||||||
p := profileData{
|
p := profileData{
|
||||||
Name: name,
|
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
|
// IsLoaded checks if a profile with the given name has been loaded into the
|
||||||
// kernel.
|
// kernel.
|
||||||
func IsLoaded(name string) (bool, error) {
|
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")
|
file, err := os.Open("/sys/kernel/security/apparmor/profiles")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
|
@ -188,3 +202,55 @@ func parseAAParserVersion(output string) (int, error) {
|
||||||
return numericVersion, nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -2,19 +2,25 @@
|
||||||
|
|
||||||
package apparmor
|
package apparmor
|
||||||
|
|
||||||
// IsEnabled returns true if AppArmor is enabled on the host.
|
// IsEnabled dummy.
|
||||||
func IsEnabled() bool {
|
func IsEnabled() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// InstallDefault generates a default profile in a temp directory determined by
|
// InstallDefault dummy.
|
||||||
// os.TempDir(), then loads the profile into the kernel using 'apparmor_parser'.
|
|
||||||
func InstallDefault(name string) error {
|
func InstallDefault(name string) error {
|
||||||
return ErrApparmorUnsupported
|
return ErrApparmorUnsupported
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsLoaded checks if a profile with the given name has been loaded into the
|
// IsLoaded dummy.
|
||||||
// kernel.
|
|
||||||
func IsLoaded(name string) (bool, error) {
|
func IsLoaded(name string) (bool, error) {
|
||||||
return false, ErrApparmorUnsupported
|
return false, ErrApparmorUnsupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckProfileAndLoadDefault dummy.
|
||||||
|
func CheckProfileAndLoadDefault(name string) (string, error) {
|
||||||
|
if name == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return "", ErrApparmorUnsupported
|
||||||
|
}
|
||||||
|
|
|
@ -252,6 +252,7 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint
|
||||||
}
|
}
|
||||||
// SECURITY OPTS
|
// SECURITY OPTS
|
||||||
g.SetProcessNoNewPrivileges(config.NoNewPrivs)
|
g.SetProcessNoNewPrivileges(config.NoNewPrivs)
|
||||||
|
|
||||||
g.SetProcessApparmorProfile(config.ApparmorProfile)
|
g.SetProcessApparmorProfile(config.ApparmorProfile)
|
||||||
|
|
||||||
blockAccessToKernelFilesystems(config, &g)
|
blockAccessToKernelFilesystems(config, &g)
|
||||||
|
|
|
@ -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"
|
|
||||||
}
|
|
Loading…
Reference in New Issue