interfaces: sanitize plugs and slots early in ReadInfo #3972

Merged
merged 16 commits into from Oct 19, 2017

Conversation

Projects
None yet
4 participants
Contributor

stolowski commented Sep 27, 2017

Sanitize plugs and slots early in snap.ReadInfo so that every PlugInfo and SlotInfo is guaranteed to be sanitized when used. To preserve the current treatment of bad interfaces (which we skip but log in the task) I had to move away from BadInterfaceError (which is not really an error) and store bad interfaces in snap.Info, as they need to be reported at the point we have task at hand, rather than immediately on sanitize.

See the plan outlined here https://forum.snapcraft.io/t/preparing-the-interfaces-logic-for-connection-hooks/2184 - this is item no. 1 of the plan.

stolowski added some commits Sep 22, 2017

codecov-io commented Sep 27, 2017

Codecov Report

Merging #3972 into master will increase coverage by 0.08%.
The diff coverage is 47.29%.

Impacted file tree graph

@@            Coverage Diff            @@
##           master   #3972      +/-   ##
=========================================
+ Coverage   75.71%   75.8%   +0.08%     
=========================================
  Files         429     433       +4     
  Lines       36709   37174     +465     
=========================================
+ Hits        27793   28178     +385     
- Misses       6964    7024      +60     
- Partials     1952    1972      +20
Impacted Files Coverage Δ
overlord/snapstate/handlers.go 65.53% <ø> (ø) ⬆️
overlord/ifacestate/handlers.go 55.01% <0%> (-0.7%) ⬇️
snap/info_snap_yaml.go 94.03% <100%> (-0.04%) ⬇️
overlord/ifacestate/ifacemgr.go 93.18% <100%> (+0.15%) ⬆️
interfaces/repo.go 97.65% <100%> (-0.16%) ⬇️
cmd/snap-exec/main.go 69.23% <100%> (-0.18%) ⬇️
snap/info.go 80.42% <26.66%> (-7.87%) ⬇️
cmd/snap/main.go 63.38% <50%> (-0.15%) ⬇️
interfaces/builtin/all.go 73.33% <55.55%> (-26.67%) ⬇️
progress/progress.go 9.09% <0%> (-26.63%) ⬇️
... and 22 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 d023f13...85124e7. Read the comment docs.

A few comments for your consideration, nothing major.

