Join GitHub today
GitHub is home to over 20 million developers working together to host and review code, manage projects, and build software together.
interfaces,interfaces/apparmor: convert apparmor code to SecurityBackend #744
Conversation
zyga
added some commits
Mar 24, 2016
niemeyer
reviewed
Mar 29, 2016
| + | ||
| +// ProfileName returns the name of apparmor profile for a given application. | ||
| +func ProfileName(appInfo *snap.AppInfo) string { | ||
| + return interfaces.SecurityTag(appInfo) |
niemeyer
Mar 29, 2016
Contributor
Importing two previously unnecessary packages here just so it can "rename" a function feels like unnecessary boilerplate, in principle.
niemeyer
reviewed
Mar 29, 2016
| +// | ||
| +// This method should be called after changing plug, slots, connections between | ||
| +// them or application present in the snap. | ||
| +func (b *Backend) Configure(snapInfo *snap.Info, repo *interfaces.Repository, developerMode bool) error { |
niemeyer
Mar 29, 2016
Contributor
Nitpick: the developerMode bool should come right after snapInfo, as it's closely associated with it. This will also fix the inconsistency between Configure and CombineSnippets in that regard.
niemeyer
reviewed
Mar 29, 2016
| +} | ||
| + | ||
| +// SecuritySystem returns the name of the managed security system. | ||
| +func (b *Backend) SecuritySystem() interfaces.SecuritySystem { |
niemeyer
Mar 29, 2016
Contributor
All these methods feel like unnecessary boilerplate. They're just referring to our own content elsewhere, so why not using the original content instead of giving new names that needs more source code and brain space to keep track of.
zyga
Mar 29, 2016
Contributor
The reason those methods exist is that I'd like to create a common backend base that then uses them as implementation helper. I don't know if you consider this as worth the effort.
For clarity, the common backend base would then implement Configure() / Deconfigure() once, having access to SecuritySystem(), Directory() and a few others.
zyga
Mar 29, 2016
Contributor
We discussed this but this doesn't change that there's something to share among the existing implementations. I you think the mild copy paste there is not something we should fix I can remove those methods.
EDIT: FYI, I'm not arguing on the public high-level interface. This is purely about the private interface that could be shared among all the current backends.
niemeyer
reviewed
Mar 29, 2016
| + | ||
| +// MockExternalCommands mocks and returns MockCmd for each of the external | ||
| +// commands used in this package. | ||
| +func MockExternalCommands(c *check.C) map[string]*testutil.MockCmd { |
niemeyer
Mar 29, 2016
Contributor
Why do we need this here? Doesn't feel like it's doing anything relevant.
zyga
Mar 29, 2016
Contributor
So that we can mock apparmor parser away in a consistent way. Upcoming udev backend also uses the same method. I can just mock it directly in tests if you prefer it that way.
niemeyer
Mar 29, 2016
Contributor
Yes, please. It has no reason to be in export_test.go, has much more logic than a trivial call to MockCommand, and is less flexible since as soon as you want more than exit 0 this won't work.
niemeyer
reviewed
Mar 29, 2016
| // http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/apparmor/templates/ubuntu-core/16.04/default | ||
| -const defaultAppArmorTemplate = ` | ||
| +const realDefaultTemplate = ` |
zyga
Mar 29, 2016
Contributor
I wanted to keep the real template, that cannot be mocked, in a constant for reference.
niemeyer
Mar 29, 2016
Contributor
This const is not ever ever used besides to assign to defaultTemplate. No benefit.
niemeyer
reviewed
Mar 29, 2016
| @@ -44,3 +46,17 @@ func WrapperNameForApp(snapName, appName string) string { | ||
| func SecurityTagForApp(snapName, appName string) string { | ||
| return fmt.Sprintf("%s.snap", WrapperNameForApp(snapName, appName)) | ||
| } | ||
| + | ||
| +// SecurityTag returns application-specific security tag. |
niemeyer
Mar 29, 2016
Contributor
Both this and the above function claim to offer an app-specific security tag, and they disagree about how it should look like.
We also need to get rid of WrapperNameForApp. This is not the place this convention should be defined.. (we're handling interfaces here).
zyga
Mar 29, 2016
Contributor
SecurityTagForApp and everything else that doesn't use snap.Info is deprecated. It gets removed with the last backend.
niemeyer
Mar 29, 2016
Contributor
Please replace the docs for both functions with:
// DEPRECATED: Remove after backends are converted to SecurityBackend.
niemeyer
reviewed
Mar 29, 2016
| + "###PROFILEATTACH### (attach_disconnected) {\n" + | ||
| + "}\n") | ||
| + defer restore() | ||
| + for _, scenario := range []struct { |
niemeyer
Mar 29, 2016
Contributor
This for loop needs real love so it becomes readable.
The whole content should be out of this function and into the top-level, such complex types should not be defined inline, the slice should be out of here as well, the strings should have their content de-dupped, etc.
Let's please be a bit more caring about how our code looks.
zyga
Mar 29, 2016
Contributor
I did my best to simplify this but I kept the strings as there are sadly each unique. Have a look if I can make anything else better.
niemeyer
reviewed
Mar 29, 2016
| + | ||
| +func (s *backendSuite) TestCombineSnippets(c *C) { | ||
| + snapInfo, err := snap.InfoFromSnapYaml([]byte(` | ||
| +name: SNAP |
niemeyer
Mar 29, 2016
Contributor
This string should be defined only once and reused, instead of inlining it everytime. We should also avoid using such strings that break the indentation of the function. It makes reading much harder.
niemeyer
reviewed
Mar 29, 2016
| + | ||
| +func (s *backendSuite) TestUpdatingSnapToOneWithMoreApps(c *C) { | ||
| + const before = ` | ||
| +name: samba |
zyga
Mar 29, 2016
Contributor
I did a pass that removes most of those but actually leaves this pair and a similar (but swapped) pair in TestUpdatingSnapTOOneWithFewerApps since they are unique to those tests. I'll do a second pass that makes those shared as well.
niemeyer
reviewed
Mar 29, 2016
| + } | ||
| + // Unload removed profiles | ||
| + for _, baseName := range removed { | ||
| + // XXX: This depends on FileName() and ProfileName() returning the same |
niemeyer
Mar 29, 2016
Contributor
What about killing FileName and ProfileName and using a single function everywhere?
zyga
Mar 29, 2016
Contributor
That's fine too. I was trying to be explicit. If we don't want to ever use different profile name and different file name those can be merged.
niemeyer
Mar 29, 2016
Contributor
"Ever" is a very long time that goes past our lives. There are three functions here: SecurityTag, ProfileName, FileName. Why do we have three functions that do nothing but return the same value that has exactly the same purpose, and that would actually break logic if diverged? (!!!)
zyga
Mar 29, 2016
Contributor
FileName and ProfileName can^H could change separately. I don't argue that they should, I'm just explaining what the reasoning was behind this.
EDIT: given the coupling between profile name and filename where we keep the profile I'm now convinced. I'll get rid of the extra names.
niemeyer
reviewed
Mar 29, 2016
| + // XXX: This depends on FileName() and ProfileName() returning the same | ||
| + // value. This is true since both of those use SecurityTag(). | ||
| + profile := Profile{Name: baseName} | ||
| + err := profile.Unload() |
zyga
Mar 29, 2016
Contributor
This part needs some cleanup/consideration of how/if we want to scan all loaded apparmor profiles. Earlier it seemed we do want to scan loaded profiles. This gave rise of the Profile struct. Now I'm not so sure. The only point of that struct is to convey profile name and enforcement mode together. If we don't need this information the API can be simplified.
niemeyer
Mar 29, 2016
Contributor
The best is to go the opposite way, simplify first, make it complex if necessary.
niemeyer
reviewed
Mar 29, 2016
| + s := make([][]byte, 0, len(snippets[appInfo.Name])+2) | ||
| + s = append(s, b.aaHeader(appInfo, developerMode)) | ||
| + s = append(s, snippets[appInfo.Name]...) | ||
| + s = append(s, []byte("}\n")) |
niemeyer
Mar 29, 2016
Contributor
That feels strange.. Where's the opening one? Why are we opening and closing in separate places?
zyga
Mar 29, 2016
Contributor
The opening one is in the header. We could split this out but that would mean we'd have to parse the template more (template header is non-trivial). I don't think it's worth the effort given the complexity and the fact that just chopping the trailing } gets us what we want.
niemeyer
reviewed
Mar 29, 2016
| + s = append(s, b.aaHeader(appInfo, developerMode)) | ||
| + s = append(s, snippets[appInfo.Name]...) | ||
| + s = append(s, []byte("}\n")) | ||
| + fileContent := bytes.Join(s, []byte("\n")) |
niemeyer
Mar 29, 2016
Contributor
The logic here is excessively wasteful: allocate a slice of slices to append slices to reallocate a slice to then join the slices. Let's please do it all in one shot: allocate once with the final length, write the final content into it.
zyga
Mar 29, 2016
Contributor
Hmm, how can I do this in one go? I need to do have []snippet{header, rest..., footer} but this is not valid go syntax.
NOTE: The slice has the right capacity (so there is no re-allocation of s).
niemeyer
Mar 29, 2016
Contributor
It's okay, I take that back. What you are doing is fine and I'm pedantic. Sorry.
|
First pass is done.. the change seems a good one, but it's a bit large, and consequently ended up with a largeish review. Sorry about that. |
niemeyer
added
the
Reviewed
label
Mar 29, 2016
|
Thanks for the review. I replied to some of the comments inline. I'll do a pass at the code and ack all individual comments next. |
zyga
added some commits
Mar 29, 2016
zyga
removed
the
Reviewed
label
Mar 29, 2016
zyga
added some commits
Mar 29, 2016
|
This is ready for another review |
pedronis
reviewed
Mar 29, 2016
| + if err != nil { | ||
| + return fmt.Errorf("cannot synchronize security files for snap %q: %s", snapInfo.Name, err) | ||
| + } | ||
| + err = b.observeChanges(changed, removed) |
zyga
Mar 29, 2016
Contributor
Why toggle? It's not like this is always on-off back-forth kind of toggle.
pedronis
Mar 29, 2016
Contributor
@zyga that was strawman, but observeChanges is really not ideal as a name, also why not have two functions one for the unload part and one for the load/reload
niemeyer
reviewed
Mar 29, 2016
| +@{APP_VERSION}="1" | ||
| +@{INSTALL_DIR}="{/snaps,/gadget}" | ||
| +profile "snap.samba.smbd" (attach_disconnected,complain) { | ||
| +snippet |
niemeyer
Mar 29, 2016
Contributor
Again, can we please kill the duplication in these snippets? The actual relevant difference in them is 5 lines, total! We don't need all 34 here. For instance:
content: commonPrefix + `
...
niemeyer
reviewed
Mar 29, 2016
| + "###PROFILEATTACH### (attach_disconnected) {\n" + | ||
| + "}\n") | ||
| + defer restore() | ||
| + for _, scenario := range combineSnippetsScenarios { |
niemeyer
reviewed
Mar 29, 2016
| +// In addition, the set of variables listed here interacts with old-security | ||
| +// interface since there the base template is provided by a particular 3rd | ||
| +// party snap, not by snappy. | ||
| +func legacyVariables(appInfo *snap.AppInfo) string { |
niemeyer
Mar 29, 2016
Contributor
These two functions should use bytes.Buffer values:
var buf bytes.Buffer
fmt.Fprintf(&buf, ...)
fmt.Fprintf(&buf, ...)
return buf.String()
(or return buf.Bytes(), depending on how that's used)
niemeyer
reviewed
Mar 29, 2016
| + text = strings.TrimRight(defaultTemplate, "\n}") | ||
| + // TODO: switch to modernVariables() after the default template is | ||
| + // compatible with them. | ||
| + text = strings.Replace(text, "###VAR###\n", legacyVariables(appInfo), 1) |
niemeyer
Mar 29, 2016
Contributor
Rather than building it just to call replace, can't we simply pass the arguments in as usual?
Same below.
niemeyer
reviewed
Mar 29, 2016
| + } | ||
| + text = strings.Replace(text, "###PROFILEATTACH###", | ||
| + fmt.Sprintf("profile \"%s\"", interfaces.SecurityTag(appInfo)), 1) | ||
| + return []byte(text) |
niemeyer
Mar 29, 2016
Contributor
Why are we working with strings above if the end goal is a []byte? Much easier to work with a bytes.Buffer all along and return the final bytes array with buf.Bytes(). You can even pass the bytes.Buffer into legacyVariables/etc for easily cooking the end result at once, instead of manipulating immutable strings everywhere.
zyga
added some commits
Mar 30, 2016
niemeyer
merged commit 9dd1702
into
snapcore:master
Mar 30, 2016
jdstrand
reviewed
Mar 30, 2016
| +// | ||
| +// Snappy creates apparmor profiles for each application (for each snap) | ||
| +// present in the system. Upon each execution of ubuntu-core-launcher, the | ||
| +// profile is attached to the running process. Prior to that the profile must |
jdstrand
Mar 30, 2016
Contributor
Minor nitpick-- the profile isn't attached to the process so much as the process is launched under the profile.
jdstrand
reviewed
Mar 30, 2016
| +// "apparmor_parser". | ||
| +// | ||
| +// Each apparmor profile contains a simple <header><content><footer> structure. | ||
| +// The header specified an identifier that is relevant to the kernel. The |
jdstrand
Mar 30, 2016
Contributor
Please use "The header specifies the profile name that the launcher will use to launch a process under this profile."
jdstrand
reviewed
Mar 30, 2016
| +// Each apparmor profile contains a simple <header><content><footer> structure. | ||
| +// The header specified an identifier that is relevant to the kernel. The | ||
| +// identifier can be either the full path of the executable or an abstract | ||
| +// identifier not related to the executable name. |
jdstrand
Mar 30, 2016
Contributor
I'm not sure why you are discussing binary attachment here, but since you are, you should say that snappy only utilizes the "abstract identifier".
zyga
Mar 30, 2016
Contributor
Removed in earlier patches. I'm inclined to add the note about abstract identifier so that the next person that comes along to look at this won't have to do that much digging.
jdstrand
reviewed
Mar 30, 2016
| +// identifier not related to the executable name. | ||
| +// | ||
| +// The actual profiles are stored in /var/lib/snappy/apparmor/profiles. | ||
| +// This directory is also hard-coded in ubuntu-core-launcher. |
jdstrand
Mar 30, 2016
Contributor
The launcher doesn't care about /var/lib/snappy/apparmor/profiles because it doesn't touch the profile files-- it references the profile name of a profile that is already loaded into the kernel. ubuntu-core-launcher does care about the seccomp directory, /var/lib/snappy/seccomp/profiles, which is perhaps what you were thinking?
zyga
Mar 30, 2016
Contributor
That's right, the path is only relevant to the systemd unit that loads those on boot. Corrected in 434301b
jdstrand
reviewed
Mar 30, 2016
| +// This directory is also hard-coded in ubuntu-core-launcher. | ||
| +// | ||
| +// NOTE: A systemd job (TODO: specify which) loads all snappy-specific apparmor | ||
| +// profiles into the kernel during the boot process. |
jdstrand
reviewed
Mar 30, 2016
| +// non-fatal to the offending application process. | ||
| +// | ||
| +// This method should be called after changing plug, slots, connections between | ||
| +// them or application present in the snap. |
zyga
Mar 30, 2016
Contributor
Yes though I didn't really mention that. This part is coming up next (~tomorrow-ish)
jdstrand
reviewed
Mar 30, 2016
| + // file called "snap.sambda.smbd" was removed | ||
| + _, err := os.Stat(profile) | ||
| + c.Check(os.IsNotExist(err), Equals, true) | ||
| + // apparmor_parser was was used to unload the profile |
jdstrand
reviewed
Mar 30, 2016
| + // and empty lines are avoided as those can be discarded in the future. | ||
| + "#include <tunables/global>\n", | ||
| + "/tmp/ r,\n", | ||
| + "/sys/class/ r,\n", |
jdstrand
Mar 30, 2016
Contributor
/sys/class/ is perhaps brittle. I'm hoping this will go away with interfaces. The tunables/global and /tmp/ should be fine, but note if we change the spacing on '/tmp/ r,\n', it will fail the test (not a blocker, just pointing out it is brittle). A test for the ###PROFILEATTACH### replacement would be good though.
jdstrand
Mar 30, 2016
Contributor
I see that the ###PROFILEATTACH### replacement tests are down below.
zyga
Mar 30, 2016
Contributor
I'll keep this as-is as I hope we'll keep this up-to-date when working on the actual template text.
jdstrand
reviewed
Mar 30, 2016
| + c.Assert(err, IsNil) | ||
| + // Our custom template was used | ||
| + c.Assert(string(data), testutil.Contains, "FOO") | ||
| + // Custom profile can rely on legacy variables |
jdstrand
Mar 30, 2016
Contributor
Why are this referred to as "legacy variables"? These are apparmor profile variables that are still in use in our policy.
zyga
Mar 30, 2016
Contributor
Because they originate from 15.04 and we'll probably simplify things for 16.04 so that we use just the concepts of snap name, app name and security tag. This is still open for discussion, I just included it here. As a second reason "legacy variables" are always used for old-security since it has to be compatible with earlier releases.
jdstrand
reviewed
Mar 30, 2016
| +) | ||
| + | ||
| +// legacyVariablees returns text defining some apparmor variables that work | ||
| +// with legacy apparmor templates. |
jdstrand
Mar 30, 2016
Contributor
Why "legacy" here? These variables are in use by interfaces/apparmor/template.go and we should continue to use them to keep the implementation clean and make maintenance easier.
jdstrand
reviewed
Mar 30, 2016
| +// | ||
| +// In addition, the set of variables listed here interacts with old-security | ||
| +// interface since there the base template is provided by a particular 3rd | ||
| +// party snap, not by snappy. |
jdstrand
Mar 30, 2016
Contributor
I'm not sure why you are referring to old-security base templates here. AppArmor variables are "a good thing" regardless of using the old old-security system or the new interfaces system.
zyga
Mar 30, 2016
Contributor
I was referring to old-security because those variables need to be maintained for old-security to work. Without old-security any set of variables can be used (AFAIK, perhaps I'm wrong) as long as they are changed in sync with template.go.
jdstrand
Mar 30, 2016
Contributor
"I was referring to old-security because those variables need to be maintained for old-security to work. Without old-security any set of variables can be used (AFAIK, perhaps I'm wrong) as long as they are changed in sync with template.go."
I see, ok that context helps a lot. With old-security there needs to be coordination between ubuntu-core-security and snappy, without old-security, everything is contained within snappy (and as you said, coordinated with template.go).
jdstrand
reviewed
Mar 30, 2016
| + fmt.Fprintf(&buf, "@{APP_ID_DBUS}=\"%s\"\n", | ||
| + dbus.SafePath(fmt.Sprintf("%s.%s_%s_%s", | ||
| + appInfo.Snap.Name, appInfo.Snap.Developer, appInfo.Name, appInfo.Snap.Version))) | ||
| + // XXX: How is this different from APP_ID_DBUS? |
jdstrand
Mar 30, 2016
Contributor
APP_ID was previously defined as '<snap><appname><version>'. So the apparmor variables were:
APP_PKGNAME=<snap>
APP_APPNAME=<appname>
APP_VERSION=<version>
In this manner, APP_ID_DBUS was '<snap><appname><version>' translated to a dbus string, and APP_PKGNAME_DBUS was '<snap>' translated to a dbus string.
jdstrand
reviewed
Mar 30, 2016
| +// @{APP_NAME}=app.Name | ||
| +// @{APP_SECURITY_TAG}=app.SecurityTag() | ||
| +// @{SNAP_NAME}=app.SnapName | ||
| +// |
jdstrand
Mar 30, 2016
Contributor
This seems simple but needs to be thought out and what variables are culled or added directly affects security policy and interactions. For example, APP_VERSION is removed in your straw man, but we previously said that apps should only have read access to other versions' data directories-- do we add APP_REVISION or revisit the decision on versioned data dirs? APP_*_DBUS is gone and would need to be replaced with something in the interface for dbus.
Let's leave all that for a future commit and proper discussion.
jdstrand
reviewed
Mar 30, 2016
| + var buf bytes.Buffer | ||
| + fmt.Fprintf(&buf, "@{APP_NAME}=\"%s\"\n", appInfo.Name) | ||
| + fmt.Fprintf(&buf, "@{APP_SECURITY_TAG}=\"%s\"\n", interfaces.SecurityTag(appInfo)) | ||
| + fmt.Fprintf(&buf, "@{SNAP_NAME}=\"%s\"\n", appInfo.Snap.Name) |
jdstrand
Mar 30, 2016
Contributor
Introducing these now confuses policy since nothing in policy references these variables. I don't even know why we are exposes APP_SECURITY_TAG-- what is that? I suspect it is the profile name, but that is unneeded since apparmor already has a special @{profile_name} variable that the parser automatically creates.
I would prefer all but INSTALL_DIR be removed until this is properly choreographed with template.go and all the builtin/ policy.
zyga
Mar 30, 2016
Contributor
I feel I should have left that part out to separate the discussion of what to have for 16.04 and what we have in the code today. Your remarks are valid.
|
In general I only suggested comment changes or clarified language. The one thing I'd like to see removed are all the modern apparmor variables in template_vars.go except INSTALL_DIR. We should instead coordinate changes to template_vars.go with template.go and builtin/* in the same commit. Before we add modern variables and update the policy, I'm strongly in favor of focusing on getting the existing policy all working, applied and converting 15.04 frameworks to 16.04 interfaces first. |
|
I've proposed all the comment changes in #765 |
zyga commentedMar 24, 2016
This branch converts the older apparmor support code into something that looks like the new SecurityBackend. It gains a lot of features, most notably, simplicity of the high-level interface,
but also support for old-security and much better test coverage.
Signed-off-by: Zygmunt Krynicki zygmunt.krynicki@canonical.com