Permalink
Browse files

interfaces/apparmor: add function for probing / mocking

This patch adds a new function for probing for supported apparmor
features as well as for mocking that. Unlike the current code in the
release module (where it doesn't really belong as it is no longer based
on looking at the /etc/os-release file).

The improvement from the old code is that the "feature level" is not a
boolean, there are three values and more may be added if desired. They
are "none" (apparmor not enabled at all), "partial" (apparmor enabled
but some features are missing) and "full" (all required features
availalble).

In the next few patches I will transition the old release interface over
to this and will start using it to make better decisions as to how
apparmor backend should be loaded and how it should operate.

Signed-off-by: Zygmunt Krynicki <me@zygoon.pl>
  • Loading branch information...
1 parent 34d5bd0 commit ef40971ea1da4110fa6474ccff634802236aebdd @zyga committed Aug 24, 2017
Showing with 147 additions and 0 deletions.
  1. +109 −0 interfaces/apparmor/probe.go
  2. +38 −0 interfaces/apparmor/probe_test.go
@@ -0,0 +1,109 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2017 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package apparmor
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+)
+
+// FeatureLevel encodes the kind of support for apparmor found on this system.
+type FeatureLevel int
+
+const (
+ // None indicates that apparmor is not enabled.
+ None FeatureLevel = iota
+ // Partial indicates that apparmor is enabled but some features are missing.
+ Partial
+ // Full indicates that all features are supported.
+ Full
+)
+
+var (
+ // featureSysPath points to the sysfs directory where apparmor features are listed.
+ featuresSysPath = "/sys/kernel/security/apparmor/features"
@jdstrand

jdstrand Aug 24, 2017

Elsewhere we use dirs/dirs.go for this sort of thing.

@jdstrand

jdstrand Aug 24, 2017

I think using dirs/dirs.go will make the mocking a little easier.

@zyga

zyga Aug 24, 2017

Owner

Ironically it's also a chore because of cyclic imports. I'm untangling that and I may end up being able to actually import dirs here as well. I'll try.

+ // requiredFeatures are the apparmor features needed for strict confinement.
+ requiredFeatures = []string{
+ "caps",
+ "dbus",
+ "domain",
+ "file",
+ "mount",
+ "namespaces",
+ "network",
+ "ptrace",
+ "signal",
@jdstrand

jdstrand Aug 24, 2017

While not currently used in our policy, we should maybe list rlimit here for completeness. rlimit is an old feature that was added in apparmor 2.3 (the same time the /sys/kernel/security/apparmor/features was added). I confirmed that Ubuntu 12.04 3.2 kernels have it and unpatched 4.9 Debian has it.

@zyga

zyga Aug 24, 2017

Owner

I'll add that! Thank you for noticing.

+ }
+)
+
+// Probe checks which apparmor features are available.
+func Probe() FeatureLevel {
+ _, err := os.Stat(featuresSysPath)
+ if err != nil {
+ return None
+ }
@jdstrand

jdstrand Aug 24, 2017

It is probably sufficient to say if the features directory isn't present, then to return None, but aa_is_enabled (http://bazaar.launchpad.net/~apparmor-dev/apparmor/master/view/head:/libraries/libapparmor/src/kernel.c#L106) is a little more robust and might be useful for logging why we are returning None. Note that aa-enabled is a small C program wrapper around aa_is_enabled, so calling it directly from the core snap might be best.

@zyga

zyga Aug 24, 2017

Owner

I've added logging (well, errors that can be logged). Looking at the referenced apparmor library code I can easily check for /sys/module/apparmor/parameters/enabled. I want to balance correctness and simplicity here and I'd rather not call into a C program to get the answer.

+ for _, feature := range requiredFeatures {
+ p := filepath.Join(featuresSysPath, feature)
+ if _, err := os.Stat(p); err != nil {
+ return Partial
+ }
+ }
+ return Full
@jdstrand

jdstrand Aug 24, 2017

It might be nice to return the 'why' here for future logging. Eg:

    return None, fmt.Errof("...")
    return Partial, fmt.Errof("missing x, y, z")
    return Full, nil
@zyga

zyga Aug 24, 2017

Owner

That's a good idea, I'll make that so

+}
+
+// MockFeatureLevel fakes the desired apparmor feature level.
+func MockFeatureLevel(level FeatureLevel) (restore func()) {
+ oldFeaturesSysPath := featuresSysPath
+
+ temp, err := ioutil.TempDir("", "mock-apparmor-feature-level")
+ if err != nil {
+ panic(err)
+ }
+ fakeFeaturesSysPath := filepath.Join(temp, "apparmor")
+
+ switch level {
+ case None:
+ // create no directory at all (apparmor not available).
+ break
+ case Partial:
+ // create just the empty directory with no features.
+ if err := os.MkdirAll(fakeFeaturesSysPath, 0755); err != nil {
+ panic(err)
+ }
+ break
+ case Full:
+ // create all the feature directories.
+ for _, feature := range requiredFeatures {
+ if err := os.MkdirAll(filepath.Join(fakeFeaturesSysPath, feature), 0755); err != nil {
+ panic(err)
+ }
+ }
+ break
+ }
+
+ featuresSysPath = fakeFeaturesSysPath
+ return func() {
+ if err := os.RemoveAll(temp); err != nil {
+ panic(err)
+ }
+ featuresSysPath = oldFeaturesSysPath
+ }
+}
@@ -0,0 +1,38 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2017 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package apparmor_test
+
+import (
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/interfaces/apparmor"
+)
+
+type probeSuite struct{}
+
+var _ = Suite(&probeSuite{})
+
+func (s *probeSuite) TestProbe(c *C) {
+ for _, l := range []apparmor.FeatureLevel{apparmor.None, apparmor.Partial, apparmor.Full} {
+ restore := apparmor.MockFeatureLevel(l)
+ defer restore()
+ c.Assert(apparmor.Probe(), Equals, l, Commentf("was hoping for %q", l))
+ }
+}

1 comment on commit ef40971

The direction of this commit is fine. Couple of small comments.

Please sign in to comment.