interfaces: enable partial apparmor support #3814

Merged
merged 15 commits into from Sep 19, 2017

Conversation

Projects
None yet
6 participants
Contributor

zyga commented Aug 28, 2017

This branch is inspired by an idea from @jdstrand to allow working with
mainline apparmor support by using the classic template (which gives broad
permissions) on such systems.

This has three direct consequences:

First, snap-confine can now be built with apparmor support on Debian and
OpenSUSE and those systems will gracefully degrade to devmode before all
apparmor features are available but will work with strict confinement as soon
as a compatible kernel is available.

The second advantage is that we may expand this concept to allow specific
interfaces to offer specific snippets if a given apparmor feature is present.
This would allow to create a level of confinement above devmode. Where, for
example, file rules are enforced on a wide number of systems. Certain
interfaces (e.g. those controlling IPC) would necessarily be more open than we
would like but it would improve the effective level of the sandbox for many
people.

Lastly this change will allow us to enable re-exec support on openSUSE as
snap-confine built from the core snap will now be compatible.

All those improvements will be proposed separately for consideration.

Signed-off-by: Zygmunt Krynicki me@zygoon.pl

zyga added some commits Aug 25, 2017

interfaces/apparmor: use classic template for partial apparmor
Whenever apparmor is missing some features we can still generate
permissive profiles and load them into the kernel. This has the advantage
of allowing snap-confine to work unmodified across wide range of kernels
those without any apparmor, those with partial apparmor and those with
full apparmor feature set.

Signed-off-by: Zygmunt Krynicki <me@zygoon.pl>
interfaces/backends: enable apparmor backend on parital feature set
AppArmor may be fully or only partially supported on a given system. In
the past when partially supported apparmor was detected then the backend
would not be loaded, leading to very confusing and error-prone behavior
for people switching between mainline (unpatched) and ubuntu (patched)
kernels. Because the backend was not loaded then now-stale profiles were
*not* updated or removed and things behaved oddly, where incorrect
profiles were actually loaded into the kernel (apparmor tries to degrade
gracefully but the profiles were not permissive enough).

With this patch the apparmor backend is loaded as long as the kernel
module is loaded and enabled. This will be coupled with a patch to the
backend, which will generate more permissive profiles for
anything-less-than-full feature set. This patch will allow snapd to
enable apparmor on Debian, openSUSE as well as Ubuntu running pre-4.15
mainline kernels.

Signed-off-by: Zygmunt Krynicki <me@zygoon.pl>

@zyga zyga requested a review from jdstrand Aug 28, 2017

codecov-io commented Aug 28, 2017

Codecov Report

Merging #3814 into master will increase coverage by 0.06%.
The diff coverage is 88.23%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #3814      +/-   ##
==========================================
+ Coverage   75.86%   75.92%   +0.06%     
==========================================
  Files         403      417      +14     
  Lines       34835    36025    +1190     
==========================================
+ Hits        26427    27352     +925     
- Misses       6535     6755     +220     
- Partials     1873     1918      +45
Impacted Files Coverage Δ
interfaces/backends/backends.go 100% <100%> (ø)
apparmor/probe.go 86.44% <100%> (ø) ⬆️
interfaces/apparmor/backend.go 72.04% <100%> (+0.53%) ⬆️
release/release.go 87.83% <63.63%> (-12.17%) ⬇️
httputil/logger.go 81.48% <0%> (-7.65%) ⬇️
overlord/configstate/config/transaction.go 80.4% <0%> (-4.78%) ⬇️
asserts/sysdb/generic.go 73.33% <0%> (-3.94%) ⬇️
corecfg/corecfg.go 50% <0%> (-1.62%) ⬇️
overlord/snapstate/check_snap.go 79.18% <0%> (-1.53%) ⬇️
interfaces/builtin/i2c.go 65.95% <0%> (-1.39%) ⬇️
... and 80 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3aa2420...0face42. Read the comment docs.

interfaces/apparmor/backend.go
+ // When apparmor partial then use the classic template as we cannot enable
+ // effective confinement and we cannot rely on the devmode template which
+ // may have things that local apparmor doesn't understand.
+ level, _ := aa.ProbeKernel().Evaluate()
@mvo5

