package supported import ( "errors" "fmt" "os" "os/exec" "path/filepath" "sync" "github.com/containers/storage/pkg/unshare" runcaa "github.com/opencontainers/runc/libcontainer/apparmor" "github.com/sirupsen/logrus" ) //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate // ApparmorVerifier is the global struct for verifying if AppAmor is available // on the system. type ApparmorVerifier struct { impl verifierImpl parserBinaryPath string } var ( singleton *ApparmorVerifier once sync.Once ) // NewAppArmorVerifier can be used to retrieve a new ApparmorVerifier instance. func NewAppArmorVerifier() *ApparmorVerifier { once.Do(func() { singleton = &ApparmorVerifier{impl: &defaultVerifier{}} }) return singleton } // IsSupported returns nil if AppAmor is supported by the host system. // The method will error if: // - the process runs in rootless mode // - AppArmor is disabled by the host system // - the `apparmor_parser` binary is not discoverable func (a *ApparmorVerifier) IsSupported() error { if a.impl.UnshareIsRootless() { return errors.New("AppAmor is not supported on rootless containers") } if !a.impl.RuncIsEnabled() { return errors.New("AppArmor not supported by the host system") } _, err := a.FindAppArmorParserBinary() return err } // FindAppArmorParserBinary returns the `apparmor_parser` binary either from // `/sbin` or from `$PATH`. It returns an error if the binary could not be // found. func (a *ApparmorVerifier) FindAppArmorParserBinary() (string, error) { // Use the memoized path if available if a.parserBinaryPath != "" { logrus.Debugf("Using %s binary", a.parserBinaryPath) return a.parserBinaryPath, nil } const ( binary = "apparmor_parser" sbin = "/sbin" ) // `/sbin` is not always in `$PATH`, so we check it explicitly sbinBinaryPath := filepath.Join(sbin, binary) if _, err := a.impl.OsStat(sbinBinaryPath); err == nil { logrus.Debugf("Found %s binary in %s", binary, sbinBinaryPath) a.parserBinaryPath = sbinBinaryPath return sbinBinaryPath, nil } // Fallback to checking $PATH if path, err := a.impl.ExecLookPath(binary); err == nil { logrus.Debugf("Found %s binary in %s", binary, path) a.parserBinaryPath = path return path, nil } return "", fmt.Errorf( "%s binary neither found in %s nor $PATH", binary, sbin, ) } //counterfeiter:generate . verifierImpl type verifierImpl interface { UnshareIsRootless() bool RuncIsEnabled() bool OsStat(name string) (os.FileInfo, error) ExecLookPath(file string) (string, error) } type defaultVerifier struct{} func (d *defaultVerifier) UnshareIsRootless() bool { return unshare.IsRootless() } func (d *defaultVerifier) RuncIsEnabled() bool { return runcaa.IsEnabled() } func (d *defaultVerifier) OsStat(name string) (os.FileInfo, error) { return os.Stat(name) } func (d *defaultVerifier) ExecLookPath(file string) (string, error) { return exec.LookPath(file) }