Skip to content

Commit

Permalink
Merge branch 'master' into snap-get-dockeys
Browse files Browse the repository at this point in the history
  • Loading branch information
stolowski committed Aug 30, 2017
2 parents 7956a54 + 7f34e53 commit ef989f0
Show file tree
Hide file tree
Showing 133 changed files with 2,464 additions and 382 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ cmd/*/*.[1-9]

# auto-generated systemd units
data/systemd/*.service

# auto-generated dbus services
data/dbus/*.service

data/info
data/env/snapd.sh

# test-driver
*.log
Expand Down
2 changes: 2 additions & 0 deletions PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Thanks for helping us make a better snapd!
Have you signed the [license agreement](https://www.ubuntu.com/legal/contributors) and read the [contribution guide](CONTRIBUTING.md)?
151 changes: 151 additions & 0 deletions apparmor/probe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2017 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 apparmor

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
)

// FeatureLevel encodes the kind of support for apparmor found on this system.
type FeatureLevel int

const (
// None indicates that apparmor is not enabled.
None FeatureLevel = iota
// Partial indicates that apparmor is enabled but some features are missing.
Partial
// Full indicates that all features are supported.
Full
)

var (
// featureSysPath points to the sysfs directory where apparmor features are listed.
featuresSysPath = "/sys/kernel/security/apparmor/features"
// requiredFeatures are the apparmor features needed for strict confinement.
requiredFeatures = []string{
"caps",
"dbus",
"domain",
"file",
"mount",
"namespaces",
"network",
"ptrace",
"rlimit",
"signal",
}
)

// KernelSupport describes apparmor features supported by the kernel.
type KernelSupport struct {
enabled bool
features map[string]bool
}

// ProbeKernel checks which apparmor features are available.
func ProbeKernel() *KernelSupport {
entries, err := ioutil.ReadDir(featuresSysPath)
if err != nil {
return nil
}
ks := &KernelSupport{
enabled: err == nil,
features: make(map[string]bool, len(entries)),
}
for _, entry := range entries {
// Each sub-directory represents a speicfic feature. Some have more
// details as additional sub-directories or files therein but we are
// not inspecting that at the moment.
if entry.IsDir() {
ks.features[entry.Name()] = true
}
}
return ks
}

// IsEnabled returns true if apparmor is enabled.
func (ks *KernelSupport) IsEnabled() bool {
return ks != nil && ks.enabled
}

// SupportsFeature returns true if a given apparmor feature is supported.
func (ks *KernelSupport) SupportsFeature(feature string) bool {
return ks != nil && ks.features[feature]
}

// Evaluate checks if the apparmor module is enabled and if all the required features are available.
func (ks *KernelSupport) Evaluate() (level FeatureLevel, summary string) {
if !ks.IsEnabled() {
return None, fmt.Sprintf("apparmor is not enabled")
}
var missing []string
for _, feature := range requiredFeatures {
if !ks.SupportsFeature(feature) {
missing = append(missing, feature)
}
}
if len(missing) > 0 {
sort.Strings(missing)
return Partial, fmt.Sprintf("apparmor is enabled but some features are missing: %s", strings.Join(missing, ", "))
}
return Full, "apparmor is enabled and all features are available"
}

// MockFeatureLevel fakes the desired apparmor feature level.
func MockFeatureLevel(level FeatureLevel) (restore func()) {
oldFeaturesSysPath := featuresSysPath

temp, err := ioutil.TempDir("", "mock-apparmor-feature-level")
if err != nil {
panic(err)
}
featuresSysPath = filepath.Join(temp, "features")

switch level {
case None:
// create no directory at all (apparmor not available).
case Partial:
// create several feature directories, matching vanilla 4.12 kernel.
for _, feature := range []string{"caps", "domain", "file", "network", "policy", "rlimit"} {
if err := os.MkdirAll(filepath.Join(featuresSysPath, feature), 0755); err != nil {
panic(err)
}
}
case Full:
// create all the feature directories, matching Ubuntu kernels.
for _, feature := range requiredFeatures {
if err := os.MkdirAll(filepath.Join(featuresSysPath, feature), 0755); err != nil {
panic(err)
}
}
}

return func() {
if err := os.RemoveAll(temp); err != nil {
panic(err)
}
featuresSysPath = oldFeaturesSysPath
}
}
77 changes: 77 additions & 0 deletions apparmor/probe_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2017 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 apparmor_test

import (
. "gopkg.in/check.v1"
"testing"

"github.com/snapcore/snapd/apparmor"
)

func Test(t *testing.T) {
TestingT(t)
}

type probeSuite struct{}

var _ = Suite(&probeSuite{})

func (s *probeSuite) TestMockProbeNone(c *C) {
restore := apparmor.MockFeatureLevel(apparmor.None)
defer restore()

ks := apparmor.ProbeKernel()
c.Assert(ks.IsEnabled(), Equals, false)
c.Assert(ks.SupportsFeature("dbus"), Equals, false)
c.Assert(ks.SupportsFeature("file"), Equals, false)

level, summary := ks.Evaluate()
c.Assert(level, Equals, apparmor.None)
c.Assert(summary, Equals, "apparmor is not enabled")
}

func (s *probeSuite) TestMockProbePartial(c *C) {
restore := apparmor.MockFeatureLevel(apparmor.Partial)
defer restore()

ks := apparmor.ProbeKernel()
c.Assert(ks.IsEnabled(), Equals, true)
c.Assert(ks.SupportsFeature("dbus"), Equals, false)
c.Assert(ks.SupportsFeature("file"), Equals, true)

level, summary := ks.Evaluate()
c.Assert(level, Equals, apparmor.Partial)
c.Assert(summary, Equals, "apparmor is enabled but some features are missing: dbus, mount, namespaces, ptrace, signal")
}

func (s *probeSuite) TestMockProbeFull(c *C) {
restore := apparmor.MockFeatureLevel(apparmor.Full)
defer restore()

ks := apparmor.ProbeKernel()
c.Assert(ks.IsEnabled(), Equals, true)
c.Assert(ks.SupportsFeature("dbus"), Equals, true)
c.Assert(ks.SupportsFeature("file"), Equals, true)

level, summary := ks.Evaluate()
c.Assert(level, Equals, apparmor.Full)
c.Assert(summary, Equals, "apparmor is enabled and all features are available")
}
7 changes: 6 additions & 1 deletion client/snap_op.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2016 Canonical Ltd
* Copyright (C) 2016-2017 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
Expand Down Expand Up @@ -117,6 +117,11 @@ func (client *Client) Revert(name string, options *SnapOptions) (changeID string
return client.doSnapAction("revert", name, options)
}

// Switch moves the snap to a different channel without a refresh
func (client *Client) Switch(name string, options *SnapOptions) (changeID string, err error) {
return client.doSnapAction("switch", name, options)
}

var ErrDangerousNotApplicable = fmt.Errorf("dangerous option only meaningful when installing from a local file")

func (client *Client) doSnapAction(actionName string, snapName string, options *SnapOptions) (changeID string, err error) {
Expand Down
1 change: 1 addition & 0 deletions client/snap_op_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ var ops = []struct {
{(*client.Client).Revert, "revert"},
{(*client.Client).Enable, "enable"},
{(*client.Client).Disable, "disable"},
{(*client.Client).Switch, "switch"},
}

var multiOps = []struct {
Expand Down
7 changes: 6 additions & 1 deletion cmd/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,15 @@ fmt: $(foreach dir,$(subdirs),$(wildcard $(srcdir)/$(dir)/*.[ch]))
# The hack target helps devlopers work on snap-confine on their live system by
# installing a fresh copy of snap confine and the appropriate apparmor profile.
.PHONY: hack
hack: snap-confine/snap-confine snap-confine/snap-confine.apparmor
hack: snap-confine/snap-confine snap-confine/snap-confine.apparmor snap-update-ns/snap-update-ns
sudo install -D -m 4755 snap-confine/snap-confine $(DESTDIR)$(libexecdir)/snap-confine
sudo install -m 644 snap-confine/snap-confine.apparmor $(DESTDIR)/etc/apparmor.d/$(patsubst .%,%,$(subst /,.,$(libexecdir))).snap-confine
sudo apparmor_parser -r snap-confine/snap-confine.apparmor
sudo install -m 755 snap-update-ns/snap-update-ns $(DESTDIR)$(libexecdir)/snap-update-ns

# for the hack target also:
snap-update-ns/snap-update-ns: snap-update-ns/*.go snap-update-ns/*.[ch]
cd snap-update-ns && GOPATH=$(or $(GOPATH),$(realpath $(srcdir)/../../../../..)) go build -i -v

##
## libsnap-confine-private.a
Expand Down
10 changes: 10 additions & 0 deletions cmd/snap-repair/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ package main

import (
"gopkg.in/retry.v1"

"github.com/snapcore/snapd/asserts"
)

var (
Expand Down Expand Up @@ -53,6 +55,14 @@ func MockMaxRepairScriptSize(maxSize int) (restore func()) {
}
}

func MockTrustedRepairRootKeys(keys []*asserts.AccountKey) (restore func()) {
original := trustedRepairRootKeys
trustedRepairRootKeys = keys
return func() {
trustedRepairRootKeys = original
}
}

func (run *Runner) BrandModel() (brand, model string) {
return run.state.Device.Brand, run.state.Device.Model
}
Expand Down
46 changes: 42 additions & 4 deletions cmd/snap-repair/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ func (run *Runner) saveStream(brandID string, seq int, repair *asserts.Repair, a
r := append([]asserts.Assertion{repair}, aux...)
for _, a := range r {
if err := enc.Encode(a); err != nil {
return fmt.Errorf("cannot encode repair assertions %q-%s for saving: %v", brandID, repairID, err)
return fmt.Errorf("cannot encode repair assertions %s-%s for saving: %v", brandID, repairID, err)
}
}
p := filepath.Join(d, fmt.Sprintf("repair.r%d", r[0].Revision()))
Expand All @@ -613,7 +613,7 @@ func (run *Runner) readSavedStream(brandID string, seq, revision int) (repair *a
break
}
if err != nil {
return nil, nil, fmt.Errorf("cannot decode repair assertions %q-%s from disk: %v", brandID, repairID, err)
return nil, nil, fmt.Errorf("cannot decode repair assertions %s-%s from disk: %v", brandID, repairID, err)
}
r = append(r, a)
}
Expand All @@ -634,7 +634,7 @@ func (run *Runner) makeReady(brandID string, sequenceNext int) (repair *asserts.
repair, aux, err = run.refetch(brandID, state.Sequence, state.Revision)
if err != nil {
if err != ErrRepairNotModified {
logger.Noticef("cannot refetch repair %q-%d, will retry what is on disk: %v", brandID, sequenceNext, err)
logger.Noticef("cannot refetch repair %s-%d, will retry what is on disk: %v", brandID, sequenceNext, err)
}
// try to use what we have already on disk
repair, aux, err = run.readSavedStream(brandID, state.Sequence, state.Revision)
Expand All @@ -661,7 +661,10 @@ func (run *Runner) makeReady(brandID string, sequenceNext int) (repair *asserts.
return nil, errSkip
}
}
// TODO: verify with signatures
// verify with signatures
if err := run.Verify(repair, aux); err != nil {
return nil, fmt.Errorf("cannot verify repair %s-%d: %v", brandID, state.Sequence, err)
}
if err := run.saveStream(brandID, state.Sequence, repair, aux); err != nil {
return nil, err
}
Expand Down Expand Up @@ -712,3 +715,38 @@ func (run *Runner) Next(brandID string) (*Repair, error) {
}, nil
}
}

// Limit trust to specific keys while there's no delegation or limited
// keys support. The obtained assertion stream may also include
// account keys that are directly or indirectly signed by a trusted
// key.
var (
trustedRepairRootKeys []*asserts.AccountKey
)

// Verify verifies that the repair is properly signed by the specific
// trusted root keys or by account keys in the stream (passed via aux)
// directly or indirectly signed by a trusted key.
func (run *Runner) Verify(repair *asserts.Repair, aux []asserts.Assertion) error {
workBS := asserts.NewMemoryBackstore()
for _, a := range aux {
if a.Type() != asserts.AccountKeyType {
continue
}
err := workBS.Put(asserts.AccountKeyType, a)
if err != nil {
return err
}
}
trustedBS := asserts.NewMemoryBackstore()
for _, t := range trustedRepairRootKeys {
trustedBS.Put(asserts.AccountKeyType, t)
}
for _, t := range sysdb.Trusted() {
if t.Type() == asserts.AccountType {
trustedBS.Put(asserts.AccountType, t)
}
}

return verifySignatures(repair, workBS, trustedBS)
}
Loading

0 comments on commit ef989f0

Please sign in to comment.