mvo5 Aug 28, 2017

Collaborator

Why are we using aa.ProbeKernel().Evaluate()" instead of just "aa.Evaluate()" and hidding the "ProbeKernel()" as an implementation detail in aa ?

@zyga

zyga Aug 28, 2017

Contributor

Because later on we'll get the result of ProbeKernel from the backend itself and specific interfaces will be able to get it from there.

Contributor

mwhudson commented Aug 28, 2017

This looks sane from my outside perspective. Didn't Jamie ask for loud logging when the wide open policy is being used? I don't see that.

Also, once this is done, I think snap-confine can lose the --disable-apparmor option entirely (in general, because we want snap-confine to be usable from the core snap on all systems, it can't really have compile time options!).

This looks fine-- looking forward to followup PRs since I'd prefer we not overuse the classic template for forced devmode. I do wonder why we just don't go to straight strict mode here-- IME, all partial is really important for in comparison with strict is logging that certain things aren't mediated.

interfaces/apparmor/backend.go
- if opts.Classic && !opts.JailMode {
+ // When apparmor partial then use the classic template as we cannot enable
+ // effective confinement and we cannot rely on the devmode template which
+ // may have things that local apparmor doesn't understand.
@jdstrand

jdstrand Aug 29, 2017

Contributor

This comment isn't quite right. Note that classicTemplate has bare rules for all the modern rules so an old parser will always choke on them. This means that partial confinement must have new tools, and those new tools know how to work with all kernels. As such, this should read as:

// When partial AppArmor is detected, use the classic template for now. We could
// use devmode, but that could generate confusing log entries for users running
// snaps on systems with partial AppArmor support.
@zyga

zyga Aug 30, 2017

Contributor

+1, thank you.

interfaces/backends/backends.go
+
+ // This should be logger.Noticef but due to ordering of initialization
+ // calls, the logger is not ready at this point yet and the message goes
+ // nowehre. Per advice from other snapd developers, we just print it
@jdstrand

jdstrand Aug 29, 2017

Contributor

Typo 'nowehre'

@zyga

zyga Aug 30, 2017

Contributor

Thanks :-) I need to sync my vimrc and :set spell there :)

interfaces/backends/backends.go
+ // This should be logger.Noticef but due to ordering of initialization
+ // calls, the logger is not ready at this point yet and the message goes
+ // nowehre. Per advice from other snapd developers, we just print it
+ // directly.
@jdstrand

jdstrand Aug 29, 2017

Contributor

By printing directly, this means that it will end up in journalctl, which is important since we want syslog to show this as well. Please mention that here as part of your comment.

@zyga

zyga Aug 30, 2017

Contributor

Will do!

Contributor

zyga commented Aug 30, 2017

@mwhudson the --disable-apparmor option must stay for some time since apparmor is not available as a library in Fedora (last time I checked). As for the "loud logging" that's the Printf there, in the future I will switch that to the warning framework where casual users will actually see it (not just the people that ... casually read their syslog).

zyga added some commits Aug 30, 2017

interfaces/apparmor: use comment suggested by jdstrand
Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
interfaces/backends: fix typo
Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
interfaces/backends: explain importance of printing to stdout
Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
Contributor

zyga commented Aug 30, 2017

Updated per review feedback. Would love to see this in 2.28 if we can pull it off @mvo5 :-)

@zyga zyga added this to the 2.28 milestone Aug 30, 2017

Thanks for the updates!

When will you be implementing the change for using strict mode with logging instead of just using the classic template?

Contributor

zyga commented Aug 30, 2017

Soon, as time allows :)

This is going in a nice direction, thanks for bringing that up. That said, let's please talk through that API, and the logging also needs fixing.

interfaces/apparmor/backend.go
+ // When partial AppArmor is detected, use the classic template for now. We could
+ // use devmode, but that could generate confusing log entries for users running
+ // snaps on systems with partial AppArmor support.
+ level, _ := aa.ProbeKernel().Evaluate()
@niemeyer

niemeyer Aug 30, 2017

Contributor

Agree with Michael: this interface is too complicated for how trivial this is. What is the expected result of probing for a kernel and what does it mean to Evaluate it?

