summaryrefslogtreecommitdiff
path: root/snap
diff options
authorPawel Stolowski <stolowski@gmail.com>2019-08-19 10:35:09 +0200
committerPawel Stolowski <stolowski@gmail.com>2019-08-19 10:35:09 +0200
commitb60b05e6dddaca79a8fd602511d44c7e69146357 (patch)
treeed7983188fce9735c404d5283f90e8d9dc829792 /snap
parent05cd9093be974af6f95af1f8aa69faa62baa7cd0 (diff)
parent4d13a33a3d14a04f53716aecac325cbe020f08b8 (diff)
Merge branch 'master' into firsboot-early-basecheck
Diffstat (limited to 'snap')
-rw-r--r--snap/gadget.go43
-rw-r--r--snap/gadget_test.go81
-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
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"`)
+}