interfaces/repo.go
ifaces: make(map[string]Interface),
plugs: make(map[string]map[string]*Plug),
slots: make(map[string]map[string]*Slot),
slotPlugs: make(map[*Slot]map[*Plug]*Connection),
plugSlots: make(map[*Plug]map[*Slot]*Connection),
backends: make(map[SecuritySystem]SecurityBackend),
}
+ snap.SanitizePlugsSlots = func(snapInfo *snap.Info) error {
@zyga

zyga Sep 28, 2017

Contributor

Could you please add a check that snap.SanitizePlugsSlots is not set yet? I worry that we may end up with sanitizer that checks against some ghost repository for whatever reason later.

@stolowski

stolowski Oct 3, 2017

Contributor

I tried a couple of different approches revolving around such check, but it turned out to be impossible due to the fact that in some tests we create two instances of repo side-by-side, or two daemons (with repos created implicitely).
Anyway, I changed this to an explicit setup call from ifacemgr, that way we know we do this only once in real code and there is no possiblity to mess it up. And ReadInfo will panic if the callback is not set.

@zyga

zyga Oct 3, 2017

Contributor

Thank you

interfaces/repo.go
- issues map[string]string // slot or plug name => message
-}
+func (r *Repository) SanitizePlugsSlots(snapInfo *snap.Info) error {
+ err := snap.Validate(snapInfo)
@zyga

zyga Sep 28, 2017

Contributor

Is there any reason to validate the snap info at this step? Also, without this the whole method returns nothing.

@stolowski

stolowski Oct 3, 2017

Contributor

Good point, moved it back to AddSnap.

interfaces/repo.go
@@ -862,11 +877,6 @@ func (e *BadInterfacesError) Error() string {
// Unknown interfaces and plugs/slots that don't validate are not added.
// Information about those failures are returned to the caller.
func (r *Repository) AddSnap(snapInfo *snap.Info) error {
- err := snap.Validate(snapInfo)
@zyga

zyga Sep 28, 2017

Contributor

If you choose to keep snap validation out of interface validation then please this code here.

@stolowski

stolowski Oct 3, 2017

Contributor

Done.

interfaces/repo_test.go
@@ -1539,7 +1539,7 @@ func (s *AddRemoveSuite) SetUpTest(c *C) {
c.Assert(err, IsNil)
}
-func (s *AddRemoveSuite) TestAddSnapComplexErrorHandling(c *C) {
+/*func (s *AddRemoveSuite) TestAddSnapComplexErrorHandling(c *C) {
@zyga

zyga Sep 28, 2017

Contributor

Can you please remove the affected test function instead of commenting it out?

@stolowski

stolowski Oct 3, 2017

Contributor

Thanks for spotting... an oversight.

- }
+ return err
+ }
+ if len(snapInfo.BadInterfaces) > 0 {
@zyga

zyga Sep 28, 2017

Contributor

Nice! Thank you

overlord/ifacestate/ifacestate_test.go
@@ -90,7 +92,11 @@ func (s *interfaceManagerSuite) SetUpTest(c *C) {
// TODO: transition this so that we don't load real backends and instead
// just load the test backend here and this is nicely integrated with
// extraBackends above.
- s.restoreBackends = ifacestate.MockSecurityBackends([]interfaces.SecurityBackend{s.secBackend})
+ restoreBackends := ifacestate.MockSecurityBackends([]interfaces.SecurityBackend{s.secBackend})
+ s.restore = func() {
@zyga

zyga Sep 28, 2017

Contributor

I wonder if we should either use (en mass) testutil.BaseTest or remove it, we seem to have a swarm of restore me later functions in all the suites.

@stolowski

stolowski Oct 3, 2017

Contributor

Good idea, done that.

@@ -165,6 +166,9 @@ type Info struct {
Plugs map[string]*PlugInfo
Slots map[string]*SlotInfo
+ // Plugs or slots with issues (they are not included in Plugs or Slots)
+ BadInterfaces map[string]string // slot or plug => message
@zyga

zyga Sep 28, 2017

Contributor

Shall we store the real error objects here? This way we could generate i18n specific message later.

@stolowski

stolowski Oct 3, 2017

Contributor

I'm not thrilled about storing error objects here, because these problems with interfaces are not actually treated as errors. Also, nothing is stopping us from populating this map with i18n, is it?

@zyga

zyga Oct 3, 2017

Contributor

The errors can be stored and transformed to per-request i18n message later. We cannot translate them at this time.

@niemeyer

niemeyer Oct 9, 2017

Contributor

We probably don't need to store those errors. Instead, if the interface definition is completely broken, we can reject the snap loading with the actual error.

@stolowski

stolowski Oct 9, 2017

Contributor

@niemeyer We have two possible problems to deal with: an unknown interface or interface not passing sanitization. AFAIR we didn't want to reject snaps on unknown interfaces.
Shall we only reject snaps that fail sanitization?

@niemeyer

niemeyer Oct 18, 2017

Contributor

It's okay, let's keep it this way for now. Strings are fine for the time being (it is already like that in master). Easy to change when we want to.

stolowski added some commits Oct 3, 2017

zyga approved these changes Oct 3, 2017

I think this looks good now. The i18n issues can be solved once we do per-request i18n

@stolowski stolowski requested a review from niemeyer Oct 3, 2017

interfaces/repo.go
+
+func (r *Repository) SetSanitizePlugSlots() {
+ snap.SanitizePlugsSlots = func(snapInfo *snap.Info) {
+ r.SanitizePlugsSlots(snapInfo)
@niemeyer

niemeyer Oct 9, 2017

Contributor

This doesn't look right. This is changing global state by a method on a repository instance, setting to its own method. The idea of enabling and disabling the global logic by fiddling with this function is also suspect.

Why do we need the repository for those validations? We know about every interface at runtime without the need to touch the repository. That function should even be changeable I think, but rather an actual function that should always be available.

@niemeyer

niemeyer Oct 9, 2017

Contributor

Sorry, I meant the function should not be changeable.

@stolowski

stolowski Oct 10, 2017

Contributor

Hmm. This function is called from snap.ReadInfo and needs to be a settable callback to avoid import cycle; in snapd it needs to actually sanitize plugs and slots against all interfaces, but in snap and snap-exec I set it to no-op func not to drag all the interfaces in (plus, it doesn't make sense to sanitize at this point).

To summarize, I'm not sure how to change this aspect to satisfy both cases - other than by having two ReadInfo functions (with- and without- sanitize), which I think is a bad idea.

NB, I got rid of the of the SetSanitizePlugSlots setter and do not depend on the repository anymore.

overlord/ifacestate/ifacemgr.go
@@ -51,6 +51,8 @@ func Manager(s *state.State, hookManager *hookstate.HookManager, extraInterfaces
runner: runner,
repo: interfaces.NewRepository(),
}
+ m.repo.SetSanitizePlugSlots()
@niemeyer

niemeyer Oct 9, 2017

Contributor

Same idea.. quite unexpected to have that initialized at a random point like this.

@@ -165,6 +166,9 @@ type Info struct {
Plugs map[string]*PlugInfo
Slots map[string]*SlotInfo
+ // Plugs or slots with issues (they are not included in Plugs or Slots)
+ BadInterfaces map[string]string // slot or plug => message
@zyga

zyga Sep 28, 2017

Contributor

Shall we store the real error objects here? This way we could generate i18n specific message later.

@stolowski

stolowski Oct 3, 2017

Contributor

I'm not thrilled about storing error objects here, because these problems with interfaces are not actually treated as errors. Also, nothing is stopping us from populating this map with i18n, is it?

@zyga

zyga Oct 3, 2017

Contributor

The errors can be stored and transformed to per-request i18n message later. We cannot translate them at this time.

@niemeyer

niemeyer Oct 9, 2017

Contributor

We probably don't need to store those errors. Instead, if the interface definition is completely broken, we can reject the snap loading with the actual error.

@stolowski

stolowski Oct 9, 2017

Contributor

@niemeyer We have two possible problems to deal with: an unknown interface or interface not passing sanitization. AFAIR we didn't want to reject snaps on unknown interfaces.
Shall we only reject snaps that fail sanitization?

@niemeyer

niemeyer Oct 18, 2017

Contributor

It's okay, let's keep it this way for now. Strings are fine for the time being (it is already like that in master). Easy to change when we want to.

Contributor

zyga commented Oct 9, 2017

I think that we can use the builtin.Interfaces() method so that we don't have to touch the repository in any way. @stolowski how does that sound to you? You can also expose the method builtin.Interface to access a specific one for the need of sanitization.

Contributor

stolowski commented Oct 9, 2017

@niemeyer @zyga Ok, I've made sanitization independent of repository, it's now a function based on allInterfaces.

@niemeyer niemeyer changed the title from repo: sanitize plugs and slots early in ReadInfo to interfaces: sanitize plugs and slots early in ReadInfo Oct 18, 2017

LGTM with the following details sorted.

cmd/snap-exec/main.go
@@ -43,6 +43,9 @@ var opts struct {
}
func main() {
+ // plug/slot sanitization not used nor possible from snap-exec, make it no-op
+ snap.SanitizePlugsSlots = func(snapInfo *snap.Info) {}
@niemeyer

niemeyer Oct 18, 2017

Contributor

If you move this into an init() function there's no need to mock it in tests.

In either case, both snap-exec and the snap command should either have it in main, or in init, so we don't need to keep that distinction in mind.

interfaces/builtin/all.go
+ for plugName, plugInfo := range snapInfo.Plugs {
+ iface, ok := allInterfaces[plugInfo.Interface]
+ if !ok {
+ snapInfo.BadInterfaces[plugName] = "unknown interface"
@niemeyer

niemeyer Oct 18, 2017

Contributor

Let's please add the interface name here:

"unknown interface %q"

@@ -165,6 +166,9 @@ type Info struct {
Plugs map[string]*PlugInfo
Slots map[string]*SlotInfo
+ // Plugs or slots with issues (they are not included in Plugs or Slots)
+ BadInterfaces map[string]string // slot or plug => message
@zyga

zyga Sep 28, 2017

Contributor

Shall we store the real error objects here? This way we could generate i18n specific message later.

@stolowski

stolowski Oct 3, 2017

Contributor

I'm not thrilled about storing error objects here, because these problems with interfaces are not actually treated as errors. Also, nothing is stopping us from populating this map with i18n, is it?

@zyga

zyga Oct 3, 2017

Contributor

The errors can be stored and transformed to per-request i18n message later. We cannot translate them at this time.

@niemeyer

niemeyer Oct 9, 2017

Contributor

We probably don't need to store those errors. Instead, if the interface definition is completely broken, we can reject the snap loading with the actual error.

@stolowski

stolowski Oct 9, 2017

Contributor

@niemeyer We have two possible problems to deal with: an unknown interface or interface not passing sanitization. AFAIR we didn't want to reject snaps on unknown interfaces.
Shall we only reject snaps that fail sanitization?

@niemeyer

niemeyer Oct 18, 2017

Contributor

It's okay, let's keep it this way for now. Strings are fine for the time being (it is already like that in master). Easy to change when we want to.

snap/info.go
@@ -323,6 +327,32 @@ func (s *Info) Services() []*AppInfo {
return svcs
}
+func (s *Info) BadInterfacesInfoString() string {
@niemeyer

niemeyer Oct 18, 2017

Contributor

This would be nicer going into a function of its own. It seems too specialized and too custom-formatted to make sense in Info itself.

The function might look like this, at the package level:

func BadInterfacesSummary(snapInfo *snap.Info) string

I think we'll also want to change the formatting soon so it's more readable, but we can do that in a follow up.

stolowski added some commits Oct 19, 2017

@stolowski stolowski merged commit bc06d51 into snapcore:master Oct 19, 2017

7 checks passed

artful-amd64 autopkgtest finished (success)
Details
artful-i386 autopkgtest finished (success)
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
zesty-amd64 autopkgtest finished (success)
Details
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment