diff options
| author | Pawel Stolowski <stolowski@gmail.com> | 2019-08-19 10:35:09 +0200 |
|---|---|---|
| committer | Pawel Stolowski <stolowski@gmail.com> | 2019-08-19 10:35:09 +0200 |
| commit | b60b05e6dddaca79a8fd602511d44c7e69146357 (patch) | |
| tree | ed7983188fce9735c404d5283f90e8d9dc829792 /snap | |
| parent | 05cd9093be974af6f95af1f8aa69faa62baa7cd0 (diff) | |
| parent | 4d13a33a3d14a04f53716aecac325cbe020f08b8 (diff) | |
Merge branch 'master' into firsboot-early-basecheck
Diffstat (limited to 'snap')
| -rw-r--r-- | snap/gadget.go | 43 | ||||
| -rw-r--r-- | snap/gadget_test.go | 81 | ||||
| -rw-r--r-- | snap/info.go | 19 | ||||
| -rw-r--r-- | snap/info_snap_yaml.go | 118 | ||||
| -rw-r--r-- | snap/info_snap_yaml_test.go | 152 | ||||
| -rw-r--r-- | snap/validate.go | 15 | ||||
| -rw-r--r-- | snap/validate_test.go | 15 |
7 files changed, 301 insertions, 142 deletions
diff --git a/snap/gadget.go b/snap/gadget.go deleted file mode 100644 index 4d46fdbd1e..0000000000 --- a/snap/gadget.go +++ /dev/null @@ -1,43 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2016 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 snap - -import ( - "fmt" - - "github.com/snapcore/snapd/gadget" -) - -// ReadGadgetInfo reads the gadget specific metadata from gadget.yaml -// in the snap. classic set to true means classic rules apply, -// i.e. content/presence of gadget.yaml is fully optional. -func ReadGadgetInfo(info *Info, classic bool) (*gadget.Info, error) { - const errorFormat = "cannot read gadget snap details: %s" - - if info.GetType() != TypeGadget { - return nil, fmt.Errorf(errorFormat, "not a gadget snap") - } - - gi, err := gadget.ReadInfo(info.MountDir(), classic) - if err != nil { - return nil, fmt.Errorf(errorFormat, err) - } - return gi, nil -} diff --git a/snap/gadget_test.go b/snap/gadget_test.go deleted file mode 100644 index 2d2d6476d0..0000000000 --- a/snap/gadget_test.go +++ /dev/null @@ -1,81 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2014-2016 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 snap_test - -import ( - "io/ioutil" - "path/filepath" - - . "gopkg.in/check.v1" - - "github.com/snapcore/snapd/dirs" - "github.com/snapcore/snapd/gadget" - "github.com/snapcore/snapd/snap" - "github.com/snapcore/snapd/snap/snaptest" -) - -type gadgetYamlTestSuite struct{} - -var _ = Suite(&gadgetYamlTestSuite{}) - -var mockGadgetSnapYaml = ` -name: canonical-pc -type: gadget -` - -func (s *gadgetYamlTestSuite) SetUpTest(c *C) { - dirs.SetRootDir(c.MkDir()) -} - -func (s *gadgetYamlTestSuite) TearDownTest(c *C) { - dirs.SetRootDir("/") -} - -func (s *gadgetYamlTestSuite) TestReadGadgetNotAGadget(c *C) { - info := snaptest.MockInfo(c, ` -name: other -version: 0 -`, &snap.SideInfo{Revision: snap.R(42)}) - _, err := snap.ReadGadgetInfo(info, false) - c.Assert(err, ErrorMatches, "cannot read gadget snap details: not a gadget snap") -} - -func (s *gadgetYamlTestSuite) TestReadGadgetYamlMissing(c *C) { - info := snaptest.MockSnap(c, mockGadgetSnapYaml, &snap.SideInfo{Revision: snap.R(42)}) - _, err := snap.ReadGadgetInfo(info, false) - c.Assert(err, ErrorMatches, ".*meta/gadget.yaml: no such file or directory") -} - -func (s *gadgetYamlTestSuite) TestReadGadgetYamlOnClassicOptional(c *C) { - info := snaptest.MockSnap(c, mockGadgetSnapYaml, &snap.SideInfo{Revision: snap.R(42)}) - gi, err := snap.ReadGadgetInfo(info, true) - c.Assert(err, IsNil) - c.Check(gi, NotNil) -} - -func (s *gadgetYamlTestSuite) TestReadGadgetYamlOnClassicEmptyIsValid(c *C) { - info := snaptest.MockSnap(c, mockGadgetSnapYaml, &snap.SideInfo{Revision: snap.R(42)}) - err := ioutil.WriteFile(filepath.Join(info.MountDir(), "meta", "gadget.yaml"), nil, 0644) - c.Assert(err, IsNil) - - ginfo, err := snap.ReadGadgetInfo(info, true) - c.Assert(err, IsNil) - c.Assert(ginfo, DeepEquals, &gadget.Info{}) -} diff --git a/snap/info.go b/snap/info.go index 579c6904df..c82b42adfa 100644 --- a/snap/info.go +++ b/snap/info.go @@ -254,6 +254,10 @@ type Info struct { // The list of common-ids from all apps of the snap CommonIDs []string + + // List of system users (usernames) this snap may use. The group + // of the same name must also exist. + SystemUsernames map[string]*SystemUsernameInfo } // StoreAccount holds information about a store account, for example @@ -801,6 +805,21 @@ type HookInfo struct { Explicit bool } +// SystemUsernameInfo provides information about a system username (ie, a +// UNIX user and group with the same name). The scope defines visibility of the +// username wrt the snap and the system. Defined scopes: +// - shared static, snapd-managed user/group shared between host and all +// snaps +// - private static, snapd-managed user/group private to a particular snap +// (currently not implemented) +// - external dynamic user/group shared between host and all snaps (currently +// not implented) +type SystemUsernameInfo struct { + Name string + Scope string + Attrs map[string]interface{} +} + // File returns the path to the *.socket file func (socket *SocketInfo) File() string { return filepath.Join(dirs.SnapServicesDir, socket.App.SecurityTag()+"."+socket.Name+".socket") diff --git a/snap/info_snap_yaml.go b/snap/info_snap_yaml.go index 46612fa289..cab8dfdf07 100644 --- a/snap/info_snap_yaml.go +++ b/snap/info_snap_yaml.go @@ -34,24 +34,25 @@ import ( ) type snapYaml struct { - Name string `yaml:"name"` - Version string `yaml:"version"` - Type Type `yaml:"type"` - Architectures []string `yaml:"architectures,omitempty"` - Assumes []string `yaml:"assumes"` - Title string `yaml:"title"` - Description string `yaml:"description"` - Summary string `yaml:"summary"` - License string `yaml:"license,omitempty"` - Epoch Epoch `yaml:"epoch,omitempty"` - Base string `yaml:"base,omitempty"` - Confinement ConfinementType `yaml:"confinement,omitempty"` - Environment strutil.OrderedMap `yaml:"environment,omitempty"` - Plugs map[string]interface{} `yaml:"plugs,omitempty"` - Slots map[string]interface{} `yaml:"slots,omitempty"` - Apps map[string]appYaml `yaml:"apps,omitempty"` - Hooks map[string]hookYaml `yaml:"hooks,omitempty"` - Layout map[string]layoutYaml `yaml:"layout,omitempty"` + Name string `yaml:"name"` + Version string `yaml:"version"` + Type Type `yaml:"type"` + Architectures []string `yaml:"architectures,omitempty"` + Assumes []string `yaml:"assumes"` + Title string `yaml:"title"` + Description string `yaml:"description"` + Summary string `yaml:"summary"` + License string `yaml:"license,omitempty"` + Epoch Epoch `yaml:"epoch,omitempty"` + Base string `yaml:"base,omitempty"` + Confinement ConfinementType `yaml:"confinement,omitempty"` + Environment strutil.OrderedMap `yaml:"environment,omitempty"` + Plugs map[string]interface{} `yaml:"plugs,omitempty"` + Slots map[string]interface{} `yaml:"slots,omitempty"` + Apps map[string]appYaml `yaml:"apps,omitempty"` + Hooks map[string]hookYaml `yaml:"hooks,omitempty"` + Layout map[string]layoutYaml `yaml:"layout,omitempty"` + SystemUsernames map[string]interface{} `yaml:"system-usernames,omitempty"` // TypoLayouts is used to detect the use of the incorrect plural form of "layout" TypoLayouts typoDetector `yaml:"layouts,omitempty"` @@ -223,6 +224,11 @@ func infoFromSnapYaml(yamlData []byte, strk *scopedTracker) (*Info, error) { snap.BadInterfaces = make(map[string]string) SanitizePlugsSlots(snap) + // Collect system usernames + if err := setSystemUsernamesFromSnapYaml(y, snap); err != nil { + return nil, err + } + // FIXME: validation of the fields return snap, nil } @@ -235,6 +241,7 @@ func infoSkeletonFromSnapYaml(y snapYaml) *Info { if len(y.Architectures) != 0 { architectures = y.Architectures } + typ := TypeApp if y.Type != "" { typ = y.Type @@ -275,6 +282,7 @@ func infoSkeletonFromSnapYaml(y snapYaml) *Info { Plugs: make(map[string]*PlugInfo), Slots: make(map[string]*SlotInfo), Environment: y.Environment, + SystemUsernames: make(map[string]*SystemUsernameInfo), } sort.Strings(snap.Assumes) @@ -497,6 +505,28 @@ func setHooksFromSnapYaml(y snapYaml, snap *Info, strk *scopedTracker) { } } +func setSystemUsernamesFromSnapYaml(y snapYaml, snap *Info) error { + for user, data := range y.SystemUsernames { + if user == "" { + return fmt.Errorf("system username cannot be empty") + } + scope, attrs, err := convertToUsernamesData(user, data) + if err != nil { + return err + } + if scope == "" { + return fmt.Errorf("system username %q does not specify a scope", user) + } + snap.SystemUsernames[user] = &SystemUsernameInfo{ + Name: user, + Scope: scope, + Attrs: attrs, + } + } + + return nil +} + func bindUnscopedPlugs(snap *Info, strk *scopedTracker) { for plugName, plug := range snap.Plugs { if strk.plug(plug) { @@ -618,3 +648,55 @@ func convertToSlotOrPlugData(plugOrSlot, name string, data interface{}) (iface, return "", "", nil, err } } + +// Short form: +// system-usernames: +// snap_daemon: shared # 'scope' is 'shared' +// lxd: external # currently unsupported +// foo: private # currently unsupported +// Attributes form: +// system-usernames: +// snap_daemon: +// scope: shared +// attrib1: ... +// attrib2: ... +func convertToUsernamesData(user string, data interface{}) (scope string, attrs map[string]interface{}, err error) { + switch data.(type) { + case string: + return data.(string), nil, nil + case nil: + return "", nil, nil + case map[interface{}]interface{}: + for keyData, valueData := range data.(map[interface{}]interface{}) { + key, ok := keyData.(string) + if !ok { + err := fmt.Errorf("system username %q has attribute key that is not a string (found %T)", user, keyData) + return "", nil, err + } + switch key { + case "scope": + value, ok := valueData.(string) + if !ok { + err := fmt.Errorf("scope on system username %q is not a string (found %T)", user, valueData) + return "", nil, err + } + scope = value + case "": + return "", nil, fmt.Errorf("system username %q has an empty attribute key", user) + default: + if attrs == nil { + attrs = make(map[string]interface{}) + } + value, err := metautil.NormalizeValue(valueData) + if err != nil { + return "", nil, fmt.Errorf("attribute %q of system username %q: %v", key, user, err) + } + attrs[key] = value + } + } + return scope, attrs, nil + default: + err := fmt.Errorf("system username %q has malformed definition (found %T)", user, data) + return "", nil, err + } +} diff --git a/snap/info_snap_yaml_test.go b/snap/info_snap_yaml_test.go index f1eb7dc87b..a000db050c 100644 --- a/snap/info_snap_yaml_test.go +++ b/snap/info_snap_yaml_test.go @@ -1819,3 +1819,155 @@ apps: c.Assert(app, NotNil) c.Check(app.RestartDelay, Equals, timeout.Timeout(12*time.Second)) } + +func (s *YamlSuite) TestSnapYamlSystemUsernamesParsing(c *C) { + y := []byte(`name: binary +version: 1.0 +system-usernames: + foo: shared + bar: + scope: external + baz: + scope: private + attr1: norf + attr2: corge + attr3: "" +`) + info, err := snap.InfoFromSnapYaml(y) + c.Assert(err, IsNil) + c.Check(info.SystemUsernames, HasLen, 3) + c.Assert(info.SystemUsernames["foo"], DeepEquals, &snap.SystemUsernameInfo{ + Name: "foo", + Scope: "shared", + }) + c.Assert(info.SystemUsernames["bar"], DeepEquals, &snap.SystemUsernameInfo{ + Name: "bar", + Scope: "external", + }) + c.Assert(info.SystemUsernames["baz"], DeepEquals, &snap.SystemUsernameInfo{ + Name: "baz", + Scope: "private", + Attrs: map[string]interface{}{ + "attr1": "norf", + "attr2": "corge", + "attr3": "", + }, + }) +} + +func (s *YamlSuite) TestSnapYamlSystemUsernamesParsingBadType(c *C) { + y := []byte(`name: binary +version: 1.0 +system-usernames: + a: true +`) + info, err := snap.InfoFromSnapYaml(y) + c.Assert(err, NotNil) + c.Assert(err, ErrorMatches, `system username "a" has malformed definition \(found bool\)`) + c.Assert(info, IsNil) +} + +func (s *YamlSuite) TestSnapYamlSystemUsernamesParsingBadValue(c *C) { + y := []byte(`name: binary +version: 1.0 +system-usernames: + a: [b, c] +`) + info, err := snap.InfoFromSnapYaml(y) + c.Assert(err, NotNil) + c.Assert(err, ErrorMatches, `system username "a" has malformed definition \(found \[\]interface {}\)`) + c.Assert(info, IsNil) +} + +func (s *YamlSuite) TestSnapYamlSystemUsernamesParsingBadKeyEmpty(c *C) { + y := []byte(`name: binary +version: 1.0 +system-usernames: + "": shared +`) + _, err := snap.InfoFromSnapYaml(y) + c.Assert(err, NotNil) + c.Assert(err, ErrorMatches, `system username cannot be empty`) +} + +func (s *YamlSuite) TestSnapYamlSystemUsernamesParsingBadKeyList(c *C) { + y := []byte(`name: binary +version: 1.0 +system-usernames: +- foo: shared +`) + _, err := snap.InfoFromSnapYaml(y) + c.Assert(err, NotNil) + c.Assert(err, ErrorMatches, `(?m)cannot parse snap.yaml:.*`) +} + +func (s *YamlSuite) TestSnapYamlSystemUsernamesParsingBadValueEmpty(c *C) { + y := []byte(`name: binary +version: 1.0 +system-usernames: + a: "" +`) + _, err := snap.InfoFromSnapYaml(y) + c.Assert(err, ErrorMatches, `system username "a" does not specify a scope`) +} + +func (s *YamlSuite) TestSnapYamlSystemUsernamesParsingBadValueNull(c *C) { + y := []byte(`name: binary +version: 1.0 +system-usernames: + a: null +`) + _, err := snap.InfoFromSnapYaml(y) + c.Assert(err, ErrorMatches, `system username "a" does not specify a scope`) +} + +func (s *YamlSuite) TestSnapYamlSystemUsernamesParsingBadAttrKeyEmpty(c *C) { + y := []byte(`name: binary +version: 1.0 +system-usernames: + foo: + scope: shared + "": bar +`) + _, err := snap.InfoFromSnapYaml(y) + c.Assert(err, NotNil) + c.Assert(err, ErrorMatches, `system username "foo" has an empty attribute key`) +} + +func (s *YamlSuite) TestSnapYamlSystemUsernamesParsingBadAttrKeyNonString(c *C) { + y := []byte(`name: binary +version: 1.0 +system-usernames: + foo: + scope: shared + 1: bar +`) + _, err := snap.InfoFromSnapYaml(y) + c.Assert(err, NotNil) + c.Assert(err, ErrorMatches, `system username "foo" has attribute key that is not a string \(found int\)`) +} + +func (s *YamlSuite) TestSnapYamlSystemUsernamesParsingBadAttrValue(c *C) { + y := []byte(`name: binary +version: 1.0 +system-usernames: + foo: + scope: shared + bar: null +`) + _, err := snap.InfoFromSnapYaml(y) + c.Assert(err, NotNil) + c.Assert(err, ErrorMatches, `attribute "bar" of system username "foo": invalid scalar:.*`) +} + +func (s *YamlSuite) TestSnapYamlSystemUsernamesParsingBadScopeNonString(c *C) { + y := []byte(`name: binary +version: 1.0 +system-usernames: + foo: + scope: 10 +`) + _, err := snap.InfoFromSnapYaml(y) + c.Assert(err, NotNil) + c.Assert(err, ErrorMatches, `scope on system username "foo" is not a string \(found int\)`) +} diff --git a/snap/validate.go b/snap/validate.go index a40c5d1ded..148fa2578a 100644 --- a/snap/validate.go +++ b/snap/validate.go @@ -30,6 +30,7 @@ import ( "strings" "unicode/utf8" + "github.com/snapcore/snapd/osutil" "github.com/snapcore/snapd/snap/naming" "github.com/snapcore/snapd/spdx" "github.com/snapcore/snapd/strutil" @@ -346,6 +347,11 @@ func Validate(info *Info) error { return err } + // Ensure system usernames are valid + if err := ValidateSystemUsernames(info); err != nil { + return err + } + // ensure that common-id(s) are unique if err := ValidateCommonIDs(info); err != nil { return err @@ -936,3 +942,12 @@ func ValidateCommonIDs(info *Info) error { } return nil } + +func ValidateSystemUsernames(info *Info) error { + for username := range info.SystemUsernames { + if !osutil.IsValidUsername(username) { + return fmt.Errorf("invalid system username %q", username) + } + } + return nil +} diff --git a/snap/validate_test.go b/snap/validate_test.go index 253a4dad9d..ecd73bd61b 100644 --- a/snap/validate_test.go +++ b/snap/validate_test.go @@ -1559,3 +1559,18 @@ apps: } } } + +func (s *ValidateSuite) TestValidateSystemUsernames(c *C) { + const yaml1 = `name: binary +version: 1.0 +system-usernames: + "b@d": shared +` + + strk := NewScopedTracker() + info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml1), nil, strk) + c.Assert(err, IsNil) + c.Assert(info.SystemUsernames, HasLen, 1) + err = Validate(info) + c.Assert(err, ErrorMatches, `invalid system username "b@d"`) +} |