How about: aa.SupportLevel() == aa.PartialSupport?

The fact we need to compute and cache should remain inside the apparmor package itself. The release package may help with some ideas on that regard.

Also, what would "partial" mean in this case, and why do we expect what is inside classic to work while what is in strict won't? It would be better to be more clear about what is supported on not, and if possible have that gradient explicit in the terminology.

As a side note, Michael is extremely polite.. when he raises something on my code or ideas, I think three times before dismissing it.

@niemeyer

niemeyer Aug 30, 2017

Contributor

In fact, that's all the apparmor package does right now. This would be better as release.ApparmorSupport instead, I think.

@zyga

zyga Aug 31, 2017

Contributor

Please bare with me, the result of ProbeKernel will be stored in the apparmor backend. From there we will call Evaluate in one spot and access available features in several spots. I can bring this patch earlier. Partial means, as it is documented, that apparmor is enabled but some of the required features are missing. As discussed with jdstrand classic is used because it works on every release of apparmor and allows us to take the first step. The next step will be to switch to the actual strict template and teach specific interfaces about any extra deficiencies or incompatibilities that resulted from how apparmor enforcement works when a particular feature is present or absent. We already have the gradient you are talking about - we can ask, through SupportsFeature if a given feature is enabled or not.

@niemeyer

niemeyer Sep 1, 2017

Contributor

@zyga I understood this proposal from your earlier comments. My points above already take it into account.

@niemeyer

niemeyer Sep 1, 2017

Contributor

release.ApparmorSupports(feature) sounds nice, btw.

interfaces/backends/backends.go
+ // snapd.service. This aspect should be retained even after the switch to
+ // user-warning.
+ if featureLevel != aa.Full {
+ fmt.Printf("WARNING: %s\n", summary)
@niemeyer

niemeyer Aug 30, 2017

Contributor

Code that prints on import seems pretty bad. Let's please drop this and the long apologetic comment. :)

We can easily log in the real log, and in a proper place, if we want to.

@zyga

zyga Aug 31, 2017

Contributor

We cannot log the warning to the real log because of initialization order of our logger. This does the right thing because stdout is properly logged into the journal.

+ // Enable apparmor backend if there is any level of apparmor support,
+ // including partial feature set. This will allow snap-confine to always
+ // link to apparmor and check if it is enabled on boot, knowing that there
+ // is always *some* profile to apply to each snap process.
@niemeyer

niemeyer Aug 30, 2017

Contributor

❤️

@zyga

zyga Sep 4, 2017

Contributor

Hmm?

@zyga

zyga Sep 11, 2017

Contributor

Ah :-)

@niemeyer niemeyer removed this from the 2.28 milestone Aug 30, 2017

Contributor

zyga commented Sep 11, 2017

I'll clean up this PR shortly.

zyga added some commits Sep 12, 2017

many: rename apparmor.Partial to .PartialSupport
Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
many: rename apparmor.Full to .FullSupport
Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
many: rename apparmor.None to .NoSupport
Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
many: rename apparmor.Evaluate to .SupportLevel
Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
many: rename FeatureLevel (type) to SupportLevel
Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
release: add apparmor query and mock APIs
This patch adds new helpers to the release module. They are placed there
out of convenience and proximity to the forced-devmode flag even though
they are not related to os-release in any way.

The remaining apparmor related functions in the release module are now
shims that just use internally cached apparmor data.

Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
interfaces/backends: switch apparmor to release-based APIs
Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
interfaces/apparmor: switch apparmor to release-based APIs
Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>

I reviewed the new commits since my last review and have a few questions, so marking back to 'Request changes' for now.

