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(),