Skip to content

Commit

Permalink
Merge pull request #12313 from markylaing/authorization-refactor
Browse files Browse the repository at this point in the history
Authorization refactor in preparation for fine-grained authorization
  • Loading branch information
tomponline authored Oct 25, 2023
2 parents b70af78 + 7c9f699 commit 20efe1b
Show file tree
Hide file tree
Showing 54 changed files with 2,847 additions and 703 deletions.
2 changes: 1 addition & 1 deletion lxd-agent/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func eventsSocket(d *Daemon, r *http.Request, w http.ResponseWriter) error {
}

// As we don't know which project we are in, subscribe to events from all projects.
listener, err := d.events.AddListener("", true, listenerConnection, strings.Split(typeStr, ","), nil, nil, nil)
listener, err := d.events.AddListener("", true, nil, listenerConnection, strings.Split(typeStr, ","), nil, nil, nil)
if err != nil {
return err
}
Expand Down
16 changes: 13 additions & 3 deletions lxd/api_1.0.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"strings"

"github.com/canonical/lxd/client"
"github.com/canonical/lxd/lxd/auth"
"github.com/canonical/lxd/lxd/auth/candid"
"github.com/canonical/lxd/lxd/auth/oidc"
"github.com/canonical/lxd/lxd/cluster"
Expand All @@ -33,8 +34,8 @@ import (

var api10Cmd = APIEndpoint{
Get: APIEndpointAction{Handler: api10Get, AllowUntrusted: true},
Patch: APIEndpointAction{Handler: api10Patch},
Put: APIEndpointAction{Handler: api10Put},
Patch: APIEndpointAction{Handler: api10Patch, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
Put: APIEndpointAction{Handler: api10Put, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

var api10 = []APIEndpoint{
Expand Down Expand Up @@ -235,6 +236,12 @@ func api10Get(d *Daemon, r *http.Request) response.Response {
return response.SyncResponseETag(true, srv, nil)
}

// If not authorized, return now.
err := s.Authorizer.CheckPermission(r.Context(), r, auth.ObjectServer(), auth.EntitlementCanView)
if err != nil {
return response.SmartError(err)
}

// If a target was specified, forward the request to the relevant node.
resp := forwardedResponseIfTargetIsRemote(s, r)
if resp != nil {
Expand Down Expand Up @@ -375,11 +382,14 @@ func api10Get(d *Daemon, r *http.Request) response.Response {
fullSrv.AuthUserName = requestor.Username
fullSrv.AuthUserMethod = requestor.Protocol

if s.Authorizer.UserIsAdmin(r) {
err = s.Authorizer.CheckPermission(r.Context(), r, auth.ObjectServer(), auth.EntitlementCanEdit)
if err == nil {
fullSrv.Config, err = daemonConfigRender(s)
if err != nil {
return response.InternalError(err)
}
} else if !api.StatusErrorCheck(err, http.StatusForbidden) {
return response.SmartError(err)
}

return response.SyncResponseETag(true, fullSrv, fullSrv.Config)
Expand Down
51 changes: 26 additions & 25 deletions lxd/api_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (

"github.com/canonical/lxd/client"
"github.com/canonical/lxd/lxd/acme"
"github.com/canonical/lxd/lxd/auth"
"github.com/canonical/lxd/lxd/certificate"
"github.com/canonical/lxd/lxd/cluster"
clusterConfig "github.com/canonical/lxd/lxd/cluster/config"
Expand Down Expand Up @@ -70,91 +71,91 @@ var targetGroupPrefix = "@"
var clusterCmd = APIEndpoint{
Path: "cluster",

Get: APIEndpointAction{Handler: clusterGet, AccessHandler: allowAuthenticated},
Put: APIEndpointAction{Handler: clusterPut},
Get: APIEndpointAction{Handler: clusterGet, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanView)},
Put: APIEndpointAction{Handler: clusterPut, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

var clusterNodesCmd = APIEndpoint{
Path: "cluster/members",

Get: APIEndpointAction{Handler: clusterNodesGet, AccessHandler: allowAuthenticated},
Post: APIEndpointAction{Handler: clusterNodesPost},
Get: APIEndpointAction{Handler: clusterNodesGet, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanView)},
Post: APIEndpointAction{Handler: clusterNodesPost, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

var clusterNodeCmd = APIEndpoint{
Path: "cluster/members/{name}",

Delete: APIEndpointAction{Handler: clusterNodeDelete},
Get: APIEndpointAction{Handler: clusterNodeGet, AccessHandler: allowAuthenticated},
Patch: APIEndpointAction{Handler: clusterNodePatch},
Put: APIEndpointAction{Handler: clusterNodePut},
Post: APIEndpointAction{Handler: clusterNodePost},
Delete: APIEndpointAction{Handler: clusterNodeDelete, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
Get: APIEndpointAction{Handler: clusterNodeGet, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanView)},
Patch: APIEndpointAction{Handler: clusterNodePatch, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
Put: APIEndpointAction{Handler: clusterNodePut, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
Post: APIEndpointAction{Handler: clusterNodePost, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

var clusterNodeStateCmd = APIEndpoint{
Path: "cluster/members/{name}/state",

Get: APIEndpointAction{Handler: clusterNodeStateGet, AccessHandler: allowAuthenticated},
Post: APIEndpointAction{Handler: clusterNodeStatePost},
Get: APIEndpointAction{Handler: clusterNodeStateGet, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanView)},
Post: APIEndpointAction{Handler: clusterNodeStatePost, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

var clusterCertificateCmd = APIEndpoint{
Path: "cluster/certificate",

Put: APIEndpointAction{Handler: clusterCertificatePut},
Put: APIEndpointAction{Handler: clusterCertificatePut, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

var clusterGroupsCmd = APIEndpoint{
Path: "cluster/groups",

Get: APIEndpointAction{Handler: clusterGroupsGet, AccessHandler: allowAuthenticated},
Post: APIEndpointAction{Handler: clusterGroupsPost},
Get: APIEndpointAction{Handler: clusterGroupsGet, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanView)},
Post: APIEndpointAction{Handler: clusterGroupsPost, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

var clusterGroupCmd = APIEndpoint{
Path: "cluster/groups/{name}",

Get: APIEndpointAction{Handler: clusterGroupGet, AccessHandler: allowAuthenticated},
Post: APIEndpointAction{Handler: clusterGroupPost},
Put: APIEndpointAction{Handler: clusterGroupPut},
Patch: APIEndpointAction{Handler: clusterGroupPatch},
Delete: APIEndpointAction{Handler: clusterGroupDelete},
Get: APIEndpointAction{Handler: clusterGroupGet, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanView)},
Post: APIEndpointAction{Handler: clusterGroupPost, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
Put: APIEndpointAction{Handler: clusterGroupPut, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
Patch: APIEndpointAction{Handler: clusterGroupPatch, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
Delete: APIEndpointAction{Handler: clusterGroupDelete, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

var internalClusterAcceptCmd = APIEndpoint{
Path: "cluster/accept",

Post: APIEndpointAction{Handler: internalClusterPostAccept},
Post: APIEndpointAction{Handler: internalClusterPostAccept, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

var internalClusterRebalanceCmd = APIEndpoint{
Path: "cluster/rebalance",

Post: APIEndpointAction{Handler: internalClusterPostRebalance},
Post: APIEndpointAction{Handler: internalClusterPostRebalance, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

var internalClusterAssignCmd = APIEndpoint{
Path: "cluster/assign",

Post: APIEndpointAction{Handler: internalClusterPostAssign},
Post: APIEndpointAction{Handler: internalClusterPostAssign, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

var internalClusterHandoverCmd = APIEndpoint{
Path: "cluster/handover",

Post: APIEndpointAction{Handler: internalClusterPostHandover},
Post: APIEndpointAction{Handler: internalClusterPostHandover, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

var internalClusterRaftNodeCmd = APIEndpoint{
Path: "cluster/raft-node/{address}",

Delete: APIEndpointAction{Handler: internalClusterRaftNodeDelete},
Delete: APIEndpointAction{Handler: internalClusterRaftNodeDelete, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

var internalClusterHealCmd = APIEndpoint{
Path: "cluster/heal/{name}",

Post: APIEndpointAction{Handler: internalClusterHeal},
Post: APIEndpointAction{Handler: internalClusterHeal, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

// swagger:operation GET /1.0/cluster cluster cluster_get
Expand Down
27 changes: 14 additions & 13 deletions lxd/api_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/gorilla/mux"
"golang.org/x/sys/unix"

"github.com/canonical/lxd/lxd/auth"
"github.com/canonical/lxd/lxd/backup"
"github.com/canonical/lxd/lxd/db"
"github.com/canonical/lxd/lxd/db/cluster"
Expand Down Expand Up @@ -65,74 +66,74 @@ var apiInternal = []APIEndpoint{
var internalShutdownCmd = APIEndpoint{
Path: "shutdown",

Put: APIEndpointAction{Handler: internalShutdown},
Put: APIEndpointAction{Handler: internalShutdown, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

var internalReadyCmd = APIEndpoint{
Path: "ready",

Get: APIEndpointAction{Handler: internalWaitReady},
Get: APIEndpointAction{Handler: internalWaitReady, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

var internalContainerOnStartCmd = APIEndpoint{
Path: "containers/{instanceRef}/onstart",

Get: APIEndpointAction{Handler: internalContainerOnStart},
Get: APIEndpointAction{Handler: internalContainerOnStart, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

var internalContainerOnStopNSCmd = APIEndpoint{
Path: "containers/{instanceRef}/onstopns",

Get: APIEndpointAction{Handler: internalContainerOnStopNS},
Get: APIEndpointAction{Handler: internalContainerOnStopNS, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

var internalContainerOnStopCmd = APIEndpoint{
Path: "containers/{instanceRef}/onstop",

Get: APIEndpointAction{Handler: internalContainerOnStop},
Get: APIEndpointAction{Handler: internalContainerOnStop, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

var internalSQLCmd = APIEndpoint{
Path: "sql",

Get: APIEndpointAction{Handler: internalSQLGet},
Post: APIEndpointAction{Handler: internalSQLPost},
Get: APIEndpointAction{Handler: internalSQLGet, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
Post: APIEndpointAction{Handler: internalSQLPost, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

var internalGarbageCollectorCmd = APIEndpoint{
Path: "gc",

Get: APIEndpointAction{Handler: internalGC},
Get: APIEndpointAction{Handler: internalGC, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

var internalRAFTSnapshotCmd = APIEndpoint{
Path: "raft-snapshot",

Get: APIEndpointAction{Handler: internalRAFTSnapshot},
Get: APIEndpointAction{Handler: internalRAFTSnapshot, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

var internalImageRefreshCmd = APIEndpoint{
Path: "testing/image-refresh",

Get: APIEndpointAction{Handler: internalRefreshImage},
Get: APIEndpointAction{Handler: internalRefreshImage, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

var internalImageOptimizeCmd = APIEndpoint{
Path: "image-optimize",

Post: APIEndpointAction{Handler: internalOptimizeImage},
Post: APIEndpointAction{Handler: internalOptimizeImage, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

var internalWarningCreateCmd = APIEndpoint{
Path: "testing/warnings",

Post: APIEndpointAction{Handler: internalCreateWarning},
Post: APIEndpointAction{Handler: internalCreateWarning, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

var internalBGPStateCmd = APIEndpoint{
Path: "testing/bgp",

Get: APIEndpointAction{Handler: internalBGPState},
Get: APIEndpointAction{Handler: internalBGPState, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

type internalImageOptimizePost struct {
Expand Down
5 changes: 3 additions & 2 deletions lxd/api_internal_recover.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"net/http"

"github.com/canonical/lxd/lxd/auth"
"github.com/canonical/lxd/lxd/backup"
backupConfig "github.com/canonical/lxd/lxd/backup/config"
"github.com/canonical/lxd/lxd/cluster"
Expand All @@ -31,13 +32,13 @@ import (
var internalRecoverValidateCmd = APIEndpoint{
Path: "recover/validate",

Post: APIEndpointAction{Handler: internalRecoverValidate},
Post: APIEndpointAction{Handler: internalRecoverValidate, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

var internalRecoverImportCmd = APIEndpoint{
Path: "recover/import",

Post: APIEndpointAction{Handler: internalRecoverImport},
Post: APIEndpointAction{Handler: internalRecoverImport, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)},
}

// init recover adds API endpoints to handler slice.
Expand Down
31 changes: 26 additions & 5 deletions lxd/api_metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"sync"
"time"

"github.com/canonical/lxd/lxd/auth"
"github.com/canonical/lxd/lxd/db"
dbCluster "github.com/canonical/lxd/lxd/db/cluster"
"github.com/canonical/lxd/lxd/instance"
Expand All @@ -19,6 +20,7 @@ import (
"github.com/canonical/lxd/lxd/metrics"
"github.com/canonical/lxd/lxd/request"
"github.com/canonical/lxd/lxd/response"
"github.com/canonical/lxd/lxd/state"
"github.com/canonical/lxd/shared"
"github.com/canonical/lxd/shared/api"
"github.com/canonical/lxd/shared/logger"
Expand All @@ -41,13 +43,11 @@ var metricsCmd = APIEndpoint{
func allowMetrics(d *Daemon, r *http.Request) response.Response {
s := d.State()

// Check if API is wide open.
if !s.GlobalConfig.MetricsAuthentication() {
return response.EmptySyncResponse
}

// If not wide open, apply project access restrictions.
return allowProjectPermission("containers", "view")(d, r)
return allowPermission(auth.ObjectTypeServer, auth.EntitlementCanViewMetrics)(d, r)
}

// swagger:operation GET /1.0/metrics metrics metrics_get
Expand Down Expand Up @@ -158,7 +158,7 @@ func metricsGet(d *Daemon, r *http.Request) response.Response {

// If all valid, return immediately.
if len(projectsToFetch) == 0 {
return response.SyncResponsePlain(true, compress, metricSet.String())
return getFilteredMetrics(s, r, compress, metricSet)
}

cacheDuration := time.Duration(8) * time.Second
Expand All @@ -183,7 +183,7 @@ func metricsGet(d *Daemon, r *http.Request) response.Response {

// If all valid, return immediately.
if len(projectsToFetch) == 0 {
return response.SyncResponsePlain(true, compress, metricSet.String())
return getFilteredMetrics(s, r, compress, metricSet)
}

// Gather information about host interfaces once.
Expand Down Expand Up @@ -286,6 +286,27 @@ func metricsGet(d *Daemon, r *http.Request) response.Response {

metricsCacheLock.Unlock()

return getFilteredMetrics(s, r, compress, metricSet)
}

func getFilteredMetrics(s *state.State, r *http.Request, compress bool, metricSet *metrics.MetricSet) response.Response {
if !s.GlobalConfig.MetricsAuthentication() {
return response.SyncResponsePlain(true, compress, metricSet.String())
}

// Get instances the user is allowed to view.
userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, auth.ObjectTypeInstance)
if err != nil && !api.StatusErrorCheck(err, http.StatusForbidden) {
return response.SmartError(err)
} else if err != nil {
// This is counterintuitive. We are unauthorized to get a permission checker for viewing instances because a metric type certificate
// can't view instances. However, in order to get to this point we must already have auth.EntitlementCanViewMetrics. So we can view
// the metrics but we can't do any filtering, so just return the metrics.
return response.SyncResponsePlain(true, compress, metricSet.String())
}

metricSet.FilterSamples(userHasPermission)

return response.SyncResponsePlain(true, compress, metricSet.String())
}

Expand Down
Loading

0 comments on commit 20efe1b

Please sign in to comment.