apparmor/probe.go
-// Evaluate checks if the apparmor module is enabled and if all the required features are available.
-func (ks *KernelSupport) Evaluate() (level FeatureLevel, summary string) {
+// SupportLevel checks if the apparmor module is enabled and if all the required features are available.
+func (ks *KernelSupport) SupportLevel() (level SupportLevel, summary string) {
@jdstrand

jdstrand Sep 13, 2017

Contributor

It feels a little weird that this function is called SupportLevel() and it takes an argument of type SupportLevel. Should the function be called GetSupportLevel()?

@niemeyer

niemeyer Sep 13, 2017

Contributor

Yes, this is all overcomplexity that should go away.

interfaces/apparmor/backend_test.go
@@ -366,6 +370,9 @@ snippet
}}
func (s *backendSuite) TestCombineSnippets(c *C) {
+ restore := release.MockAppArmorSupportLevel(aa.FullSupport)
+ defer restore()
+
// NOTE: replace the real template with a shorter variant
restoreTemplate := apparmor.MockTemplate("\n" +
@jdstrand

jdstrand Sep 13, 2017

Contributor

The mixture of 'aa.Foo' and 'apparmor.Foo' here and elsewhere in the PR seems weird to me. Is there a reason why you aren't consistently using one over the other?

release/release.go
@@ -55,8 +55,24 @@ 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.ProbeKernel().Evaluate()
- return level != apparmor.Full
+ return AppArmorSupportLevel() != apparmor.FullSupport
@jdstrand

jdstrand Sep 13, 2017

Contributor

I realize that you are returning the equivalent of before, but this (forcing devmode if not full support) seems inconsistent with interfaces/backends/backends.go init() where now add the apparmor backend if there is any level of support. Perhaps at least the comment needs to be adjusted?

@niemeyer

niemeyer Sep 13, 2017

Contributor

Let's please not change this here. ForceDevMode as an idea should hopefully die altogether, but this PR is lingering for too long on issues which are related to its implementation. Let's keep unrelated fixes for follow ups please.

@jdstrand

jdstrand Sep 13, 2017

Contributor

I agree it should be in a future PR, but felt the inconsistency should at least be pointed out (so we know to change it going forward! :) and that perhaps the comment should change.

release/release.go
+
+// AppArmorSupportLevel quantifies how well apparmor is supported on the current kernel.
+func AppArmorSupportLevel() apparmor.SupportLevel {
+ level, _ := aa.SupportLevel()
@niemeyer

niemeyer Sep 13, 2017

Contributor

Sorry, but it feels super strange to have such trivial helpers to our own trivial helpers (!) that are sitting across two different packages, and having one of those two packages consistently entirely of the trivial helpers.

Since this is taking a bit longer than it should for such a simple change, here is a more clear path for LGTM:

  • The level function becomes release.AppArmorLevel
  • The type becomes release.AppArmorLevelType
  • The summary becomes release.AppArmorSummary
  • All of that inside release/apparmor{,_test}.go
  • KernelSupport dies. All of that functionality fits inside a tiny init function that runs once during execution and stores single two global variables: one level, one summary.
  • apparmor package dies. Its whole point is that funcionality today.
@niemeyer

niemeyer Sep 13, 2017

Contributor

Ah, and consts might be release.{No,Partial,Full}AppArmor.

Contributor

jdstrand commented Sep 13, 2017

Since @niemeyer addressed my questions, is driving the design and I agree with that design, I'll mark my bits as approved now.

Collaborator

mvo5 commented Sep 14, 2017

This should be ready for a re-review.

Thanks for sorting this out, @mvo5!

LGTM.. just re-running tests now.

Contributor

niemeyer commented Sep 19, 2017

The broken test looks unrelated, btw. Seems to be an unrelated Debian issue:


dpkg: error processing archive /tmp/apt-dpkg-install-HCzLFj/39-manpages-dev_4.13-2_all.deb (--unpack):
 trying to overwrite '/usr/share/man/man4/console_ioctl.4.gz', which is also in package manpages 4.10-2
Errors were encountered while processing:
 /tmp/apt-dpkg-install-HCzLFj/39-manpages-dev_4.13-2_all.deb
E: Sub-process /usr/bin/dpkg returned an error code (1)
quiet: end of output.

@mvo5 mvo5 merged commit 702d1f4 into snapcore:master Sep 19, 2017

4 of 7 checks passed

artful-amd64 autopkgtest finished (failure)
Details
artful-i386 autopkgtest finished (failure)
Details
zesty-amd64 autopkgtest finished (failure)
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details
xenial-amd64 autopkgtest finished (success)
Details
xenial-i386 autopkgtest finished (success)
Details
xenial-ppc64el autopkgtest finished (success)
Details
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment