diff --git a/internals/daemon/access.go b/internals/daemon/access.go
new file mode 100644
index 00000000..766bf694
--- /dev/null
+++ b/internals/daemon/access.go
@@ -0,0 +1,55 @@
+// Copyright (C) 2024 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 .
+
+package daemon
+
+import (
+ "net/http"
+ "os"
+)
+
+// AccessChecker checks whether a particular request is allowed.
+type AccessChecker interface {
+ // Check if access should be granted or denied. In case of granting access,
+ // return nil. In case access is denied, return a non-nil error response,
+ // such as Unauthorized("access denied").
+ CheckAccess(d *Daemon, r *http.Request, ucred *Ucrednet, user *UserState) Response
+}
+
+// OpenAccess allows all requests, including non-local sockets (e.g. TCP)
+type OpenAccess struct{}
+
+func (ac OpenAccess) CheckAccess(d *Daemon, r *http.Request, ucred *Ucrednet, user *UserState) Response {
+ return nil
+}
+
+// AdminAccess allows requests over the UNIX domain socket from the root uid and the current user's uid
+type AdminAccess struct{}
+
+func (ac AdminAccess) CheckAccess(d *Daemon, r *http.Request, ucred *Ucrednet, user *UserState) Response {
+ if ucred != nil && (ucred.Uid == 0 || ucred.Uid == uint32(os.Getuid())) {
+ return nil
+ }
+ return Unauthorized("access denied")
+}
+
+// UserAccess allows requests over the UNIX domain socket from any local user
+type UserAccess struct{}
+
+func (ac UserAccess) CheckAccess(d *Daemon, r *http.Request, ucred *Ucrednet, user *UserState) Response {
+ if ucred == nil {
+ return Unauthorized("access denied")
+ }
+ return nil
+}
diff --git a/internals/daemon/access_test.go b/internals/daemon/access_test.go
new file mode 100644
index 00000000..ff70eeea
--- /dev/null
+++ b/internals/daemon/access_test.go
@@ -0,0 +1,82 @@
+// Copyright (C) 2024 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 .
+
+package daemon_test
+
+import (
+ "os"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/canonical/pebble/internals/daemon"
+)
+
+type accessSuite struct {
+}
+
+var _ = Suite(&accessSuite{})
+
+var errUnauthorized = daemon.Unauthorized("access denied")
+
+func (s *accessSuite) TestOpenAccess(c *C) {
+ var ac daemon.AccessChecker = daemon.OpenAccess{}
+
+ // OpenAccess allows access without peer credentials.
+ c.Check(ac.CheckAccess(nil, nil, nil, nil), IsNil)
+
+ // OpenAccess allows access from normal user
+ ucred := &daemon.Ucrednet{Uid: 42, Pid: 100}
+ c.Check(ac.CheckAccess(nil, nil, ucred, nil), IsNil)
+
+ // OpenAccess allows access from root user
+ ucred = &daemon.Ucrednet{Uid: 0, Pid: 100}
+ c.Check(ac.CheckAccess(nil, nil, ucred, nil), IsNil)
+}
+
+func (s *accessSuite) TestUserAccess(c *C) {
+ var ac daemon.AccessChecker = daemon.UserAccess{}
+
+ // UserAccess denies access without peer credentials.
+ c.Check(ac.CheckAccess(nil, nil, nil, nil), DeepEquals, errUnauthorized)
+
+ // UserAccess allows access from root user
+ ucred := &daemon.Ucrednet{Uid: 0, Pid: 100}
+ c.Check(ac.CheckAccess(nil, nil, ucred, nil), IsNil)
+
+ // UserAccess allows access form normal user
+ ucred = &daemon.Ucrednet{Uid: 42, Pid: 100}
+ c.Check(ac.CheckAccess(nil, nil, ucred, nil), IsNil)
+}
+
+func (s *accessSuite) TestAdminAccess(c *C) {
+ var ac daemon.AccessChecker = daemon.AdminAccess{}
+
+ // AdminAccess denies access without peer credentials.
+ c.Check(ac.CheckAccess(nil, nil, nil, nil), DeepEquals, errUnauthorized)
+
+ // Current user's UID
+ uid := uint32(os.Getuid())
+
+ // Non-root users that are different from the current user are forbidden
+ ucred := &daemon.Ucrednet{Uid: uid + 1, Pid: 100}
+ c.Check(ac.CheckAccess(nil, nil, ucred, nil), DeepEquals, errUnauthorized)
+
+ // The current user is granted access
+ ucred = &daemon.Ucrednet{Uid: uid, Pid: 100}
+ c.Check(ac.CheckAccess(nil, nil, ucred, nil), IsNil)
+
+ // Root is granted access
+ ucred = &daemon.Ucrednet{Uid: 0, Pid: 100}
+ c.Check(ac.CheckAccess(nil, nil, ucred, nil), IsNil)
+}
diff --git a/internals/daemon/api.go b/internals/daemon/api.go
index 59db906a..16c1db6e 100644
--- a/internals/daemon/api.go
+++ b/internals/daemon/api.go
@@ -25,84 +25,89 @@ import (
)
var API = []*Command{{
- // See daemon.go:canAccess for details how the access is controlled.
- Path: "/v1/system-info",
- GuestOK: true,
- GET: v1SystemInfo,
-}, {
- Path: "/v1/health",
- GuestOK: true,
- GET: v1Health,
-}, {
- Path: "/v1/warnings",
- UserOK: true,
- GET: v1GetWarnings,
- POST: v1AckWarnings,
-}, {
- Path: "/v1/changes",
- UserOK: true,
- GET: v1GetChanges,
-}, {
- Path: "/v1/changes/{id}",
- UserOK: true,
- GET: v1GetChange,
- POST: v1PostChange,
-}, {
- Path: "/v1/changes/{id}/wait",
- UserOK: true,
- GET: v1GetChangeWait,
-}, {
- Path: "/v1/services",
- UserOK: true,
- GET: v1GetServices,
- POST: v1PostServices,
-}, {
- Path: "/v1/services/{name}",
- UserOK: true,
- GET: v1GetService,
- POST: v1PostService,
-}, {
- Path: "/v1/plan",
- UserOK: true,
- GET: v1GetPlan,
-}, {
- Path: "/v1/layers",
- UserOK: true,
- POST: v1PostLayers,
-}, {
- Path: "/v1/files",
- UserOK: true,
- GET: v1GetFiles,
- POST: v1PostFiles,
-}, {
- Path: "/v1/logs",
- UserOK: true,
- GET: v1GetLogs,
-}, {
- Path: "/v1/exec",
- UserOK: true,
- POST: v1PostExec,
-}, {
- Path: "/v1/tasks/{task-id}/websocket/{websocket-id}",
- UserOK: true,
- GET: v1GetTaskWebsocket,
-}, {
- Path: "/v1/signals",
- UserOK: true,
- POST: v1PostSignals,
-}, {
- Path: "/v1/checks",
- UserOK: true,
- GET: v1GetChecks,
-}, {
- Path: "/v1/notices",
- UserOK: true,
- GET: v1GetNotices,
- POST: v1PostNotices,
-}, {
- Path: "/v1/notices/{id}",
- UserOK: true,
- GET: v1GetNotice,
+ Path: "/v1/system-info",
+ ReadAccess: OpenAccess{},
+ GET: v1SystemInfo,
+}, {
+ Path: "/v1/health",
+ ReadAccess: OpenAccess{},
+ GET: v1Health,
+}, {
+ Path: "/v1/warnings",
+ ReadAccess: UserAccess{},
+ WriteAccess: UserAccess{},
+ GET: v1GetWarnings,
+ POST: v1AckWarnings,
+}, {
+ Path: "/v1/changes",
+ ReadAccess: UserAccess{},
+ GET: v1GetChanges,
+}, {
+ Path: "/v1/changes/{id}",
+ ReadAccess: UserAccess{},
+ WriteAccess: UserAccess{},
+ GET: v1GetChange,
+ POST: v1PostChange,
+}, {
+ Path: "/v1/changes/{id}/wait",
+ ReadAccess: UserAccess{},
+ GET: v1GetChangeWait,
+}, {
+ Path: "/v1/services",
+ ReadAccess: UserAccess{},
+ WriteAccess: UserAccess{},
+ GET: v1GetServices,
+ POST: v1PostServices,
+}, {
+ Path: "/v1/services/{name}",
+ ReadAccess: UserAccess{},
+ WriteAccess: UserAccess{},
+ GET: v1GetService,
+ POST: v1PostService,
+}, {
+ Path: "/v1/plan",
+ ReadAccess: UserAccess{},
+ GET: v1GetPlan,
+}, {
+ Path: "/v1/layers",
+ WriteAccess: UserAccess{},
+ POST: v1PostLayers,
+}, {
+ Path: "/v1/files",
+ ReadAccess: UserAccess{},
+ WriteAccess: UserAccess{},
+ GET: v1GetFiles,
+ POST: v1PostFiles,
+}, {
+ Path: "/v1/logs",
+ ReadAccess: UserAccess{},
+ GET: v1GetLogs,
+}, {
+ Path: "/v1/exec",
+ WriteAccess: UserAccess{},
+ POST: v1PostExec,
+}, {
+ Path: "/v1/tasks/{task-id}/websocket/{websocket-id}",
+ ReadAccess: UserAccess{},
+ GET: v1GetTaskWebsocket,
+}, {
+ Path: "/v1/signals",
+ WriteAccess: UserAccess{},
+ POST: v1PostSignals,
+}, {
+ Path: "/v1/checks",
+ ReadAccess: UserAccess{},
+ GET: v1GetChecks,
+}, {
+ Path: "/v1/notices",
+ ReadAccess: UserAccess{},
+ WriteAccess: UserAccess{},
+ GET: v1GetNotices,
+ POST: v1PostNotices,
+}, {
+ Path: "/v1/notices/{id}",
+ ReadAccess: UserAccess{},
+ GET: v1GetNotice,
}}
var (
diff --git a/internals/daemon/api_test.go b/internals/daemon/api_test.go
index 79e87ec1..4ba2da15 100644
--- a/internals/daemon/api_test.go
+++ b/internals/daemon/api_test.go
@@ -80,7 +80,6 @@ func (s *apiSuite) TestSysInfo(c *check.C) {
c.Assert(sysInfoCmd.GET, check.NotNil)
c.Check(sysInfoCmd.PUT, check.IsNil)
c.Check(sysInfoCmd.POST, check.IsNil)
- c.Check(sysInfoCmd.DELETE, check.IsNil)
rec := httptest.NewRecorder()
diff --git a/internals/daemon/daemon.go b/internals/daemon/daemon.go
index 6def7fe2..4973ac0b 100644
--- a/internals/daemon/daemon.go
+++ b/internals/daemon/daemon.go
@@ -121,13 +121,13 @@ type Command struct {
Path string
PathPrefix string
//
- GET ResponseFunc
- PUT ResponseFunc
- POST ResponseFunc
- DELETE ResponseFunc
- GuestOK bool
- UserOK bool
- AdminOnly bool
+ GET ResponseFunc
+ PUT ResponseFunc
+ POST ResponseFunc
+
+ // Access control.
+ ReadAccess AccessChecker
+ WriteAccess AccessChecker
d *Daemon
}
@@ -140,65 +140,6 @@ const (
accessForbidden
)
-// canAccess checks the following properties:
-//
-// - if the user is `root` everything is allowed
-// - if a user is logged in and the command doesn't have AdminOnly, everything is allowed
-// - POST/PUT/DELETE all require the admin, or just login if not AdminOnly
-//
-// Otherwise for GET requests the following parameters are honored:
-// - GuestOK: anyone can access GET
-// - UserOK: any uid on the local system can access GET
-// - AdminOnly: only the administrator can access this
-func (c *Command) canAccess(r *http.Request, user *UserState) accessResult {
- if c.AdminOnly && (c.UserOK || c.GuestOK) {
- logger.Panicf("internal error: command cannot have AdminOnly together with any *OK flag")
- }
-
- if user != nil && !c.AdminOnly {
- // Authenticated users do anything not requiring explicit admin.
- return accessOK
- }
-
- // isUser means we have a UID for the request
- isUser := false
- ucred, err := ucrednetGet(r.RemoteAddr)
- if err == nil {
- isUser = true
- } else if err != errNoID {
- logger.Noticef("Cannot parse UID from remote address %q: %s", r.RemoteAddr, err)
- return accessForbidden
- }
-
- // the !AdminOnly check is redundant, but belt-and-suspenders
- if r.Method == "GET" && !c.AdminOnly {
- // Guest and user access restricted to GET requests
- if c.GuestOK {
- return accessOK
- }
-
- if isUser && c.UserOK {
- return accessOK
- }
- }
-
- // Remaining admin checks rely on identifying peer uid
- if !isUser {
- return accessUnauthorized
- }
-
- if ucred.Uid == 0 || sys.UserID(ucred.Uid) == sysGetuid() {
- // Superuser and process owner can do anything.
- return accessOK
- }
-
- if c.AdminOnly {
- return accessUnauthorized
- }
-
- return accessUnauthorized
-}
-
func userFromRequest(state interface{}, r *http.Request) (*UserState, error) {
return nil, nil
}
@@ -224,35 +165,40 @@ func (c *Command) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
- switch c.canAccess(r, user) {
- case accessOK:
- // nothing
- case accessUnauthorized:
- Unauthorized("access denied").ServeHTTP(w, r)
- return
- case accessForbidden:
- Forbidden("forbidden").ServeHTTP(w, r)
+ ucred, err := ucrednetGet(r.RemoteAddr)
+ if err != nil && err != errNoID {
+ logger.Noticef("Cannot parse UID from remote address %q: %s", r.RemoteAddr, err)
+ InternalError(err.Error()).ServeHTTP(w, r)
return
}
var rspf ResponseFunc
- var rsp = MethodNotAllowed("method %q not allowed", r.Method)
+ var access AccessChecker
switch r.Method {
case "GET":
rspf = c.GET
+ access = c.ReadAccess
case "PUT":
rspf = c.PUT
+ access = c.WriteAccess
case "POST":
rspf = c.POST
- case "DELETE":
- rspf = c.DELETE
+ access = c.WriteAccess
+ }
+
+ if rspf == nil {
+ MethodNotAllowed("method %q not allowed", r.Method).ServeHTTP(w, r)
+ return
}
- if rspf != nil {
- rsp = rspf(c, r, user)
+ if rspe := access.CheckAccess(c.d, r, ucred, user); rspe != nil {
+ rspe.ServeHTTP(w, r)
+ return
}
+ rsp := rspf(c, r, user)
+
if rsp, ok := rsp.(*resp); ok {
st := c.d.state
st.Lock()
diff --git a/internals/daemon/daemon_test.go b/internals/daemon/daemon_test.go
index b04134a5..2172238f 100644
--- a/internals/daemon/daemon_test.go
+++ b/internals/daemon/daemon_test.go
@@ -174,9 +174,9 @@ func (s *daemonSuite) TestAddCommand(c *C) {
return &handler
}
command := Command{
- Path: endpoint,
- GuestOK: true,
- GET: getCallback,
+ Path: endpoint,
+ ReadAccess: OpenAccess{},
+ GET: getCallback,
}
API = append(API, &command)
defer func() {
@@ -218,9 +218,10 @@ func (s *daemonSuite) TestCommandMethodDispatch(c *C) {
cmd.GET = rf
cmd.PUT = rf
cmd.POST = rf
- cmd.DELETE = rf
+ cmd.ReadAccess = UserAccess{}
+ cmd.WriteAccess = UserAccess{}
- for _, method := range []string{"GET", "POST", "PUT", "DELETE"} {
+ for _, method := range []string{"GET", "POST", "PUT"} {
req, err := http.NewRequest(method, "", nil)
req.Header.Add("User-Agent", fakeUserAgent)
c.Assert(err, IsNil)
@@ -249,7 +250,7 @@ func (s *daemonSuite) TestCommandMethodDispatch(c *C) {
func (s *daemonSuite) TestCommandRestartingState(c *C) {
d := s.newDaemon(c)
- cmd := &Command{d: d}
+ cmd := &Command{d: d, ReadAccess: OpenAccess{}}
cmd.GET = func(*Command, *http.Request, *UserState) Response {
return SyncResponse(nil)
}
@@ -299,7 +300,7 @@ func (s *daemonSuite) TestCommandRestartingState(c *C) {
func (s *daemonSuite) TestFillsWarnings(c *C) {
d := s.newDaemon(c)
- cmd := &Command{d: d}
+ cmd := &Command{d: d, ReadAccess: OpenAccess{}}
cmd.GET = func(*Command, *http.Request, *UserState) Response {
return SyncResponse(nil)
}
@@ -333,109 +334,171 @@ func (s *daemonSuite) TestFillsWarnings(c *C) {
c.Check(rst.WarningTimestamp, NotNil)
}
-func (s *daemonSuite) TestGuestAccess(c *C) {
- d := s.newDaemon(c)
-
- get := &http.Request{Method: "GET"}
- put := &http.Request{Method: "PUT"}
- pst := &http.Request{Method: "POST"}
- del := &http.Request{Method: "DELETE"}
-
- cmd := &Command{d: d}
- c.Check(cmd.canAccess(get, nil), Equals, accessUnauthorized)
- c.Check(cmd.canAccess(put, nil), Equals, accessUnauthorized)
- c.Check(cmd.canAccess(pst, nil), Equals, accessUnauthorized)
- c.Check(cmd.canAccess(del, nil), Equals, accessUnauthorized)
-
- cmd = &Command{d: d, AdminOnly: true}
- c.Check(cmd.canAccess(get, nil), Equals, accessUnauthorized)
- c.Check(cmd.canAccess(put, nil), Equals, accessUnauthorized)
- c.Check(cmd.canAccess(pst, nil), Equals, accessUnauthorized)
- c.Check(cmd.canAccess(del, nil), Equals, accessUnauthorized)
-
- cmd = &Command{d: d, UserOK: true}
- c.Check(cmd.canAccess(get, nil), Equals, accessUnauthorized)
- c.Check(cmd.canAccess(put, nil), Equals, accessUnauthorized)
- c.Check(cmd.canAccess(pst, nil), Equals, accessUnauthorized)
- c.Check(cmd.canAccess(del, nil), Equals, accessUnauthorized)
-
- cmd = &Command{d: d, GuestOK: true}
- c.Check(cmd.canAccess(get, nil), Equals, accessOK)
- c.Check(cmd.canAccess(put, nil), Equals, accessUnauthorized)
- c.Check(cmd.canAccess(pst, nil), Equals, accessUnauthorized)
- c.Check(cmd.canAccess(del, nil), Equals, accessUnauthorized)
+type accessCheckerTestCase struct {
+ get, put, post int // expected status for each method
+ read, write AccessChecker
}
-func (s *daemonSuite) TestUserAccess(c *C) {
+func (s *daemonSuite) testAccessChecker(c *C, tests []accessCheckerTestCase, remoteAddr string) {
d := s.newDaemon(c)
- get := &http.Request{Method: "GET", RemoteAddr: "pid=100;uid=42;socket=;"}
- put := &http.Request{Method: "PUT", RemoteAddr: "pid=100;uid=42;socket=;"}
-
- cmd := &Command{d: d}
- c.Check(cmd.canAccess(get, nil), Equals, accessUnauthorized)
- c.Check(cmd.canAccess(put, nil), Equals, accessUnauthorized)
-
- cmd = &Command{d: d, AdminOnly: true}
- c.Check(cmd.canAccess(get, nil), Equals, accessUnauthorized)
- c.Check(cmd.canAccess(put, nil), Equals, accessUnauthorized)
+ responseFunc := func(c *Command, r *http.Request, s *UserState) Response {
+ return SyncResponse(true)
+ }
- cmd = &Command{d: d, UserOK: true}
- c.Check(cmd.canAccess(get, nil), Equals, accessOK)
- c.Check(cmd.canAccess(put, nil), Equals, accessUnauthorized)
+ doTestReqFunc := func(cmd *Command, mth string) *httptest.ResponseRecorder {
+ req := &http.Request{Method: mth, RemoteAddr: remoteAddr}
+ rec := httptest.NewRecorder()
+ cmd.ServeHTTP(rec, req)
+ return rec
+ }
- cmd = &Command{d: d, GuestOK: true}
- c.Check(cmd.canAccess(get, nil), Equals, accessOK)
- c.Check(cmd.canAccess(put, nil), Equals, accessUnauthorized)
-}
+ for _, t := range tests {
+ cmd := &Command{
+ d: d,
-func (s *daemonSuite) TestLoggedInUserAccess(c *C) {
- d := s.newDaemon(c)
+ GET: responseFunc,
+ PUT: responseFunc,
+ POST: responseFunc,
- user := &UserState{}
- get := &http.Request{Method: "GET", RemoteAddr: "pid=100;uid=42;socket=;"}
- put := &http.Request{Method: "PUT", RemoteAddr: "pid=100;uid=42;socket=;"}
+ ReadAccess: t.read,
+ WriteAccess: t.write,
+ }
- cmd := &Command{d: d}
- c.Check(cmd.canAccess(get, user), Equals, accessOK)
- c.Check(cmd.canAccess(put, user), Equals, accessOK)
+ comment := Commentf("remoteAddr: %v, read: %T, write: %T", remoteAddr, t.read, t.write)
- cmd = &Command{d: d, AdminOnly: true}
- c.Check(cmd.canAccess(get, user), Equals, accessUnauthorized)
- c.Check(cmd.canAccess(put, user), Equals, accessUnauthorized)
+ c.Check(doTestReqFunc(cmd, "GET").Code, Equals, t.get, comment)
+ c.Check(doTestReqFunc(cmd, "PUT").Code, Equals, t.put, comment)
+ c.Check(doTestReqFunc(cmd, "POST").Code, Equals, t.post, comment)
+ }
+}
- cmd = &Command{d: d, UserOK: true}
- c.Check(cmd.canAccess(get, user), Equals, accessOK)
- c.Check(cmd.canAccess(put, user), Equals, accessOK)
+func (s *daemonSuite) TestGuestAccess(c *C) {
+ tests := []accessCheckerTestCase{{
+ get: http.StatusOK,
+ put: http.StatusOK,
+ post: http.StatusOK,
+ read: OpenAccess{},
+ write: OpenAccess{},
+ }, {
+ get: http.StatusOK,
+ put: http.StatusUnauthorized,
+ post: http.StatusUnauthorized,
+ read: OpenAccess{},
+ write: UserAccess{},
+ }, {
+ get: http.StatusOK,
+ put: http.StatusUnauthorized,
+ post: http.StatusUnauthorized,
+ read: OpenAccess{},
+ write: AdminAccess{},
+ }, {
+ get: http.StatusUnauthorized,
+ put: http.StatusUnauthorized,
+ post: http.StatusUnauthorized,
+ read: UserAccess{},
+ write: UserAccess{},
+ }, {
+ get: http.StatusUnauthorized,
+ put: http.StatusUnauthorized,
+ post: http.StatusUnauthorized,
+ read: UserAccess{},
+ write: AdminAccess{},
+ }, {
+ get: http.StatusUnauthorized,
+ put: http.StatusUnauthorized,
+ post: http.StatusUnauthorized,
+ read: AdminAccess{},
+ write: AdminAccess{},
+ }}
+
+ s.testAccessChecker(c, tests, "")
+}
- cmd = &Command{d: d, GuestOK: true}
- c.Check(cmd.canAccess(get, user), Equals, accessOK)
- c.Check(cmd.canAccess(put, user), Equals, accessOK)
+func (s *daemonSuite) TestUserAccess(c *C) {
+ tests := []accessCheckerTestCase{{
+ get: http.StatusOK,
+ put: http.StatusOK,
+ post: http.StatusOK,
+ read: OpenAccess{},
+ write: OpenAccess{},
+ }, {
+ get: http.StatusOK,
+ put: http.StatusOK,
+ post: http.StatusOK,
+ read: OpenAccess{},
+ write: UserAccess{},
+ }, {
+ get: http.StatusOK,
+ put: http.StatusUnauthorized,
+ post: http.StatusUnauthorized,
+ read: OpenAccess{},
+ write: AdminAccess{},
+ }, {
+ get: http.StatusOK,
+ put: http.StatusOK,
+ post: http.StatusOK,
+ read: UserAccess{},
+ write: UserAccess{},
+ }, {
+ get: http.StatusOK,
+ put: http.StatusUnauthorized,
+ post: http.StatusUnauthorized,
+ read: UserAccess{},
+ write: AdminAccess{},
+ }, {
+ get: http.StatusUnauthorized,
+ put: http.StatusUnauthorized,
+ post: http.StatusUnauthorized,
+ read: AdminAccess{},
+ write: AdminAccess{},
+ }}
+
+ s.testAccessChecker(c, tests, "pid=100;uid=42;socket=;")
}
func (s *daemonSuite) TestSuperAccess(c *C) {
- d := s.newDaemon(c)
+ tests := []accessCheckerTestCase{{
+ get: http.StatusOK,
+ put: http.StatusOK,
+ post: http.StatusOK,
+ read: OpenAccess{},
+ write: OpenAccess{},
+ }, {
+ get: http.StatusOK,
+ put: http.StatusOK,
+ post: http.StatusOK,
+ read: OpenAccess{},
+ write: UserAccess{},
+ }, {
+ get: http.StatusOK,
+ put: http.StatusOK,
+ post: http.StatusOK,
+ read: OpenAccess{},
+ write: AdminAccess{},
+ }, {
+ get: http.StatusOK,
+ put: http.StatusOK,
+ post: http.StatusOK,
+ read: UserAccess{},
+ write: UserAccess{},
+ }, {
+ get: http.StatusOK,
+ put: http.StatusOK,
+ post: http.StatusOK,
+ read: UserAccess{},
+ write: AdminAccess{},
+ }, {
+ get: http.StatusOK,
+ put: http.StatusOK,
+ post: http.StatusOK,
+ read: AdminAccess{},
+ write: AdminAccess{},
+ }}
for _, uid := range []int{0, os.Getuid()} {
remoteAddr := fmt.Sprintf("pid=100;uid=%d;socket=;", uid)
- get := &http.Request{Method: "GET", RemoteAddr: remoteAddr}
- put := &http.Request{Method: "PUT", RemoteAddr: remoteAddr}
-
- cmd := &Command{d: d}
- c.Check(cmd.canAccess(get, nil), Equals, accessOK)
- c.Check(cmd.canAccess(put, nil), Equals, accessOK)
-
- cmd = &Command{d: d, AdminOnly: true}
- c.Check(cmd.canAccess(get, nil), Equals, accessOK)
- c.Check(cmd.canAccess(put, nil), Equals, accessOK)
-
- cmd = &Command{d: d, UserOK: true}
- c.Check(cmd.canAccess(get, nil), Equals, accessOK)
- c.Check(cmd.canAccess(put, nil), Equals, accessOK)
-
- cmd = &Command{d: d, GuestOK: true}
- c.Check(cmd.canAccess(get, nil), Equals, accessOK)
- c.Check(cmd.canAccess(put, nil), Equals, accessOK)
+ s.testAccessChecker(c, tests, remoteAddr)
}
}
@@ -1051,7 +1114,7 @@ func doTestReq(c *C, cmd *Command, mth string) *httptest.ResponseRecorder {
func (s *daemonSuite) TestDegradedModeReply(c *C) {
d := s.newDaemon(c)
- cmd := &Command{d: d}
+ cmd := &Command{d: d, ReadAccess: OpenAccess{}, WriteAccess: OpenAccess{}}
cmd.GET = func(*Command, *http.Request, *UserState) Response {
return SyncResponse(nil)
}
diff --git a/internals/daemon/ucrednet.go b/internals/daemon/ucrednet.go
index 2f640a0b..44eca740 100644
--- a/internals/daemon/ucrednet.go
+++ b/internals/daemon/ucrednet.go
@@ -35,11 +35,11 @@ const (
var raddrRegexp = regexp.MustCompile(`^pid=(\d+);uid=(\d+);socket=([^;]*);$`)
-func ucrednetGet(remoteAddr string) (*ucrednet, error) {
+func ucrednetGet(remoteAddr string) (*Ucrednet, error) {
// NOTE treat remoteAddr at one point included a user-controlled
// string. In case that happens again by accident, treat it as tainted,
// and be very suspicious of it.
- u := &ucrednet{
+ u := &Ucrednet{
Pid: ucrednetNoProcess,
Uid: ucrednetNobody,
}
@@ -60,13 +60,13 @@ func ucrednetGet(remoteAddr string) (*ucrednet, error) {
return u, nil
}
-type ucrednet struct {
+type Ucrednet struct {
Pid int32
Uid uint32
Socket string
}
-func (un *ucrednet) String() string {
+func (un *Ucrednet) String() string {
if un == nil {
return "pid=;uid=;socket=;"
}
@@ -75,23 +75,23 @@ func (un *ucrednet) String() string {
type ucrednetAddr struct {
net.Addr
- *ucrednet
+ *Ucrednet
}
func (wa *ucrednetAddr) String() string {
// NOTE we drop the original (user-supplied) net.Addr from the
// serialization entirely. We carry it this far so it helps debugging
// (via %#v logging), but from here on in it's not helpful.
- return wa.ucrednet.String()
+ return wa.Ucrednet.String()
}
type ucrednetConn struct {
net.Conn
- *ucrednet
+ *Ucrednet
}
func (wc *ucrednetConn) RemoteAddr() net.Addr {
- return &ucrednetAddr{wc.Conn.RemoteAddr(), wc.ucrednet}
+ return &ucrednetAddr{wc.Conn.RemoteAddr(), wc.Ucrednet}
}
type ucrednetListener struct {
@@ -109,7 +109,7 @@ func (wl *ucrednetListener) Accept() (net.Conn, error) {
return nil, err
}
- var unet *ucrednet
+ var unet *Ucrednet
if ucon, ok := con.(*net.UnixConn); ok {
rawConn, err := ucon.SyscallConn()
if err != nil {
@@ -128,7 +128,7 @@ func (wl *ucrednetListener) Accept() (net.Conn, error) {
if ucredErr != nil {
return nil, ucredErr
}
- unet = &ucrednet{
+ unet = &Ucrednet{
Pid: ucred.Pid,
Uid: ucred.Uid,
Socket: ucon.LocalAddr().String(),