Skip to content

Commit

Permalink
daemon: add unit tests for themesOpenAccess access checker
Browse files Browse the repository at this point in the history
  • Loading branch information
jhenstridge committed Jul 28, 2021
1 parent 2428aac commit ca054f2
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 4 deletions.
11 changes: 8 additions & 3 deletions daemon/access.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,12 @@ func (ac snapAccess) CheckAccess(d *Daemon, r *http.Request, ucred *ucrednet, us
return Forbidden("access denied")
}

func requireThemeApiAccess(d *Daemon, ucred *ucrednet) *apiError {
var (
cgroupSnapNameFromPid = cgroup.SnapNameFromPid
requireThemeApiAccess = requireThemeApiAccessImpl
)

func requireThemeApiAccessImpl(d *Daemon, ucred *ucrednet) *apiError {
if ucred == nil {
return Forbidden("access denied")
}
Expand All @@ -173,7 +178,7 @@ func requireThemeApiAccess(d *Daemon, ucred *ucrednet) *apiError {

// Access on snapd-snap.socket requires a connected
// snapd-themes-control plug.
snapName, err := cgroup.SnapNameFromPid(int(ucred.Pid))
snapName, err := cgroupSnapNameFromPid(int(ucred.Pid))
if err != nil {
return Forbidden("could not determine snap name for pid: %s", err)
}
Expand All @@ -186,7 +191,7 @@ func requireThemeApiAccess(d *Daemon, ucred *ucrednet) *apiError {
return Forbidden("internal error: cannot get connections: %s", err)
}
for refStr, connState := range conns {
if connState.Undesired || connState.HotplugGone || connState.Interface != "snapd-theme-control" {
if connState.Undesired || connState.HotplugGone || connState.Interface != "snapd-themes-control" {
continue
}
connRef, err := interfaces.ParseConnRef(refStr)
Expand Down
87 changes: 86 additions & 1 deletion daemon/access_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package daemon_test

import (
"fmt"
"net/http"
"net/http/httptest"

Expand All @@ -34,7 +35,9 @@ import (
"github.com/snapcore/snapd/testutil"
)

type accessSuite struct{}
type accessSuite struct {
apiBaseSuite
}

var _ = Suite(&accessSuite{})

Expand Down Expand Up @@ -223,3 +226,85 @@ func (s *accessSuite) TestSnapAccess(c *C) {
c.Check(ac.CheckAccess(nil, nil, ucred, nil), DeepEquals, errForbidden)
c.Check(ac.CheckAccess(nil, nil, nil, nil), DeepEquals, errForbidden)
}

func (s *accessSuite) TestRequireThemeApiAccessImpl(c *C) {
d := s.daemon(c)
s.mockSnap(c, `
name: core
type: os
version: 1
slots:
snapd-themes-control:
`)
s.mockSnap(c, `
name: some-snap
version: 1
plugs:
snapd-themes-control:
`)

restore := daemon.MockCgroupSnapNameFromPid(func(pid int) (string, error) {
if pid == 42 {
return "some-snap", nil
}
return "", fmt.Errorf("not a snap")
})
defer restore()

var ac daemon.AccessChecker = daemon.ThemesOpenAccess{}

// Access with no ucred data is forbidden
c.Check(ac.CheckAccess(d, nil, nil, nil), DeepEquals, errForbidden)

// Access from snapd.socket is allowed
ucred := &daemon.Ucrednet{Uid: 1000, Pid: 1001, Socket: dirs.SnapdSocket}
c.Check(ac.CheckAccess(d, nil, ucred, nil), IsNil)

// Access from unknown sockets is forbidden
ucred = &daemon.Ucrednet{Uid: 1000, Pid: 1001, Socket: "unknown.socket"}
c.Check(ac.CheckAccess(d, nil, ucred, nil), DeepEquals, errForbidden)

// Access from snapd-snap.socket is rejected by default
ucred = &daemon.Ucrednet{Uid: 1000, Pid: 42, Socket: dirs.SnapSocket}
c.Check(ac.CheckAccess(d, nil, ucred, nil), DeepEquals, errForbidden)

// Now connect the marker interface
st := d.Overlord().State()
st.Lock()
st.Set("conns", map[string]interface{}{
"some-snap:snapd-themes-control core:snapd-themes-control": map[string]interface{}{
"interface": "snapd-themes-control",
},
})
st.Unlock()

// Access is allowed now that the snap has the plug connected
c.Check(ac.CheckAccess(s.d, nil, ucred, nil), IsNil)

// Access from pids that cannot be mapped to a snap on
// snapd-snap.socket are rejected
ucred = &daemon.Ucrednet{Uid: 1000, Pid: 1001, Socket: dirs.SnapSocket}
c.Check(ac.CheckAccess(d, nil, ucred, nil), DeepEquals, daemon.Forbidden("could not determine snap name for pid: not a snap"))
}

func (s *accessSuite) TestThemesOpenAccess(c *C) {
var ac daemon.AccessChecker = daemon.ThemesOpenAccess{}

s.daemon(c)
// themesOpenAccess allows access if requireThemeApiAccess() succeeds
ucred := &daemon.Ucrednet{Uid: 42, Pid: 100, Socket: dirs.SnapSocket}
restore := daemon.MockRequireThemeApiAccess(func(d *daemon.Daemon, u *daemon.Ucrednet) *daemon.APIError {
c.Check(d, Equals, s.d)
c.Check(u, Equals, ucred)
return nil
})
defer restore()
c.Check(ac.CheckAccess(s.d, nil, ucred, nil), IsNil)

// Access is forbidden if requireThemeApiAccess() fails
restore = daemon.MockRequireThemeApiAccess(func(d *daemon.Daemon, u *daemon.Ucrednet) *daemon.APIError {
return errForbidden
})
defer restore()
c.Check(ac.CheckAccess(s.d, nil, ucred, nil), DeepEquals, errForbidden)
}
19 changes: 19 additions & 0 deletions daemon/export_access_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type (
AuthenticatedAccess = authenticatedAccess
RootAccess = rootAccess
SnapAccess = snapAccess
ThemesOpenAccess = themesOpenAccess
)

var CheckPolkitActionImpl = checkPolkitActionImpl
Expand All @@ -51,3 +52,21 @@ func MockPolkitCheckAuthorization(new func(pid int32, uid uint32, actionId strin
polkitCheckAuthorization = old
}
}

func MockCgroupSnapNameFromPid(new func(pid int) (string, error)) (restore func()) {
old := cgroupSnapNameFromPid
cgroupSnapNameFromPid = new
return func() {
cgroupSnapNameFromPid = old
}
}

var RequireThemeApiAccessImpl = requireThemeApiAccessImpl

func MockRequireThemeApiAccess(new func(d *Daemon, ucred *ucrednet) *apiError) (restore func()) {
old := requireThemeApiAccess
requireThemeApiAccess = new
return func() {
requireThemeApiAccess = old
}
}

0 comments on commit ca054f2

Please sign in to comment.