summaryrefslogtreecommitdiff
path: root/snap
diff options
authorPawel Stolowski <stolowski@gmail.com>2019-08-19 09:35:38 +0200
committerPawel Stolowski <stolowski@gmail.com>2019-08-19 09:35:38 +0200
commit997775e1293efe8b90e33b762f73e64d6b684342 (patch)
treef79a0ebf039c866da17f990e7ca05b2d2b3f07f8 /snap
parent14c3de1cc406e8a0f609be9109566212dc5eb48a (diff)
parent4d13a33a3d14a04f53716aecac325cbe020f08b8 (diff)
Merge branch 'master' into seed-error-out-on-duplicated-snap
Diffstat (limited to 'snap')
-rw-r--r--snap/info.go19
-rw-r--r--snap/info_snap_yaml.go118
-rw-r--r--snap/info_snap_yaml_test.go152
-rw-r--r--snap/validate.go15
-rw-r--r--snap/validate_test.go15
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"`)
+}