Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
Already on GitHub? Sign in to your account
apparmor,release: add better apparmor detection/mocking code #3808
ef40971
66e29b2
401fe35
d0b527c
ba9974b
3e7da29
c382b22
ee03ff0
f1f03bb
0f10b7d
534e497
6fc7559
apparmor,release: refactor apparmor probing / evaluation code
This patch is inspired by an idea from Michael Vogt. The probing code
now returns an apparmor.KernelSupport object which can be queried for
distinct facts: is apparmor enabled, is specific feature available.
The object can also be asked to evaluate overal support as required by
snapd. This last operation matches the previous model where a tri-state
answer is provided (None, Partial, Full) as well as a textual summary
with more human-readable information ("this-and-that feature is
missing").
This will also allow specific interfaces to behave appropriately in
light of presence or absence of specific features.
Signed-off-by: Zygmunt Krynicki <me@zygoon.pl>- Loading branch information...
| @@ -24,6 +24,7 @@ import ( | ||
| "io/ioutil" | ||
| "os" | ||
| "path/filepath" | ||
| + "sort" | ||
| "strings" | ||
| ) | ||
| @@ -57,24 +58,59 @@ var ( | ||
| } | ||
| ) | ||
| -// Probe checks which apparmor features are available. | ||
| -// | ||
| -// The error is returned whenever less-than-full support is detected. | ||
| -func Probe() (FeatureLevel, error) { | ||
| - if _, err := os.Stat(featuresSysPath); err != nil { | ||
| - return None, fmt.Errorf("apparmor feature directory not found: %s", err) | ||
| +// KernelSupport describes apparmor features supported by the kernel. | ||
| +type KernelSupport struct { | ||
| + enabled bool | ||
| + features map[string]bool | ||
| +} | ||
| + | ||
| +// ProbeKernel checks which apparmor features are available. | ||
| +func ProbeKernel() *KernelSupport { | ||
zyga
Contributor
|
||
| + entries, err := ioutil.ReadDir(featuresSysPath) | ||
| + if err != nil { | ||
mvo5
Collaborator
|
||
| + return nil | ||
| + } | ||
| + ks := &KernelSupport{ | ||
| + enabled: err == nil, | ||
| + features: make(map[string]bool, len(entries)), | ||
| + } | ||
| + for _, entry := range entries { | ||
| + // Each sub-directory represents a speicfic feature. Some have more | ||
| + // details as additional sub-directories or files therein but we are | ||
| + // not inspecting that at the moment. | ||
| + if entry.IsDir() { | ||
| + ks.features[entry.Name()] = true | ||
| + } | ||
| + } | ||
| + return ks | ||
| +} | ||
| + | ||
| +// IsEnabled returns true if apparmor is enabled. | ||
| +func (ks *KernelSupport) IsEnabled() bool { | ||
| + return ks != nil && ks.enabled | ||
mvo5
Collaborator
|
||
| +} | ||
| + | ||
| +// SupportsFeature returns true if a given apparmor feature is supported. | ||
| +func (ks *KernelSupport) SupportsFeature(feature string) bool { | ||
| + return ks != nil && ks.features[feature] | ||
|
|
||
| +} | ||
| + | ||
| +// Evaluate checks if the apparmor module is enabled and if all the required features are available. | ||
| +func (ks *KernelSupport) Evaluate() (level FeatureLevel, summary string) { | ||
| + if !ks.IsEnabled() { | ||
| + return None, fmt.Sprintf("apparmor is not enabled") | ||
| } | ||
| var missing []string | ||
| for _, feature := range requiredFeatures { | ||
| - p := filepath.Join(featuresSysPath, feature) | ||
| - if _, err := os.Stat(p); err != nil { | ||
| + if !ks.SupportsFeature(feature) { | ||
| missing = append(missing, feature) | ||
| } | ||
| } | ||
| if len(missing) > 0 { | ||
| - return Partial, fmt.Errorf("apparmor features missing: %s", strings.Join(missing, ", ")) | ||
| + sort.Strings(missing) | ||
| + return Partial, fmt.Sprintf("apparmor is enabled but some features are missing: %s", strings.Join(missing, ", ")) | ||
| } | ||
| - return Full, nil | ||
| + return Full, "apparmor is enabled and all features are available" | ||
| } | ||
| // MockFeatureLevel fakes the desired apparmor feature level. | ||
| @@ -91,12 +127,14 @@ func MockFeatureLevel(level FeatureLevel) (restore func()) { | ||
| case None: | ||
| // create no directory at all (apparmor not available). | ||
| case Partial: | ||
| - // create just the empty directory with no features. | ||
| - if err := os.MkdirAll(featuresSysPath, 0755); err != nil { | ||
| - panic(err) | ||
| + // create several feature directories, matching vanilla 4.12 kernel. | ||
| + for _, feature := range []string{"caps", "domain", "file", "network", "policy", "rlimit"} { | ||
| + if err := os.MkdirAll(filepath.Join(featuresSysPath, feature), 0755); err != nil { | ||
| + panic(err) | ||
| + } | ||
| } | ||
| case Full: | ||
| - // create all the feature directories. | ||
| + // create all the feature directories, matching Ubuntu kernels. | ||
| for _, feature := range requiredFeatures { | ||
| if err := os.MkdirAll(filepath.Join(featuresSysPath, feature), 0755); err != nil { | ||
| panic(err) | ||
| @@ -55,7 +55,7 @@ var ( | ||
| // ForceDevMode returns true if the distribution doesn't implement required | ||
| // security features for confinement and devmode is forced. | ||
| func (o *OS) ForceDevMode() bool { | ||
| - level, _ := apparmor.Probe() | ||
| + level, _ := apparmor.ProbeKernel().Evaluate() | ||
mvo5
Collaborator
|
||
| return level != apparmor.Full | ||
| } | ||
Why not just return the struct instead of a pointer to avoid the non-nil checks below?