Skip to content

Commit

Permalink
feat(daemon): Port accessChecker API from snapd
Browse files Browse the repository at this point in the history
  • Loading branch information
thp-canonical committed Feb 13, 2024
1 parent c658437 commit aa74f21
Show file tree
Hide file tree
Showing 6 changed files with 290 additions and 323 deletions.
60 changes: 60 additions & 0 deletions internals/daemon/access.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2021 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/>.
*
*/

// Based on: https://github.com/snapcore/snapd/blob/master/daemon/access.go

package daemon

import (
"net/http"
)

// accessChecker checks whether a particular request is allowed.
//
// An access checker will either allow a request (returns nil) or deny it.
type accessChecker interface {
CheckAccess(d *Daemon, r *http.Request, ucred *ucrednet, user *UserState) Response
}

// openAccess allows all requests
type openAccess struct{}

func (ac openAccess) CheckAccess(d *Daemon, r *http.Request, ucred *ucrednet, user *UserState) Response {
return nil
}

// rootAccess allows requests from the root uid
type rootAccess struct{}

func (ac rootAccess) CheckAccess(d *Daemon, r *http.Request, ucred *ucrednet, user *UserState) Response {
if ucred != nil && ucred.Uid == 0 {
return nil
}
return statusForbidden("access denied")
}

// userAccess allows requests 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 statusForbidden("access denied")
}
return nil
}
87 changes: 87 additions & 0 deletions internals/daemon/access_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2021 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/>.
*
*/

// Based on: https://github.com/snapcore/snapd/blob/master/daemon/access.go

package daemon_test

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

"github.com/canonical/pebble/internals/daemon"
)

type accessSuite struct {
}

var _ = Suite(&accessSuite{})

var (
errForbidden = daemon.StatusForbidden("access denied")
errUnauthorized = daemon.StatusUnauthorized("access denied")
)

const (
socketPath = "/tmp/foo.sock"
)

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, Socket: socketPath}
c.Check(ac.CheckAccess(nil, nil, ucred, nil), IsNil)

// openAccess allows access from root user
ucred = &daemon.Ucrednet{Uid: 0, Pid: 100, Socket: socketPath}
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, errForbidden)

// userAccess allows access from root user
ucred := &daemon.Ucrednet{Uid: 0, Pid: 100, Socket: socketPath}
c.Check(ac.CheckAccess(nil, nil, ucred, nil), IsNil)

// userAccess allows access form normal user
ucred = &daemon.Ucrednet{Uid: 42, Pid: 100, Socket: socketPath}
c.Check(ac.CheckAccess(nil, nil, ucred, nil), IsNil)
}

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

// rootAccess denies access without peer credentials.
c.Check(ac.CheckAccess(nil, nil, nil, nil), DeepEquals, errForbidden)

// Non-root users are forbidden
ucred := &daemon.Ucrednet{Uid: 42, Pid: 100, Socket: socketPath}
c.Check(ac.CheckAccess(nil, nil, ucred, nil), DeepEquals, errForbidden)

// Root is granted access
ucred = &daemon.Ucrednet{Uid: 0, Pid: 100, Socket: socketPath}
c.Check(ac.CheckAccess(nil, nil, ucred, nil), IsNil)
}
173 changes: 95 additions & 78 deletions internals/daemon/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,84 +25,101 @@ 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{},
WriteAccess: userAccess{},
GET: v1SystemInfo,
}, {
Path: "/v1/health",
ReadAccess: openAccess{},
WriteAccess: userAccess{},
GET: v1Health,
}, {
Path: "/v1/warnings",
ReadAccess: userAccess{},
WriteAccess: userAccess{},
GET: v1GetWarnings,
POST: v1AckWarnings,
}, {
Path: "/v1/changes",
ReadAccess: userAccess{},
WriteAccess: userAccess{},
GET: v1GetChanges,
}, {
Path: "/v1/changes/{id}",
ReadAccess: userAccess{},
WriteAccess: userAccess{},
GET: v1GetChange,
POST: v1PostChange,
}, {
Path: "/v1/changes/{id}/wait",
ReadAccess: userAccess{},
WriteAccess: 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{},
WriteAccess: userAccess{},
GET: v1GetPlan,
}, {
Path: "/v1/layers",
ReadAccess: userAccess{},
WriteAccess: userAccess{},
POST: v1PostLayers,
}, {
Path: "/v1/files",
ReadAccess: userAccess{},
WriteAccess: userAccess{},
GET: v1GetFiles,
POST: v1PostFiles,
}, {
Path: "/v1/logs",
ReadAccess: userAccess{},
WriteAccess: userAccess{},
GET: v1GetLogs,
}, {
Path: "/v1/exec",
ReadAccess: userAccess{},
WriteAccess: userAccess{},
POST: v1PostExec,
}, {
Path: "/v1/tasks/{task-id}/websocket/{websocket-id}",
ReadAccess: userAccess{},
WriteAccess: userAccess{},
GET: v1GetTaskWebsocket,
}, {
Path: "/v1/signals",
ReadAccess: userAccess{},
WriteAccess: userAccess{},
POST: v1PostSignals,
}, {
Path: "/v1/checks",
ReadAccess: userAccess{},
WriteAccess: userAccess{},
GET: v1GetChecks,
}, {
Path: "/v1/notices",
ReadAccess: userAccess{},
WriteAccess: userAccess{},
GET: v1GetNotices,
POST: v1PostNotices,
}, {
Path: "/v1/notices/{id}",
ReadAccess: userAccess{},
WriteAccess: userAccess{},
GET: v1GetNotice,
}}

var (
Expand Down
Loading

0 comments on commit aa74f21

Please sign in to comment.