diff options
| author | Pawel Stolowski <stolowski@gmail.com> | 2019-08-19 09:35:38 +0200 |
|---|---|---|
| committer | Pawel Stolowski <stolowski@gmail.com> | 2019-08-19 09:35:38 +0200 |
| commit | 997775e1293efe8b90e33b762f73e64d6b684342 (patch) | |
| tree | f79a0ebf039c866da17f990e7ca05b2d2b3f07f8 /snap | |
| parent | 14c3de1cc406e8a0f609be9109566212dc5eb48a (diff) | |
| parent | 4d13a33a3d14a04f53716aecac325cbe020f08b8 (diff) | |
Merge branch 'master' into seed-error-out-on-duplicated-snap
Diffstat (limited to 'snap')
| -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 |
5 files changed, 301 insertions, 18 deletions
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"`) +} |
