diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go index f79c6003f641..9fa1f18aa833 100644 --- a/lxd/api_1.0.go +++ b/lxd/api_1.0.go @@ -15,6 +15,7 @@ import ( clusterConfig "github.com/canonical/lxd/lxd/cluster/config" "github.com/canonical/lxd/lxd/config" "github.com/canonical/lxd/lxd/db" + "github.com/canonical/lxd/lxd/entity" instanceDrivers "github.com/canonical/lxd/lxd/instance/drivers" "github.com/canonical/lxd/lxd/lifecycle" "github.com/canonical/lxd/lxd/node" @@ -32,8 +33,8 @@ import ( var api10Cmd = APIEndpoint{ Get: APIEndpointAction{Handler: api10Get, AllowUntrusted: true}, - Patch: APIEndpointAction{Handler: api10Patch, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, - Put: APIEndpointAction{Handler: api10Put, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Patch: APIEndpointAction{Handler: api10Patch, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, + Put: APIEndpointAction{Handler: api10Put, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } var api10 = []APIEndpoint{ @@ -230,7 +231,7 @@ func api10Get(d *Daemon, r *http.Request) response.Response { } // If not authorized, return now. - err := s.Authorizer.CheckPermission(r.Context(), r, auth.ObjectServer(), auth.EntitlementCanView) + err := s.Authorizer.CheckPermission(r.Context(), r, entity.ServerURL(), auth.EntitlementCanView) if err != nil { return response.SmartError(err) } @@ -376,7 +377,7 @@ func api10Get(d *Daemon, r *http.Request) response.Response { fullSrv.AuthUserName = requestor.Username fullSrv.AuthUserMethod = requestor.Protocol - err = s.Authorizer.CheckPermission(r.Context(), r, auth.ObjectServer(), auth.EntitlementCanEdit) + err = s.Authorizer.CheckPermission(r.Context(), r, entity.ServerURL(), auth.EntitlementCanViewConfiguration) if err == nil { fullSrv.Config, err = daemonConfigRender(s) if err != nil { diff --git a/lxd/api_cluster.go b/lxd/api_cluster.go index 6347235acf5f..ab819fe5b23b 100644 --- a/lxd/api_cluster.go +++ b/lxd/api_cluster.go @@ -28,6 +28,7 @@ import ( dbCluster "github.com/canonical/lxd/lxd/db/cluster" "github.com/canonical/lxd/lxd/db/operationtype" "github.com/canonical/lxd/lxd/db/warningtype" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/instance" instanceDrivers "github.com/canonical/lxd/lxd/instance/drivers" "github.com/canonical/lxd/lxd/instance/instancetype" @@ -72,91 +73,91 @@ var targetGroupPrefix = "@" var clusterCmd = APIEndpoint{ Path: "cluster", - Get: APIEndpointAction{Handler: clusterGet, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanView)}, - Put: APIEndpointAction{Handler: clusterPut, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Get: APIEndpointAction{Handler: clusterGet, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanView)}, + Put: APIEndpointAction{Handler: clusterPut, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } var clusterNodesCmd = APIEndpoint{ Path: "cluster/members", - Get: APIEndpointAction{Handler: clusterNodesGet, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanView)}, - Post: APIEndpointAction{Handler: clusterNodesPost, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Get: APIEndpointAction{Handler: clusterNodesGet, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanView)}, + Post: APIEndpointAction{Handler: clusterNodesPost, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } var clusterNodeCmd = APIEndpoint{ Path: "cluster/members/{name}", - 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)}, + Delete: APIEndpointAction{Handler: clusterNodeDelete, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, + Get: APIEndpointAction{Handler: clusterNodeGet, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanView)}, + Patch: APIEndpointAction{Handler: clusterNodePatch, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, + Put: APIEndpointAction{Handler: clusterNodePut, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, + Post: APIEndpointAction{Handler: clusterNodePost, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } var clusterNodeStateCmd = APIEndpoint{ Path: "cluster/members/{name}/state", - Get: APIEndpointAction{Handler: clusterNodeStateGet, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanView)}, - Post: APIEndpointAction{Handler: clusterNodeStatePost, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Get: APIEndpointAction{Handler: clusterNodeStateGet, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanView)}, + Post: APIEndpointAction{Handler: clusterNodeStatePost, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } var clusterCertificateCmd = APIEndpoint{ Path: "cluster/certificate", - Put: APIEndpointAction{Handler: clusterCertificatePut, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Put: APIEndpointAction{Handler: clusterCertificatePut, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } var clusterGroupsCmd = APIEndpoint{ Path: "cluster/groups", - Get: APIEndpointAction{Handler: clusterGroupsGet, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanView)}, - Post: APIEndpointAction{Handler: clusterGroupsPost, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Get: APIEndpointAction{Handler: clusterGroupsGet, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanView)}, + Post: APIEndpointAction{Handler: clusterGroupsPost, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } var clusterGroupCmd = APIEndpoint{ Path: "cluster/groups/{name}", - 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)}, + Get: APIEndpointAction{Handler: clusterGroupGet, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanView)}, + Post: APIEndpointAction{Handler: clusterGroupPost, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, + Put: APIEndpointAction{Handler: clusterGroupPut, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, + Patch: APIEndpointAction{Handler: clusterGroupPatch, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, + Delete: APIEndpointAction{Handler: clusterGroupDelete, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } var internalClusterAcceptCmd = APIEndpoint{ Path: "cluster/accept", - Post: APIEndpointAction{Handler: internalClusterPostAccept, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Post: APIEndpointAction{Handler: internalClusterPostAccept, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } var internalClusterRebalanceCmd = APIEndpoint{ Path: "cluster/rebalance", - Post: APIEndpointAction{Handler: internalClusterPostRebalance, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Post: APIEndpointAction{Handler: internalClusterPostRebalance, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } var internalClusterAssignCmd = APIEndpoint{ Path: "cluster/assign", - Post: APIEndpointAction{Handler: internalClusterPostAssign, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Post: APIEndpointAction{Handler: internalClusterPostAssign, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } var internalClusterHandoverCmd = APIEndpoint{ Path: "cluster/handover", - Post: APIEndpointAction{Handler: internalClusterPostHandover, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Post: APIEndpointAction{Handler: internalClusterPostHandover, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } var internalClusterRaftNodeCmd = APIEndpoint{ Path: "cluster/raft-node/{address}", - Delete: APIEndpointAction{Handler: internalClusterRaftNodeDelete, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Delete: APIEndpointAction{Handler: internalClusterRaftNodeDelete, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } var internalClusterHealCmd = APIEndpoint{ Path: "cluster/heal/{name}", - Post: APIEndpointAction{Handler: internalClusterHeal, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Post: APIEndpointAction{Handler: internalClusterHeal, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } // swagger:operation GET /1.0/cluster cluster cluster_get diff --git a/lxd/api_internal.go b/lxd/api_internal.go index fdac2799126f..8fbb4db611ac 100644 --- a/lxd/api_internal.go +++ b/lxd/api_internal.go @@ -25,6 +25,7 @@ import ( "github.com/canonical/lxd/lxd/db/query" "github.com/canonical/lxd/lxd/db/warningtype" deviceConfig "github.com/canonical/lxd/lxd/device/config" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/instance" "github.com/canonical/lxd/lxd/instance/instancetype" "github.com/canonical/lxd/lxd/project" @@ -35,7 +36,6 @@ import ( storageDrivers "github.com/canonical/lxd/lxd/storage/drivers" "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" - "github.com/canonical/lxd/shared/entity" "github.com/canonical/lxd/shared/logger" "github.com/canonical/lxd/shared/osarch" "github.com/canonical/lxd/shared/revert" @@ -66,74 +66,74 @@ var apiInternal = []APIEndpoint{ var internalShutdownCmd = APIEndpoint{ Path: "shutdown", - Put: APIEndpointAction{Handler: internalShutdown, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Put: APIEndpointAction{Handler: internalShutdown, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } var internalReadyCmd = APIEndpoint{ Path: "ready", - Get: APIEndpointAction{Handler: internalWaitReady, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Get: APIEndpointAction{Handler: internalWaitReady, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } var internalContainerOnStartCmd = APIEndpoint{ Path: "containers/{instanceRef}/onstart", - Get: APIEndpointAction{Handler: internalContainerOnStart, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Get: APIEndpointAction{Handler: internalContainerOnStart, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } var internalContainerOnStopNSCmd = APIEndpoint{ Path: "containers/{instanceRef}/onstopns", - Get: APIEndpointAction{Handler: internalContainerOnStopNS, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Get: APIEndpointAction{Handler: internalContainerOnStopNS, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } var internalContainerOnStopCmd = APIEndpoint{ Path: "containers/{instanceRef}/onstop", - Get: APIEndpointAction{Handler: internalContainerOnStop, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Get: APIEndpointAction{Handler: internalContainerOnStop, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } var internalSQLCmd = APIEndpoint{ Path: "sql", - Get: APIEndpointAction{Handler: internalSQLGet, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, - Post: APIEndpointAction{Handler: internalSQLPost, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Get: APIEndpointAction{Handler: internalSQLGet, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, + Post: APIEndpointAction{Handler: internalSQLPost, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } var internalGarbageCollectorCmd = APIEndpoint{ Path: "gc", - Get: APIEndpointAction{Handler: internalGC, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Get: APIEndpointAction{Handler: internalGC, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } var internalRAFTSnapshotCmd = APIEndpoint{ Path: "raft-snapshot", - Get: APIEndpointAction{Handler: internalRAFTSnapshot, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Get: APIEndpointAction{Handler: internalRAFTSnapshot, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } var internalImageRefreshCmd = APIEndpoint{ Path: "testing/image-refresh", - Get: APIEndpointAction{Handler: internalRefreshImage, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Get: APIEndpointAction{Handler: internalRefreshImage, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } var internalImageOptimizeCmd = APIEndpoint{ Path: "image-optimize", - Post: APIEndpointAction{Handler: internalOptimizeImage, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Post: APIEndpointAction{Handler: internalOptimizeImage, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } var internalWarningCreateCmd = APIEndpoint{ Path: "testing/warnings", - Post: APIEndpointAction{Handler: internalCreateWarning, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Post: APIEndpointAction{Handler: internalCreateWarning, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } var internalBGPStateCmd = APIEndpoint{ Path: "testing/bgp", - Get: APIEndpointAction{Handler: internalBGPState, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Get: APIEndpointAction{Handler: internalBGPState, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } type internalImageOptimizePost struct { diff --git a/lxd/api_internal_recover.go b/lxd/api_internal_recover.go index 058366ec99db..edc298739863 100644 --- a/lxd/api_internal_recover.go +++ b/lxd/api_internal_recover.go @@ -13,6 +13,7 @@ import ( "github.com/canonical/lxd/lxd/db" dbCluster "github.com/canonical/lxd/lxd/db/cluster" deviceConfig "github.com/canonical/lxd/lxd/device/config" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/instance" "github.com/canonical/lxd/lxd/instance/instancetype" "github.com/canonical/lxd/lxd/project" @@ -31,13 +32,13 @@ import ( var internalRecoverValidateCmd = APIEndpoint{ Path: "recover/validate", - Post: APIEndpointAction{Handler: internalRecoverValidate, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Post: APIEndpointAction{Handler: internalRecoverValidate, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } var internalRecoverImportCmd = APIEndpoint{ Path: "recover/import", - Post: APIEndpointAction{Handler: internalRecoverImport, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Post: APIEndpointAction{Handler: internalRecoverImport, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } // init recover adds API endpoints to handler slice. diff --git a/lxd/api_metrics.go b/lxd/api_metrics.go index 2512d4d832d0..13df257a4f0c 100644 --- a/lxd/api_metrics.go +++ b/lxd/api_metrics.go @@ -14,6 +14,7 @@ import ( "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/entity" "github.com/canonical/lxd/lxd/instance" instanceDrivers "github.com/canonical/lxd/lxd/instance/drivers" "github.com/canonical/lxd/lxd/instance/instancetype" @@ -48,7 +49,7 @@ func allowMetrics(d *Daemon, r *http.Request) response.Response { return response.EmptySyncResponse } - return allowPermission(auth.ObjectTypeServer, auth.EntitlementCanViewMetrics)(d, r) + return allowPermission(entity.TypeServer, auth.EntitlementCanViewMetrics)(d, r) } // swagger:operation GET /1.0/metrics metrics metrics_get @@ -325,7 +326,7 @@ func getFilteredMetrics(s *state.State, r *http.Request, compress bool, metricSe } // Get instances the user is allowed to view. - userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, auth.ObjectTypeInstance) + userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, entity.TypeInstance) if err != nil && !api.StatusErrorCheck(err, http.StatusForbidden) { return response.SmartError(err) } else if err != nil { @@ -338,7 +339,7 @@ func getFilteredMetrics(s *state.State, r *http.Request, compress bool, metricSe // Filter by project name and instance name. metricSet.FilterSamples(userHasPermission) - userHasPermission, err = s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, auth.ObjectTypeProject) + userHasPermission, err = s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, entity.TypeProject) if err != nil { return response.SmartError(err) } diff --git a/lxd/api_project.go b/lxd/api_project.go index b87b5c974f43..7dd3b774e3fc 100644 --- a/lxd/api_project.go +++ b/lxd/api_project.go @@ -17,6 +17,7 @@ import ( "github.com/canonical/lxd/lxd/db" "github.com/canonical/lxd/lxd/db/cluster" "github.com/canonical/lxd/lxd/db/operationtype" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/lifecycle" "github.com/canonical/lxd/lxd/network" "github.com/canonical/lxd/lxd/operations" @@ -36,23 +37,23 @@ var projectsCmd = APIEndpoint{ Path: "projects", Get: APIEndpointAction{Handler: projectsGet, AccessHandler: allowAuthenticated}, - Post: APIEndpointAction{Handler: projectsPost, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanCreateProjects)}, + Post: APIEndpointAction{Handler: projectsPost, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanCreateProjects)}, } var projectCmd = APIEndpoint{ Path: "projects/{name}", - Delete: APIEndpointAction{Handler: projectDelete, AccessHandler: allowPermission(auth.ObjectTypeProject, auth.EntitlementCanEdit, "name")}, - Get: APIEndpointAction{Handler: projectGet, AccessHandler: allowPermission(auth.ObjectTypeProject, auth.EntitlementCanView, "name")}, - Patch: APIEndpointAction{Handler: projectPatch, AccessHandler: allowPermission(auth.ObjectTypeProject, auth.EntitlementCanEdit, "name")}, - Post: APIEndpointAction{Handler: projectPost, AccessHandler: allowPermission(auth.ObjectTypeProject, auth.EntitlementCanEdit, "name")}, - Put: APIEndpointAction{Handler: projectPut, AccessHandler: allowPermission(auth.ObjectTypeProject, auth.EntitlementCanEdit, "name")}, + Delete: APIEndpointAction{Handler: projectDelete, AccessHandler: allowPermission(entity.TypeProject, auth.EntitlementCanDelete, "name")}, + Get: APIEndpointAction{Handler: projectGet, AccessHandler: allowPermission(entity.TypeProject, auth.EntitlementCanView, "name")}, + Patch: APIEndpointAction{Handler: projectPatch, AccessHandler: allowPermission(entity.TypeProject, auth.EntitlementCanEdit, "name")}, + Post: APIEndpointAction{Handler: projectPost, AccessHandler: allowPermission(entity.TypeProject, auth.EntitlementCanEdit, "name")}, + Put: APIEndpointAction{Handler: projectPut, AccessHandler: allowPermission(entity.TypeProject, auth.EntitlementCanEdit, "name")}, } var projectStateCmd = APIEndpoint{ Path: "projects/{name}/state", - Get: APIEndpointAction{Handler: projectStateGet, AccessHandler: allowPermission(auth.ObjectTypeProject, auth.EntitlementCanView, "name")}, + Get: APIEndpointAction{Handler: projectStateGet, AccessHandler: allowPermission(entity.TypeProject, auth.EntitlementCanView, "name")}, } // swagger:operation GET /1.0/projects projects projects_get @@ -140,7 +141,7 @@ func projectsGet(d *Daemon, r *http.Request) response.Response { recursion := util.IsRecursionRequest(r) - userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, auth.ObjectTypeProject) + userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, entity.TypeProject) if err != nil { return response.InternalError(err) } @@ -154,7 +155,7 @@ func projectsGet(d *Daemon, r *http.Request) response.Response { filtered := []api.Project{} for _, project := range projects { - if !userHasPermission(auth.ObjectProject(project.Name)) { + if !userHasPermission(entity.ProjectURL(project.Name)) { continue } diff --git a/lxd/auth/authorization.go b/lxd/auth/authorization.go index a5f7fbb279f9..2b89c97da41a 100644 --- a/lxd/auth/authorization.go +++ b/lxd/auth/authorization.go @@ -5,7 +5,9 @@ import ( "fmt" "net/http" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/identity" + "github.com/canonical/lxd/shared/api" "github.com/canonical/lxd/shared/logger" ) @@ -30,15 +32,15 @@ type authorizer interface { // PermissionChecker is a type alias for a function that returns whether a user has required permissions on an object. // It is returned by Authorizer.GetPermissionChecker. -type PermissionChecker func(object Object) bool +type PermissionChecker func(entityURL *api.URL) bool // Authorizer is the primary external API for this package. type Authorizer interface { Driver() string StopService(ctx context.Context) error - CheckPermission(ctx context.Context, r *http.Request, object Object, entitlement Entitlement) error - GetPermissionChecker(ctx context.Context, r *http.Request, entitlement Entitlement, objectType ObjectType) (PermissionChecker, error) + CheckPermission(ctx context.Context, r *http.Request, entityURL *api.URL, entitlement Entitlement) error + GetPermissionChecker(ctx context.Context, r *http.Request, entitlement Entitlement, entityType entity.Type) (PermissionChecker, error) AddProject(ctx context.Context, projectID int64, projectName string) error DeleteProject(ctx context.Context, projectID int64, projectName string) error diff --git a/lxd/auth/authorization_objects.go b/lxd/auth/authorization_objects.go deleted file mode 100644 index b545c8bf5396..000000000000 --- a/lxd/auth/authorization_objects.go +++ /dev/null @@ -1,293 +0,0 @@ -package auth - -import ( - "fmt" - "net/http" - "net/url" - "strings" - - "github.com/gorilla/mux" - - "github.com/canonical/lxd/shared/version" -) - -// Object is a string alias that represents an authorization object. These are formatted strings that -// uniquely identify an API resource, and can be constructed/deconstructed reliably. -// An Object is always of the form : where the identifier is a "/" delimited path containing elements that -// uniquely identify a resource. If the resource is defined at the project level, the first element of this path is always the project. -// Some example objects would be: -// - `instance:default/c1`: Instance object in project "default" and name "c1". -// - `storage_pool:local`: Storage pool object with name "local". -// - `storage_volume:default/local/custom/vol1`: Storage volume object in project "default", storage pool "local", type "custom", and name "vol1". -type Object string - -const ( - // objectTypeDelimiter is the string which separates the ObjectType from the remaining elements. Object types are - // statically defined and do not contain this character, so we can extract the object type from an object by splitting - // the string at this character. - objectTypeDelimiter = ":" - - // objectElementDelimiter is the string which separates the elements of an object that make it a uniquely identifiable - // resource. This was chosen because the character is not allowed in the majority of LXD resource names. Nevertheless - // it is still necessary to escape this character in order to reliably construct/deconstruct an Object. - objectElementDelimiter = "/" -) - -// String implements fmt.Stringer for Object. -func (o Object) String() string { - return string(o) -} - -// Type returns the ObjectType of the Object. -func (o Object) Type() ObjectType { - t, _, _ := strings.Cut(o.String(), objectTypeDelimiter) - return ObjectType(t) -} - -// Project returns the project of the Object if present. -func (o Object) Project() string { - project, _ := o.projectAndElements() - return project -} - -// Elements returns the elements that uniquely identify the authorization Object. -func (o Object) Elements() []string { - _, elements := o.projectAndElements() - return elements -} - -func (o Object) projectAndElements() (string, []string) { - validator := objectValidators[o.Type()] - _, identifier, _ := strings.Cut(o.String(), objectTypeDelimiter) - - var projectName string - escapedObjectComponents := strings.SplitN(identifier, objectElementDelimiter, -1) - components := make([]string, 0, len(escapedObjectComponents)) - for i, escapedComponent := range escapedObjectComponents { - if validator.requireProject && i == 0 { - projectName = unescape(escapedComponent) - continue - } - - components = append(components, unescape(escapedComponent)) - } - - return projectName, components -} - -func (o Object) validate() error { - objectType := o.Type() - v, ok := objectValidators[objectType] - if !ok { - return fmt.Errorf("Missing validator for object of type %q", objectType) - } - - projectName, identifierElements := o.projectAndElements() - if v.requireProject && projectName == "" { - return fmt.Errorf("Authorization objects of type %q require a project", objectType) - } - - if len(identifierElements) != v.nIdentifierElements { - return fmt.Errorf("Authorization objects of type %q require %d components to be uniquely identifiable", objectType, v.nIdentifierElements) - } - - return nil -} - -// objectValidator contains fields that can be used to determine if a string is a valid Object. -type objectValidator struct { - nIdentifierElements int - requireProject bool -} - -var objectValidators = map[ObjectType]objectValidator{ - ObjectTypeUser: {nIdentifierElements: 1, requireProject: false}, - ObjectTypeServer: {nIdentifierElements: 1, requireProject: false}, - ObjectTypeCertificate: {nIdentifierElements: 1, requireProject: false}, - ObjectTypeStoragePool: {nIdentifierElements: 1, requireProject: false}, - ObjectTypeProject: {nIdentifierElements: 0, requireProject: true}, - ObjectTypeImage: {nIdentifierElements: 1, requireProject: true}, - ObjectTypeImageAlias: {nIdentifierElements: 1, requireProject: true}, - ObjectTypeInstance: {nIdentifierElements: 1, requireProject: true}, - ObjectTypeNetwork: {nIdentifierElements: 1, requireProject: true}, - ObjectTypeNetworkACL: {nIdentifierElements: 1, requireProject: true}, - ObjectTypeNetworkZone: {nIdentifierElements: 1, requireProject: true}, - ObjectTypeProfile: {nIdentifierElements: 1, requireProject: true}, - ObjectTypeStorageBucket: {nIdentifierElements: 2, requireProject: true}, - ObjectTypeStorageVolume: {nIdentifierElements: 3, requireProject: true}, -} - -// NewObject returns an Object of the given type. The passed in arguments must be in the correct -// order (as found in the URL for the resource). This function will error if an invalid object type is -// given, or if the correct number of arguments is not passed in. -func NewObject(objectType ObjectType, projectName string, identifierElements ...string) (Object, error) { - v, ok := objectValidators[objectType] - if !ok { - return "", fmt.Errorf("Missing validator for object of type %q", objectType) - } - - if v.requireProject && projectName == "" { - return "", fmt.Errorf("Authorization objects of type %q require a project", objectType) - } - - if len(identifierElements) != v.nIdentifierElements { - return "", fmt.Errorf("Authorization objects of type %q require %d components to be uniquely identifiable", objectType, v.nIdentifierElements) - } - - builder := strings.Builder{} - builder.WriteString(string(objectType)) - builder.WriteString(objectTypeDelimiter) - if v.requireProject { - builder.WriteString(escape(projectName)) - if len(identifierElements) > 0 { - builder.WriteString(objectElementDelimiter) - } - } - - for i, c := range identifierElements { - builder.WriteString(escape(c)) - if i != len(identifierElements)-1 { - builder.WriteString(objectElementDelimiter) - } - } - - return Object(builder.String()), nil -} - -// ObjectFromRequest returns an object created from the request by evaluating the given mux vars. -// Mux vars must be provided in the order that they are found in the endpoint path. If the object -// requires a project name, this is taken from the project query parameter unless the URL begins -// with /1.0/projects. -func ObjectFromRequest(r *http.Request, objectType ObjectType, muxVars ...string) (Object, error) { - // Shortcut for server objects which don't require any arguments. - if objectType == ObjectTypeServer { - return ObjectServer(), nil - } - - muxValues := make([]string, 0, len(muxVars)) - vars := mux.Vars(r) - for _, muxVar := range muxVars { - muxValue, err := url.PathUnescape(vars[muxVar]) - if err != nil { - return "", fmt.Errorf("Failed to unescape mux var %q for object type %q: %w", muxVar, objectType, err) - } - - if muxValue == "" { - return "", fmt.Errorf("Mux var %q not found for object type %q", muxVar, objectType) - } - - muxValues = append(muxValues, muxValue) - } - - values, err := url.ParseQuery(r.URL.RawQuery) - if err != nil { - return "", err - } - - projectName := values.Get("project") - if projectName == "" { - projectName = "default" - } - - // If using projects API we want to pass in the mux var, not the query parameter. - if objectType == ObjectTypeProject && strings.HasPrefix(r.URL.Path, fmt.Sprintf("/%s/projects", version.APIVersion)) { - if len(muxValues) == 0 { - return "", fmt.Errorf("Missing project name path variable") - } - - return ObjectProject(muxValues[0]), nil - } - - return NewObject(objectType, projectName, muxValues...) -} - -// ObjectFromString parses a string into an Object. It returns an error if the string is not valid. -func ObjectFromString(objectstr string) (Object, error) { - o := Object(objectstr) - err := o.validate() - if err != nil { - return "", err - } - - return o, nil -} - -func ObjectUser(userName string) Object { - object, _ := NewObject(ObjectTypeUser, "", userName) - return object -} - -func ObjectServer() Object { - object, _ := NewObject(ObjectTypeServer, "", "lxd") - return object -} - -func ObjectCertificate(fingerprint string) Object { - object, _ := NewObject(ObjectTypeCertificate, "", fingerprint) - return object -} - -func ObjectStoragePool(storagePoolName string) Object { - object, _ := NewObject(ObjectTypeStoragePool, "", storagePoolName) - return object -} - -func ObjectProject(projectName string) Object { - object, _ := NewObject(ObjectTypeProject, projectName) - return object -} - -func ObjectImage(projectName string, imageFingerprint string) Object { - object, _ := NewObject(ObjectTypeImage, projectName, imageFingerprint) - return object -} - -func ObjectImageAlias(projectName string, aliasName string) Object { - object, _ := NewObject(ObjectTypeImageAlias, projectName, aliasName) - return object -} - -func ObjectInstance(projectName string, instanceName string) Object { - object, _ := NewObject(ObjectTypeInstance, projectName, instanceName) - return object -} - -func ObjectNetwork(projectName string, networkName string) Object { - object, _ := NewObject(ObjectTypeNetwork, projectName, networkName) - return object -} - -func ObjectNetworkACL(projectName string, networkACLName string) Object { - object, _ := NewObject(ObjectTypeNetworkACL, projectName, networkACLName) - return object -} - -func ObjectNetworkZone(projectName string, networkZoneName string) Object { - object, _ := NewObject(ObjectTypeNetworkZone, projectName, networkZoneName) - return object -} - -func ObjectProfile(projectName string, profileName string) Object { - object, _ := NewObject(ObjectTypeProfile, projectName, profileName) - return object -} - -func ObjectStorageBucket(projectName string, poolName string, bucketName string) Object { - object, _ := NewObject(ObjectTypeStorageBucket, projectName, poolName, bucketName) - return object -} - -func ObjectStorageVolume(projectName string, poolName string, volumeType string, volumeName string) Object { - object, _ := NewObject(ObjectTypeStorageVolume, projectName, poolName, volumeType, volumeName) - return object -} - -// escape escapes only the forward slash character as this is used as a delimiter. Everything else is allowed. -func escape(s string) string { - return strings.Replace(s, "/", "%2F", -1) -} - -// unescape replaces only the escaped forward slashes. -func unescape(s string) string { - return strings.Replace(s, "%2F", "/", -1) -} diff --git a/lxd/auth/authorization_objects_test.go b/lxd/auth/authorization_objects_test.go deleted file mode 100644 index 7910b607968a..000000000000 --- a/lxd/auth/authorization_objects_test.go +++ /dev/null @@ -1,189 +0,0 @@ -package auth - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/suite" - - "github.com/canonical/lxd/shared" -) - -type objectSuite struct { - suite.Suite -} - -func TestObjectSuite(t *testing.T) { - suite.Run(t, new(objectSuite)) -} - -func (s *objectSuite) TestObjectCertificate() { - s.Assert().NotPanics(func() { - fingerprint := shared.TestingKeyPair().Fingerprint() - o := ObjectCertificate(fingerprint) - s.Equal(fmt.Sprintf("certificate:%s", fingerprint), string(o)) - }) -} - -func (s *objectSuite) TestObjectImage() { - s.Assert().NotPanics(func() { - fingerprint := shared.TestingKeyPair().Fingerprint() - o := ObjectImage("default", fingerprint) - s.Equal(fmt.Sprintf("image:default/%s", fingerprint), string(o)) - }) -} - -func (s *objectSuite) TestObjectImageAlias() { - s.Assert().NotPanics(func() { - o := ObjectImageAlias("default", "image_alias_name") - s.Equal("image_alias:default/image_alias_name", string(o)) - }) -} - -func (s *objectSuite) TestObjectInstance() { - s.Assert().NotPanics(func() { - o := ObjectInstance("default", "instance_name") - s.Equal("instance:default/instance_name", string(o)) - }) -} - -func (s *objectSuite) TestObjectNetwork() { - s.Assert().NotPanics(func() { - o := ObjectNetwork("default", "network_name") - s.Equal("network:default/network_name", string(o)) - }) -} - -func (s *objectSuite) TestObjectNetworkACL() { - s.Assert().NotPanics(func() { - o := ObjectNetworkACL("default", "network_acl_name") - s.Equal("network_acl:default/network_acl_name", string(o)) - }) -} - -func (s *objectSuite) TestObjectNetworkZone() { - s.Assert().NotPanics(func() { - o := ObjectNetworkZone("default", "network_zone_name") - s.Equal("network_zone:default/network_zone_name", string(o)) - }) -} - -func (s *objectSuite) TestObjectProfile() { - s.Assert().NotPanics(func() { - o := ObjectProfile("default", "profile_name") - s.Equal("profile:default/profile_name", string(o)) - }) -} - -func (s *objectSuite) TestObjectProject() { - s.Assert().NotPanics(func() { - o := ObjectProject("default") - s.Equal("project:default", string(o)) - }) -} - -func (s *objectSuite) TestObjectServer() { - s.Assert().NotPanics(func() { - o := ObjectServer() - s.Equal("server:lxd", string(o)) - }) -} - -func (s *objectSuite) TestObjectStorageBucket() { - s.Assert().NotPanics(func() { - o := ObjectStorageBucket("default", "pool_name", "storage_bucket_name") - s.Equal("storage_bucket:default/pool_name/storage_bucket_name", string(o)) - }) -} - -func (s *objectSuite) TestObjectStoragePool() { - s.Assert().NotPanics(func() { - o := ObjectStoragePool("pool_name") - s.Equal("storage_pool:pool_name", string(o)) - }) -} - -func (s *objectSuite) TestObjectStorageVolume() { - s.Assert().NotPanics(func() { - o := ObjectStorageVolume("default", "pool_name", "volume_type", "volume_name") - s.Equal("storage_volume:default/pool_name/volume_type/volume_name", string(o)) - }) -} - -func (s *objectSuite) TestObjectUser() { - s.Assert().NotPanics(func() { - o := ObjectUser("username") - s.Equal("user:username", string(o)) - }) -} - -func (s *objectSuite) TestObjectFromString() { - tests := []struct { - in string - out Object - err error - }{ - { - in: "server:lxd", - out: Object("server:lxd"), - }, - { - in: "certificate:weaowiejfoiawefpajewfpoawjfepojawef", - out: Object("certificate:weaowiejfoiawefpajewfpoawjfepojawef"), - }, - { - in: "storage_pool:local", - out: Object("storage_pool:local"), - }, - { - in: "project:default", - out: Object("project:default"), - }, - { - in: "profile:default/default", - out: Object("profile:default/default"), - }, - { - in: "image:default/eoaiwenfoaiwnefoianwef", - out: Object("image:default/eoaiwenfoaiwnefoianwef"), - }, - { - in: "image_alias:default/windows11", - out: Object("image_alias:default/windows11"), - }, - { - in: "network:default/lxdbr0", - out: Object("network:default/lxdbr0"), - }, - { - in: "network_acl:default/acl1", - out: Object("network_acl:default/acl1"), - }, - { - in: "network_zone:default/example.com", - out: Object("network_zone:default/example.com"), - }, - { - in: "storage_volume:default/local/custom/vol1", - out: Object("storage_volume:default/local/custom/vol1"), - }, - { - in: "storage_bucket:default/local/bucket1", - out: Object("storage_bucket:default/local/bucket1"), - }, - } - - for _, tt := range tests { - o, err := ObjectFromString(tt.in) - s.Equal(tt.err, err) - s.Equal(tt.out, o) - } -} - -// Objects shouldn't continuously path escape. -func (s *objectSuite) TestRemake() { - o := ObjectProject("contains/forward/slashes") - oSquared, err := ObjectFromString(o.String()) - s.Nil(err) - s.Equal(o.String(), oSquared.String()) -} diff --git a/lxd/auth/authorization_types.go b/lxd/auth/authorization_types.go index 3bf2e7a26f25..af798eb0a125 100644 --- a/lxd/auth/authorization_types.go +++ b/lxd/auth/authorization_types.go @@ -1,64 +1,376 @@ -//nolint:revive package auth -// Entitlement is a type representation of a permission as it applies to a particular ObjectType. +import ( + "fmt" + + "github.com/canonical/lxd/shared" +) + +// Entitlement represents a permission that can be applied to an entity. type Entitlement string const ( - // Entitlements that apply to all resources. - EntitlementCanEdit Entitlement = "can_edit" + // EntitlementCanView is the `can_view` Entitlement. It applies to most entity types. EntitlementCanView Entitlement = "can_view" - // Server entitlements. - EntitlementCanCreateStoragePools Entitlement = "can_create_storage_pools" - EntitlementCanCreateProjects Entitlement = "can_create_projects" - EntitlementCanViewResources Entitlement = "can_view_resources" - EntitlementCanCreateCertificates Entitlement = "can_create_certificates" - EntitlementCanViewMetrics Entitlement = "can_view_metrics" + // EntitlementCanEdit is the `can_edit` Entitlement. It applies to most entity types. + EntitlementCanEdit Entitlement = "can_edit" + + // EntitlementCanDelete is the `can_delete` Entitlement. It applies to most entity types. + EntitlementCanDelete Entitlement = "can_delete" + + // EntitlementServerAdmin is the `admin` Entitlement. It applies to entity.TypeServer. + EntitlementServerAdmin Entitlement = "admin" + + // EntitlementServerViewer is the `viewer` Entitlement. It applies to entity.TypeServer. + EntitlementServerViewer Entitlement = "viewer" + + // EntitlementCanViewConfiguration is the `can_view_configuration` Entitlement. It applies to entity.TypeServer. + EntitlementCanViewConfiguration Entitlement = "can_view_configuration" + + // EntitlementPermissionManager is the `permission_manager` Entitlement. It applies to entity.TypeServer. + EntitlementPermissionManager Entitlement = "permission_manager" + + // EntitlementCanCreateIdentities is the `can_create_identities` Entitlement. It applies to entity.TypeServer. + EntitlementCanCreateIdentities Entitlement = "can_create_identities" + + // EntitlementCanViewIdentities is the `can_view_identities` Entitlement. It applies to entity.TypeServer. + EntitlementCanViewIdentities Entitlement = "can_view_identities" + + // EntitlementCanEditIdentities is the `can_edit_identities` Entitlement. It applies to entity.TypeServer. + EntitlementCanEditIdentities Entitlement = "can_edit_identities" + + // EntitlementCanDeleteIdentities is the `can_delete_identities` Entitlement. It applies to entity.TypeServer. + EntitlementCanDeleteIdentities Entitlement = "can_delete_identities" + + // EntitlementCanCreateGroups is the `can_create_groups` Entitlement. It applies to entity.TypeServer. + EntitlementCanCreateGroups Entitlement = "can_create_groups" + + // EntitlementCanViewGroups is the `can_view_groups` Entitlement. It applies to entity.TypeServer. + EntitlementCanViewGroups Entitlement = "can_view_groups" + + // EntitlementCanEditGroups is the `can_edit_groups` Entitlement. It applies to entity.TypeServer. + EntitlementCanEditGroups Entitlement = "can_edit_groups" + + // EntitlementCanDeleteGroups is the `can_delete_groups` Entitlement. It applies to entity.TypeServer. + EntitlementCanDeleteGroups Entitlement = "can_delete_groups" + + // EntitlementStoragePoolManager is the `storage_pool_manager` Entitlement. It applies to entity.TypeServer. + EntitlementStoragePoolManager Entitlement = "storage_pool_manager" + + // EntitlementCanCreateStoragePools is the `can_create_storage_pools` Entitlement. It applies to entity.TypeServer. + EntitlementCanCreateStoragePools Entitlement = "can_create_storage_pools" + + // EntitlementCanEditStoragePools is the `can_edit_storage_pools` Entitlement. It applies to entity.TypeServer. + EntitlementCanEditStoragePools Entitlement = "can_edit_storage_pools" + + // EntitlementCanDeleteStoragePools is the `can_delete_storage_pools` Entitlement. It applies to entity.TypeServer. + EntitlementCanDeleteStoragePools Entitlement = "can_delete_storage_pools" + + // EntitlementProjectManager is the `project_manager` Entitlement. It applies to entity.TypeServer. + EntitlementProjectManager Entitlement = "project_manager" + + // EntitlementCanCreateProjects is the `can_create_projects` Entitlement. It applies to entity.TypeServer. + EntitlementCanCreateProjects Entitlement = "can_create_projects" + + // EntitlementCanViewProjects is the `can_view_projects` Entitlement. It applies to entity.TypeServer. + EntitlementCanViewProjects Entitlement = "can_view_projects" + + // EntitlementCanEditProjects is the `can_edit_projects` Entitlement. It applies to entity.TypeServer. + EntitlementCanEditProjects Entitlement = "can_edit_projects" + + // EntitlementCanDeleteProjects is the `can_delete_projects` Entitlement. It applies to entity.TypeServer. + EntitlementCanDeleteProjects Entitlement = "can_delete_projects" + + // EntitlementCanOverrideClusterTargetRestriction is the `can_override_cluster_target_restriction` Entitlement. It applies to entity.TypeServer. EntitlementCanOverrideClusterTargetRestriction Entitlement = "can_override_cluster_target_restriction" - EntitlementCanViewPrivilegedEvents Entitlement = "can_view_privileged_events" - - // Project entitlements. - EntitlementCanCreateImages Entitlement = "can_create_images" - EntitlementCanCreateImageAliases Entitlement = "can_create_image_aliases" - EntitlementCanCreateInstances Entitlement = "can_create_instances" - EntitlementCanCreateNetworks Entitlement = "can_create_networks" - EntitlementCanCreateNetworkACLs Entitlement = "can_create_network_acls" - EntitlementCanCreateNetworkZones Entitlement = "can_create_network_zones" - EntitlementCanCreateProfiles Entitlement = "can_create_profiles" + + // EntitlementCanViewPrivilegedEvents is the `can_view_privileged_events` Entitlement. It applies to entity.TypeServer. + EntitlementCanViewPrivilegedEvents Entitlement = "can_view_privileged_events" + + // EntitlementCanViewResources is the `can_view_resources` Entitlement. It applies to entity.TypeServer. + EntitlementCanViewResources Entitlement = "can_view_resources" + + // EntitlementCanViewMetrics is the `can_view_metrics` Entitlement. It applies to entity.TypeServer. + EntitlementCanViewMetrics Entitlement = "can_view_metrics" + + // EntitlementCanViewWarnings is the `can_view_warnings` Entitlement. It applies to entity.TypeServer. + EntitlementCanViewWarnings Entitlement = "can_view_warnings" + + // EntitlementProjectOperator is the `operator` Entitlement. It applies to entity.TypeProject. + EntitlementProjectOperator Entitlement = "operator" + + // EntitlementProjectViewer is the `viewer` Entitlement. It applies to entity.TypeProject. + EntitlementProjectViewer Entitlement = "viewer" + + // EntitlementImageManager is the `image_manager` Entitlement. It applies to entity.TypeProject. + EntitlementImageManager Entitlement = "image_manager" + + // EntitlementCanCreateImages is the `can_create_images` Entitlement. It applies to entity.TypeProject. + EntitlementCanCreateImages Entitlement = "can_create_images" + + // EntitlementCanViewImages is the `can_view_images` Entitlement. It applies to entity.TypeProject. + EntitlementCanViewImages Entitlement = "can_view_images" + + // EntitlementCanEditImages is the `can_edit_images` Entitlement. It applies to entity.TypeProject. + EntitlementCanEditImages Entitlement = "can_edit_images" + + // EntitlementCanDeleteImages is the `can_delete_images` Entitlement. It applies to entity.TypeProject. + EntitlementCanDeleteImages Entitlement = "can_delete_images" + + // EntitlementImageAliasManager is the `image_alias_manager` Entitlement. It applies to entity.TypeProject. + EntitlementImageAliasManager Entitlement = "image_alias_manager" + + // EntitlementCanCreateImageAliases is the `can_create_image_aliases` Entitlement. It applies to entity.TypeProject. + EntitlementCanCreateImageAliases Entitlement = "can_create_image_aliases" + + // EntitlementCanViewImageAliases is the `can_view_image_aliases` Entitlement. It applies to entity.TypeProject. + EntitlementCanViewImageAliases Entitlement = "can_view_image_aliases" + + // EntitlementCanEditImageAliases is the `can_edit_image_aliases` Entitlement. It applies to entity.TypeProject. + EntitlementCanEditImageAliases Entitlement = "can_edit_image_aliases" + + // EntitlementCanDeleteImageAliases is the `can_delete_image_aliases` Entitlement. It applies to entity.TypeProject. + EntitlementCanDeleteImageAliases Entitlement = "can_delete_image_aliases" + + // EntitlementInstanceManager is the `instance_manager` Entitlement. It applies to entity.TypeProject. + EntitlementInstanceManager Entitlement = "instance_manager" + + // EntitlementCanCreateInstances is the `can_create_instances` Entitlement. It applies to entity.TypeProject. + EntitlementCanCreateInstances Entitlement = "can_create_instances" + + // EntitlementCanViewInstances is the `can_view_instances` Entitlement. It applies to entity.TypeProject. + EntitlementCanViewInstances Entitlement = "can_view_instances" + + // EntitlementCanEditInstances is the `can_edit_instances` Entitlement. It applies to entity.TypeProject. + EntitlementCanEditInstances Entitlement = "can_edit_instances" + + // EntitlementCanDeleteInstances is the `can_delete_instances` Entitlement. It applies to entity.TypeProject. + EntitlementCanDeleteInstances Entitlement = "can_delete_instances" + + // EntitlementCanOperateInstances is the `can_operate_instances` Entitlement. It applies to entity.TypeProject. + EntitlementCanOperateInstances Entitlement = "can_operate_instances" + + // EntitlementNetworkManager is the `network_manager` Entitlement. It applies to entity.TypeProject. + EntitlementNetworkManager Entitlement = "network_manager" + + // EntitlementCanCreateNetworks is the `can_create_networks` Entitlement. It applies to entity.TypeProject. + EntitlementCanCreateNetworks Entitlement = "can_create_networks" + + // EntitlementCanViewNetworks is the `can_view_networks` Entitlement. It applies to entity.TypeProject. + EntitlementCanViewNetworks Entitlement = "can_view_networks" + + // EntitlementCanEditNetworks is the `can_edit_networks` Entitlement. It applies to entity.TypeProject. + EntitlementCanEditNetworks Entitlement = "can_edit_networks" + + // EntitlementCanDeleteNetworks is the `can_delete_networks` Entitlement. It applies to entity.TypeProject. + EntitlementCanDeleteNetworks Entitlement = "can_delete_networks" + + // EntitlementNetworkACLManager is the `network_acl_manager` Entitlement. It applies to entity.TypeProject. + EntitlementNetworkACLManager Entitlement = "network_acl_manager" + + // EntitlementCanCreateNetworkACLs is the `can_create_network_acls` Entitlement. It applies to entity.TypeProject. + EntitlementCanCreateNetworkACLs Entitlement = "can_create_network_acls" + + // EntitlementCanViewNetworkACLs is the `can_view_network_acls` Entitlement. It applies to entity.TypeProject. + EntitlementCanViewNetworkACLs Entitlement = "can_view_network_acls" + + // EntitlementCanEditNetworkACLs is the `can_edit_network_acls` Entitlement. It applies to entity.TypeProject. + EntitlementCanEditNetworkACLs Entitlement = "can_edit_network_acls" + + // EntitlementCanDeleteNetworkACLs is the `can_delete_network_acls` Entitlement. It applies to entity.TypeProject. + EntitlementCanDeleteNetworkACLs Entitlement = "can_delete_network_acls" + + // EntitlementNetworkZoneManager is the `network_zone_manager` Entitlement. It applies to entity.TypeProject. + EntitlementNetworkZoneManager Entitlement = "network_zone_manager" + + // EntitlementCanCreateNetworkZones is the `can_create_network_zones` Entitlement. It applies to entity.TypeProject. + EntitlementCanCreateNetworkZones Entitlement = "can_create_network_zones" + + // EntitlementCanViewNetworkZones is the `can_view_network_zones` Entitlement. It applies to entity.TypeProject. + EntitlementCanViewNetworkZones Entitlement = "can_view_network_zones" + + // EntitlementCanEditNetworkZones is the `can_edit_network_zones` Entitlement. It applies to entity.TypeProject. + EntitlementCanEditNetworkZones Entitlement = "can_edit_network_zones" + + // EntitlementCanDeleteNetworkZones is the `can_delete_network_zones` Entitlement. It applies to entity.TypeProject. + EntitlementCanDeleteNetworkZones Entitlement = "can_delete_network_zones" + + // EntitlementProfileManager is the `profile_manager` Entitlement. It applies to entity.TypeProject. + EntitlementProfileManager Entitlement = "profile_manager" + + // EntitlementCanCreateProfiles is the `can_create_profiles` Entitlement. It applies to entity.TypeProject. + EntitlementCanCreateProfiles Entitlement = "can_create_profiles" + + // EntitlementCanViewProfiles is the `can_view_profiles` Entitlement. It applies to entity.TypeProject. + EntitlementCanViewProfiles Entitlement = "can_view_profiles" + + // EntitlementCanEditProfiles is the `can_edit_profiles` Entitlement. It applies to entity.TypeProject. + EntitlementCanEditProfiles Entitlement = "can_edit_profiles" + + // EntitlementCanDeleteProfiles is the `can_delete_profiles` Entitlement. It applies to entity.TypeProject. + EntitlementCanDeleteProfiles Entitlement = "can_delete_profiles" + + // EntitlementStorageVolumeManager is the `storage_volume_manager` Entitlement. It applies to entity.TypeProject. + EntitlementStorageVolumeManager Entitlement = "storage_volume_manager" + + // EntitlementCanCreateStorageVolumes is the `can_create_storage_volumes` Entitlement. It applies to entity.TypeProject. EntitlementCanCreateStorageVolumes Entitlement = "can_create_storage_volumes" + + // EntitlementCanViewStorageVolumes is the `can_view_storage_volumes` Entitlement. It applies to entity.TypeProject. + EntitlementCanViewStorageVolumes Entitlement = "can_view_storage_volumes" + + // EntitlementCanEditStorageVolumes is the `can_edit_storage_volumes` Entitlement. It applies to entity.TypeProject. + EntitlementCanEditStorageVolumes Entitlement = "can_edit_storage_volumes" + + // EntitlementCanDeleteStorageVolumes is the `can_delete_storage_volumes` Entitlement. It applies to entity.TypeProject. + EntitlementCanDeleteStorageVolumes Entitlement = "can_delete_storage_volumes" + + // EntitlementStorageBucketManager is the `storage_bucket_manager` Entitlement. It applies to entity.TypeProject. + EntitlementStorageBucketManager Entitlement = "storage_bucket_manager" + + // EntitlementCanCreateStorageBuckets is the `can_create_storage_buckets` Entitlement. It applies to entity.TypeProject. EntitlementCanCreateStorageBuckets Entitlement = "can_create_storage_buckets" - EntitlementCanViewOperations Entitlement = "can_view_operations" - EntitlementCanViewEvents Entitlement = "can_view_events" - // Instance entitlements. - EntitlementCanUpdateState Entitlement = "can_update_state" - EntitlementCanConnectSFTP Entitlement = "can_connect_sftp" - EntitlementCanAccessFiles Entitlement = "can_access_files" + // EntitlementCanViewStorageBuckets is the `can_view_storage_buckets` Entitlement. It applies to entity.TypeProject. + EntitlementCanViewStorageBuckets Entitlement = "can_view_storage_buckets" + + // EntitlementCanEditStorageBuckets is the `can_edit_storage_buckets` Entitlement. It applies to entity.TypeProject. + EntitlementCanEditStorageBuckets Entitlement = "can_edit_storage_buckets" + + // EntitlementCanDeleteStorageBuckets is the `can_delete_storage_buckets` Entitlement. It applies to entity.TypeProject. + EntitlementCanDeleteStorageBuckets Entitlement = "can_delete_storage_buckets" + + // EntitlementCanViewOperations is the `can_view_operations` Entitlement. It applies to entity.TypeProject. + EntitlementCanViewOperations Entitlement = "can_view_operations" + + // EntitlementCanViewEvents is the `can_view_events` Entitlement. It applies to entity.TypeProject. + EntitlementCanViewEvents Entitlement = "can_view_events" + + // EntitlementInstanceUser is the `user` Entitlement. It applies to entity.TypeInstance. + EntitlementInstanceUser Entitlement = "user" + + // EntitlementInstanceOperator is the `operator` Entitlement. It applies to entity.TypeInstance. + EntitlementInstanceOperator Entitlement = "operator" + + // EntitlementCanUpdateState is the `can_update_state` Entitlement. It applies to entity.TypeInstance. + EntitlementCanUpdateState Entitlement = "can_update_state" + + // EntitlementCanConnectSFTP is the `can_connect_sftp` Entitlement. It applies to entity.TypeInstance. + EntitlementCanConnectSFTP Entitlement = "can_connect_sftp" + + // EntitlementCanAccessFiles is the `can_access_files` Entitlement. It applies to entity.TypeInstance. + EntitlementCanAccessFiles Entitlement = "can_access_files" + + // EntitlementCanAccessConsole is the `can_access_console` Entitlement. It applies to entity.TypeInstance. EntitlementCanAccessConsole Entitlement = "can_access_console" - EntitlementCanExec Entitlement = "can_exec" - // Instance and storage volume entitlements. + // EntitlementCanExec is the `can_exec` Entitlement. It applies to entity.TypeInstance. + EntitlementCanExec Entitlement = "can_exec" + + // EntitlementCanManageSnapshots is the `can_manage_snapshots` Entitlement. It applies to entity.TypeInstance and entity.TypeStorageVolume. EntitlementCanManageSnapshots Entitlement = "can_manage_snapshots" - EntitlementCanManageBackups Entitlement = "can_manage_backups" + + // EntitlementCanManageBackups is the `can_manage_backups` Entitlement. It applies to entity.TypeInstance and entity.TypeStorageVolume. + EntitlementCanManageBackups Entitlement = "can_manage_backups" ) -// ObjectType is a type of resource within LXD. -type ObjectType string +var allEntitlements = []Entitlement{ + EntitlementCanView, + EntitlementCanEdit, + EntitlementCanDelete, + EntitlementServerAdmin, + EntitlementServerViewer, + EntitlementCanViewConfiguration, + EntitlementPermissionManager, + EntitlementCanCreateIdentities, + EntitlementCanViewIdentities, + EntitlementCanEditIdentities, + EntitlementCanDeleteIdentities, + EntitlementCanCreateGroups, + EntitlementCanViewGroups, + EntitlementCanEditGroups, + EntitlementCanDeleteGroups, + EntitlementStoragePoolManager, + EntitlementCanCreateStoragePools, + EntitlementCanEditStoragePools, + EntitlementCanDeleteStoragePools, + EntitlementProjectManager, + EntitlementCanCreateProjects, + EntitlementCanViewProjects, + EntitlementCanEditProjects, + EntitlementCanDeleteProjects, + EntitlementCanOverrideClusterTargetRestriction, + EntitlementCanViewPrivilegedEvents, + EntitlementCanViewResources, + EntitlementCanViewMetrics, + EntitlementCanViewWarnings, + EntitlementProjectOperator, + EntitlementProjectViewer, + EntitlementImageManager, + EntitlementCanCreateImages, + EntitlementCanViewImages, + EntitlementCanEditImages, + EntitlementCanDeleteImages, + EntitlementImageAliasManager, + EntitlementCanCreateImageAliases, + EntitlementCanViewImageAliases, + EntitlementCanEditImageAliases, + EntitlementCanDeleteImageAliases, + EntitlementInstanceManager, + EntitlementCanCreateInstances, + EntitlementCanViewInstances, + EntitlementCanEditInstances, + EntitlementCanDeleteInstances, + EntitlementCanOperateInstances, + EntitlementNetworkManager, + EntitlementCanCreateNetworks, + EntitlementCanViewNetworks, + EntitlementCanEditNetworks, + EntitlementCanDeleteNetworks, + EntitlementNetworkACLManager, + EntitlementCanCreateNetworkACLs, + EntitlementCanViewNetworkACLs, + EntitlementCanEditNetworkACLs, + EntitlementCanDeleteNetworkACLs, + EntitlementNetworkZoneManager, + EntitlementCanCreateNetworkZones, + EntitlementCanViewNetworkZones, + EntitlementCanEditNetworkZones, + EntitlementCanDeleteNetworkZones, + EntitlementProfileManager, + EntitlementCanCreateProfiles, + EntitlementCanViewProfiles, + EntitlementCanEditProfiles, + EntitlementCanDeleteProfiles, + EntitlementStorageVolumeManager, + EntitlementCanCreateStorageVolumes, + EntitlementCanViewStorageVolumes, + EntitlementCanEditStorageVolumes, + EntitlementCanDeleteStorageVolumes, + EntitlementStorageBucketManager, + EntitlementCanCreateStorageBuckets, + EntitlementCanViewStorageBuckets, + EntitlementCanEditStorageBuckets, + EntitlementCanDeleteStorageBuckets, + EntitlementCanViewOperations, + EntitlementCanViewEvents, + EntitlementInstanceUser, + EntitlementInstanceOperator, + EntitlementCanUpdateState, + EntitlementCanConnectSFTP, + EntitlementCanAccessFiles, + EntitlementCanAccessConsole, + EntitlementCanExec, + EntitlementCanManageSnapshots, + EntitlementCanManageBackups, +} -const ( - ObjectTypeUser ObjectType = "user" - ObjectTypeServer ObjectType = "server" - ObjectTypeCertificate ObjectType = "certificate" - ObjectTypeStoragePool ObjectType = "storage_pool" - ObjectTypeProject ObjectType = "project" - ObjectTypeImage ObjectType = "image" - ObjectTypeImageAlias ObjectType = "image_alias" - ObjectTypeInstance ObjectType = "instance" - ObjectTypeNetwork ObjectType = "network" - ObjectTypeNetworkACL ObjectType = "network_acl" - ObjectTypeNetworkZone ObjectType = "network_zone" - ObjectTypeProfile ObjectType = "profile" - ObjectTypeStorageBucket ObjectType = "storage_bucket" - ObjectTypeStorageVolume ObjectType = "storage_volume" -) +// Validate returns an error if the Entitlement is not recognised. +func Validate(e Entitlement) error { + if !shared.ValueInSlice(e, allEntitlements) { + return fmt.Errorf("Entitlement %q not defined", e) + } + + return nil +} diff --git a/lxd/auth/driver_tls.go b/lxd/auth/driver_tls.go index 4e8f953c1072..256204be2fe0 100644 --- a/lxd/auth/driver_tls.go +++ b/lxd/auth/driver_tls.go @@ -6,10 +6,10 @@ import ( "fmt" "net/http" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/identity" "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" - "github.com/canonical/lxd/shared/auth" "github.com/canonical/lxd/shared/logger" ) @@ -28,7 +28,7 @@ func (t *tls) load(ctx context.Context, identityCache *identity.Cache, opts Opts } // CheckPermission returns an error if the user does not have the given Entitlement on the given Object. -func (t *tls) CheckPermission(ctx context.Context, r *http.Request, object Object, entitlement Entitlement) error { +func (t *tls) CheckPermission(ctx context.Context, r *http.Request, entityURL *api.URL, entitlement Entitlement) error { details, err := t.requestDetails(r) if err != nil { return api.StatusErrorf(http.StatusForbidden, "Failed to extract request details: %v", err) @@ -52,7 +52,7 @@ func (t *tls) CheckPermission(ctx context.Context, r *http.Request, object Objec return fmt.Errorf("Failed loading certificate for %q: %w", username, err) } - isRestricted, err := auth.IsRestrictedIdentityType(id.IdentityType) + isRestricted, err := identity.IsRestrictedIdentityType(id.IdentityType) if err != nil { return fmt.Errorf("Failed to check restricted status of identity: %w", err) } @@ -68,15 +68,20 @@ func (t *tls) CheckPermission(ctx context.Context, r *http.Request, object Objec return api.StatusErrorf(http.StatusForbidden, "Certificate is restricted") } + entityType, projectName, _, _, err := entity.ParseURL(entityURL.URL) + if err != nil { + return fmt.Errorf("Failed to parse entity URL: %w", err) + } + // Check server level object types - switch object.Type() { - case ObjectTypeServer: + switch entityType { + case entity.TypeServer: if entitlement == EntitlementCanView || entitlement == EntitlementCanViewResources || entitlement == EntitlementCanViewMetrics { return nil } return api.StatusErrorf(http.StatusForbidden, "Certificate is restricted") - case ObjectTypeStoragePool, ObjectTypeCertificate: + case entity.TypeStoragePool, entity.TypeCertificate: if entitlement == EntitlementCanView { return nil } @@ -85,7 +90,6 @@ func (t *tls) CheckPermission(ctx context.Context, r *http.Request, object Objec } // Check project level permissions against the certificates project list. - projectName := object.Project() if !shared.ValueInSlice(projectName, id.Projects) { return api.StatusErrorf(http.StatusForbidden, "User does not have permission for project %q", projectName) } @@ -94,9 +98,9 @@ func (t *tls) CheckPermission(ctx context.Context, r *http.Request, object Objec } // GetPermissionChecker returns a function that can be used to check whether a user has the required entitlement on an authorization object. -func (t *tls) GetPermissionChecker(ctx context.Context, r *http.Request, entitlement Entitlement, objectType ObjectType) (PermissionChecker, error) { - allowFunc := func(b bool) func(Object) bool { - return func(Object) bool { +func (t *tls) GetPermissionChecker(ctx context.Context, r *http.Request, entitlement Entitlement, entityType entity.Type) (PermissionChecker, error) { + allowFunc := func(b bool) func(*api.URL) bool { + return func(*api.URL) bool { return b } } @@ -124,7 +128,7 @@ func (t *tls) GetPermissionChecker(ctx context.Context, r *http.Request, entitle return nil, fmt.Errorf("Failed loading certificate for %q: %w", username, err) } - isRestricted, err := auth.IsRestrictedIdentityType(id.IdentityType) + isRestricted, err := identity.IsRestrictedIdentityType(id.IdentityType) if err != nil { return nil, fmt.Errorf("Failed to check restricted status of identity: %w", err) } @@ -141,14 +145,14 @@ func (t *tls) GetPermissionChecker(ctx context.Context, r *http.Request, entitle } // Check server level object types - switch objectType { - case ObjectTypeServer: + switch entityType { + case entity.TypeServer: if entitlement == EntitlementCanView || entitlement == EntitlementCanViewResources || entitlement == EntitlementCanViewMetrics { return allowFunc(true), nil } return allowFunc(false), nil - case ObjectTypeStoragePool, ObjectTypeCertificate: + case entity.TypeStoragePool, entity.TypeCertificate: if entitlement == EntitlementCanView { return allowFunc(true), nil } @@ -157,12 +161,27 @@ func (t *tls) GetPermissionChecker(ctx context.Context, r *http.Request, entitle } // Error if user does not have access to the project (unless we're getting projects, where we want to filter the results). - if !shared.ValueInSlice(details.projectName, id.Projects) && objectType != ObjectTypeProject { + if !shared.ValueInSlice(details.projectName, id.Projects) && entityType != entity.TypeProject { return nil, api.StatusErrorf(http.StatusForbidden, "User does not have permissions for project %q", details.projectName) } // Filter objects by project. - return func(object Object) bool { - return shared.ValueInSlice(object.Project(), id.Projects) + return func(entityURL *api.URL) bool { + eType, project, _, pathArgs, err := entity.ParseURL(entityURL.URL) + if err != nil { + logger.Warn("Permission checker failed to parse entity URL", logger.Ctx{"entity_url": entityURL, "error": err}) + return false + } + + if eType == entity.TypeProject { + project = pathArgs[0] + } + + if eType != entityType { + logger.Warn("Permission checker received URL with unexpected entity type", logger.Ctx{"expected": entityType, "actual": eType, "entity_url": entityURL}) + return false + } + + return shared.ValueInSlice(project, id.Projects) }, nil } diff --git a/lxd/auth/util.go b/lxd/auth/util.go new file mode 100644 index 000000000000..0f2eb0ebf565 --- /dev/null +++ b/lxd/auth/util.go @@ -0,0 +1,151 @@ +package auth + +import ( + "fmt" + + "github.com/canonical/lxd/lxd/entity" + "github.com/canonical/lxd/shared" +) + +// ValidateEntitlement returns an error if the given Entitlement does not apply to the entity.Type. +func ValidateEntitlement(entityType entity.Type, entitlement Entitlement) error { + entitlements, err := EntitlementsByEntityType(entityType) + if err != nil { + return err + } + + if !shared.ValueInSlice(entitlement, entitlements) { + return fmt.Errorf("Entitlement %q not valid for entity type %q", entitlement, entityType) + } + + return nil +} + +// EntitlementsByEntityType returns a list of available Entitlement for the entity.Type. +func EntitlementsByEntityType(entityType entity.Type) ([]Entitlement, error) { + err := entityType.Validate() + if err != nil { + return nil, fmt.Errorf("Entity type %q is not valid: %w", entityType, err) + } + + // With the exception of entity types in the list below. All entity types have EntitlementCanView, + // EntitlementCanEdit, and EntitlementCanDelete. + if !shared.ValueInSlice(entityType, []entity.Type{entity.TypeStorageVolume, entity.TypeInstance, entity.TypeProject, entity.TypeServer}) { + return []Entitlement{EntitlementCanView, EntitlementCanEdit, EntitlementCanDelete}, nil + } + + switch entityType { + case entity.TypeStorageVolume: + return []Entitlement{ + EntitlementCanView, + EntitlementCanEdit, + EntitlementCanDelete, + EntitlementCanManageBackups, + EntitlementCanManageSnapshots, + }, nil + case entity.TypeInstance: + return []Entitlement{ + EntitlementCanView, + EntitlementCanEdit, + EntitlementCanDelete, + EntitlementInstanceUser, + EntitlementInstanceOperator, + EntitlementCanUpdateState, + EntitlementCanConnectSFTP, + EntitlementCanAccessFiles, + EntitlementCanAccessConsole, + EntitlementCanExec, + EntitlementCanManageBackups, + EntitlementCanManageSnapshots, + }, nil + case entity.TypeProject: + return []Entitlement{ + EntitlementCanView, + EntitlementCanEdit, + EntitlementCanDelete, + EntitlementProjectOperator, + EntitlementProjectViewer, + EntitlementImageManager, + EntitlementCanCreateImages, + EntitlementCanViewImages, + EntitlementCanEditImages, + EntitlementCanDeleteImages, + EntitlementImageAliasManager, + EntitlementCanCreateImageAliases, + EntitlementCanViewImageAliases, + EntitlementCanEditImageAliases, + EntitlementCanDeleteImageAliases, + EntitlementInstanceManager, + EntitlementCanCreateInstances, + EntitlementCanViewInstances, + EntitlementCanEditInstances, + EntitlementCanDeleteInstances, + EntitlementCanOperateInstances, + EntitlementNetworkManager, + EntitlementCanCreateNetworks, + EntitlementCanViewNetworks, + EntitlementCanEditNetworks, + EntitlementCanDeleteNetworks, + EntitlementNetworkACLManager, + EntitlementCanCreateNetworkACLs, + EntitlementCanViewNetworkACLs, + EntitlementCanEditNetworkACLs, + EntitlementCanDeleteNetworkACLs, + EntitlementNetworkZoneManager, + EntitlementCanCreateNetworkZones, + EntitlementCanViewNetworkZones, + EntitlementCanEditNetworkZones, + EntitlementCanDeleteNetworkZones, + EntitlementProfileManager, + EntitlementCanCreateProfiles, + EntitlementCanViewProfiles, + EntitlementCanEditProfiles, + EntitlementCanDeleteProfiles, + EntitlementStorageVolumeManager, + EntitlementCanCreateStorageVolumes, + EntitlementCanViewStorageVolumes, + EntitlementCanEditStorageVolumes, + EntitlementCanDeleteStorageVolumes, + EntitlementStorageBucketManager, + EntitlementCanCreateStorageBuckets, + EntitlementCanViewStorageBuckets, + EntitlementCanEditStorageBuckets, + EntitlementCanDeleteStorageBuckets, + EntitlementCanViewOperations, + EntitlementCanViewEvents, + }, nil + case entity.TypeServer: + return []Entitlement{ + EntitlementCanView, + EntitlementCanEdit, + EntitlementServerAdmin, + EntitlementServerViewer, + EntitlementCanViewConfiguration, + EntitlementPermissionManager, + EntitlementCanCreateIdentities, + EntitlementCanViewIdentities, + EntitlementCanEditIdentities, + EntitlementCanDeleteIdentities, + EntitlementCanCreateGroups, + EntitlementCanViewGroups, + EntitlementCanEditGroups, + EntitlementCanDeleteGroups, + EntitlementStoragePoolManager, + EntitlementCanCreateStoragePools, + EntitlementCanEditStoragePools, + EntitlementCanDeleteStoragePools, + EntitlementProjectManager, + EntitlementCanCreateProjects, + EntitlementCanViewProjects, + EntitlementCanEditProjects, + EntitlementCanDeleteProjects, + EntitlementCanOverrideClusterTargetRestriction, + EntitlementCanViewPrivilegedEvents, + EntitlementCanViewResources, + EntitlementCanViewMetrics, + EntitlementCanViewWarnings, + }, nil + } + + return nil, fmt.Errorf("Missing entitlements definition for entity type %q", entityType) +} diff --git a/lxd/certificates.go b/lxd/certificates.go index 185a2d187bc2..508e24bd4441 100644 --- a/lxd/certificates.go +++ b/lxd/certificates.go @@ -24,6 +24,7 @@ import ( "github.com/canonical/lxd/lxd/db" dbCluster "github.com/canonical/lxd/lxd/db/cluster" "github.com/canonical/lxd/lxd/db/operationtype" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/lifecycle" "github.com/canonical/lxd/lxd/operations" "github.com/canonical/lxd/lxd/request" @@ -47,7 +48,7 @@ var certificateCmd = APIEndpoint{ Path: "certificates/{fingerprint}", Delete: APIEndpointAction{Handler: certificateDelete, AccessHandler: allowAuthenticated}, - Get: APIEndpointAction{Handler: certificateGet, AccessHandler: allowPermission(auth.ObjectTypeCertificate, auth.EntitlementCanView, "fingerprint")}, + Get: APIEndpointAction{Handler: certificateGet, AccessHandler: allowPermission(entity.TypeCertificate, auth.EntitlementCanView, "fingerprint")}, Patch: APIEndpointAction{Handler: certificatePatch, AccessHandler: allowAuthenticated}, Put: APIEndpointAction{Handler: certificatePut, AccessHandler: allowAuthenticated}, } @@ -136,7 +137,7 @@ func certificatesGet(d *Daemon, r *http.Request) response.Response { recursion := util.IsRecursionRequest(r) s := d.State() - userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, auth.ObjectTypeCertificate) + userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, entity.TypeCertificate) if err != nil { return response.SmartError(err) } @@ -153,7 +154,7 @@ func certificatesGet(d *Daemon, r *http.Request) response.Response { certResponses = make([]api.Certificate, 0, len(baseCerts)) for _, baseCert := range baseCerts { - if !userHasPermission(auth.ObjectCertificate(baseCert.Fingerprint)) { + if !userHasPermission(entity.CertificateURL(baseCert.Fingerprint)) { continue } @@ -176,7 +177,7 @@ func certificatesGet(d *Daemon, r *http.Request) response.Response { body := []string{} for _, identity := range d.identityCache.GetByAuthenticationMethod(api.AuthenticationMethodTLS) { - if !userHasPermission(auth.ObjectCertificate(identity.Identifier)) { + if !userHasPermission(entity.CertificateURL(identity.Identifier)) { continue } @@ -422,7 +423,7 @@ func certificatesPost(d *Daemon, r *http.Request) response.Response { // Handle requests by non-admin users. var userCanCreateCertificates bool - err = s.Authorizer.CheckPermission(r.Context(), r, auth.ObjectServer(), auth.EntitlementCanCreateCertificates) + err = s.Authorizer.CheckPermission(r.Context(), r, entity.ServerURL(), auth.EntitlementCanCreateIdentities) if err == nil { userCanCreateCertificates = true } else if !api.StatusErrorCheck(err, http.StatusForbidden) { @@ -874,7 +875,7 @@ func doCertificateUpdate(d *Daemon, dbInfo api.Certificate, req api.CertificateP } var userCanEditCertificate bool - err = s.Authorizer.CheckPermission(r.Context(), r, auth.ObjectCertificate(dbInfo.Fingerprint), auth.EntitlementCanEdit) + err = s.Authorizer.CheckPermission(r.Context(), r, entity.CertificateURL(dbInfo.Fingerprint), auth.EntitlementCanEdit) if err == nil { userCanEditCertificate = true } else if !api.StatusErrorCheck(err, http.StatusForbidden) { @@ -1030,7 +1031,7 @@ func certificateDelete(d *Daemon, r *http.Request) response.Response { } var userCanEditCertificate bool - err = s.Authorizer.CheckPermission(r.Context(), r, auth.ObjectCertificate(fingerprint), auth.EntitlementCanEdit) + err = s.Authorizer.CheckPermission(r.Context(), r, entity.CertificateURL(fingerprint), auth.EntitlementCanDelete) if err == nil { userCanEditCertificate = true } else if api.StatusErrorCheck(err, http.StatusForbidden) { diff --git a/lxd/cluster/heartbeat.go b/lxd/cluster/heartbeat.go index 57fcb0b91ba9..8015f16e2668 100644 --- a/lxd/cluster/heartbeat.go +++ b/lxd/cluster/heartbeat.go @@ -14,12 +14,12 @@ import ( "github.com/canonical/lxd/lxd/db" "github.com/canonical/lxd/lxd/db/query" "github.com/canonical/lxd/lxd/db/warningtype" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/response" "github.com/canonical/lxd/lxd/task" "github.com/canonical/lxd/lxd/warnings" "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" - "github.com/canonical/lxd/shared/entity" "github.com/canonical/lxd/shared/logger" ) diff --git a/lxd/daemon.go b/lxd/daemon.go index cf22e954874a..d04212e04030 100644 --- a/lxd/daemon.go +++ b/lxd/daemon.go @@ -38,6 +38,7 @@ import ( "github.com/canonical/lxd/lxd/db/warningtype" "github.com/canonical/lxd/lxd/dns" "github.com/canonical/lxd/lxd/endpoints" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/events" "github.com/canonical/lxd/lxd/firewall" "github.com/canonical/lxd/lxd/fsmonitor" @@ -248,17 +249,40 @@ func allowAuthenticated(d *Daemon, r *http.Request) response.Response { // Mux vars should be passed in so that the object we are checking can be created. For example, a certificate object requires // a fingerprint, the mux var for certificate fingerprints is "fingerprint", so that string should be passed in. // Mux vars should always be passed in with the same order they appear in the API route. -func allowPermission(objectType auth.ObjectType, entitlement auth.Entitlement, muxVars ...string) func(d *Daemon, r *http.Request) response.Response { +func allowPermission(entityType entity.Type, entitlement auth.Entitlement, muxVars ...string) func(d *Daemon, r *http.Request) response.Response { return func(d *Daemon, r *http.Request) response.Response { - objectName, err := auth.ObjectFromRequest(r, objectType, muxVars...) - if err != nil { - return response.InternalError(fmt.Errorf("Failed to create authentication object: %w", err)) - } - s := d.State() + var err error + var entityURL *api.URL + if entityType == entity.TypeServer { + // For server permission checks, skip mux var logic. + entityURL = entity.ServerURL() + } else if entityType == entity.TypeProject && len(muxVars) == 0 { + // If we're checking project permissions on a non-project endpoint (e.g. `can_create_instances` on POST /1.0/instances) + // we get the project name from the query parameter. + // If we're checking project permissions on a project endpoint, we expect to get the project name from its path variable + // in the next else block. + entityURL = entity.ProjectURL(request.ProjectParam(r)) + } else { + muxValues := make([]string, 0, len(muxVars)) + vars := mux.Vars(r) + for _, muxVar := range muxVars { + muxValue := vars[muxVar] + if muxValue == "" { + return response.InternalError(fmt.Errorf("Failed to perform permission check: Path argument label %q not found in request URL %q", muxVar, r.URL)) + } + + muxValues = append(muxValues, muxValue) + } + + entityURL, err = entityType.URL(request.QueryParam(r, "project"), request.QueryParam(r, "target"), muxValues...) + if err != nil { + return response.InternalError(fmt.Errorf("Failed to perform permission check: %w", err)) + } + } // Validate whether the user has the needed permission - err = s.Authorizer.CheckPermission(r.Context(), r, objectName, entitlement) + err = s.Authorizer.CheckPermission(r.Context(), r, entityURL, entitlement) if err != nil { return response.SmartError(err) } diff --git a/lxd/db/cluster/entities.go b/lxd/db/cluster/entities.go index ebf7a29c14c3..66ebfea51145 100644 --- a/lxd/db/cluster/entities.go +++ b/lxd/db/cluster/entities.go @@ -4,7 +4,7 @@ import ( "database/sql/driver" "fmt" - "github.com/canonical/lxd/shared/entity" + "github.com/canonical/lxd/lxd/entity" ) // EntityType is a database representation of an entity type. diff --git a/lxd/db/cluster/identities.go b/lxd/db/cluster/identities.go index d4f4aeb11963..0e8012b801bb 100644 --- a/lxd/db/cluster/identities.go +++ b/lxd/db/cluster/identities.go @@ -11,8 +11,8 @@ import ( "fmt" "github.com/canonical/lxd/lxd/certificate" + "github.com/canonical/lxd/lxd/identity" "github.com/canonical/lxd/shared/api" - "github.com/canonical/lxd/shared/auth" ) // Code generation directives. @@ -233,7 +233,7 @@ func (i Identity) ToCertificate() (*Certificate, error) { return nil, fmt.Errorf("Failed to unmarshal certificate identity metadata: %w", err) } - isRestricted, err := auth.IsRestrictedIdentityType(string(i.Type)) + isRestricted, err := identity.IsRestrictedIdentityType(string(i.Type)) if err != nil { return nil, fmt.Errorf("Failed to check restricted status of identity: %w", err) } diff --git a/lxd/db/entity.go b/lxd/db/entity.go index 6fa19fb949b7..169a6861f449 100644 --- a/lxd/db/entity.go +++ b/lxd/db/entity.go @@ -8,8 +8,8 @@ import ( "strings" "github.com/canonical/lxd/lxd/db/cluster" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/shared/api" - "github.com/canonical/lxd/shared/entity" ) // ErrUnknownEntityID describes the unknown entity ID error. diff --git a/lxd/db/operationtype/operation_type.go b/lxd/db/operationtype/operation_type.go index fddfec410c89..3434ce60d5f9 100644 --- a/lxd/db/operationtype/operation_type.go +++ b/lxd/db/operationtype/operation_type.go @@ -2,6 +2,7 @@ package operationtype import ( "github.com/canonical/lxd/lxd/auth" + "github.com/canonical/lxd/lxd/entity" ) // Type is a numeric code indentifying the type of an Operation. @@ -203,82 +204,82 @@ func (t Type) Description() string { } } -// Permission returns the auth.ObjectType and auth.Entitlement required to cancel the operation. -func (t Type) Permission() (auth.ObjectType, auth.Entitlement) { +// Permission returns the entity.Type and auth.Entitlement required to cancel the operation. +func (t Type) Permission() (entity.Type, auth.Entitlement) { switch t { case BackupCreate: - return auth.ObjectTypeInstance, auth.EntitlementCanManageBackups + return entity.TypeInstance, auth.EntitlementCanManageBackups case BackupRename: - return auth.ObjectTypeInstance, auth.EntitlementCanManageBackups + return entity.TypeInstance, auth.EntitlementCanManageBackups case BackupRestore: - return auth.ObjectTypeInstance, auth.EntitlementCanManageBackups + return entity.TypeInstance, auth.EntitlementCanManageBackups case BackupRemove: - return auth.ObjectTypeInstance, auth.EntitlementCanManageBackups + return entity.TypeInstance, auth.EntitlementCanManageBackups case ConsoleShow: - return auth.ObjectTypeInstance, auth.EntitlementCanAccessConsole + return entity.TypeInstance, auth.EntitlementCanAccessConsole case InstanceFreeze: - return auth.ObjectTypeInstance, auth.EntitlementCanUpdateState + return entity.TypeInstance, auth.EntitlementCanUpdateState case InstanceUnfreeze: - return auth.ObjectTypeInstance, auth.EntitlementCanUpdateState + return entity.TypeInstance, auth.EntitlementCanUpdateState case InstanceStart: - return auth.ObjectTypeInstance, auth.EntitlementCanUpdateState + return entity.TypeInstance, auth.EntitlementCanUpdateState case InstanceStop: - return auth.ObjectTypeInstance, auth.EntitlementCanUpdateState + return entity.TypeInstance, auth.EntitlementCanUpdateState case InstanceRestart: - return auth.ObjectTypeInstance, auth.EntitlementCanUpdateState + return entity.TypeInstance, auth.EntitlementCanUpdateState case CommandExec: - return auth.ObjectTypeInstance, auth.EntitlementCanExec + return entity.TypeInstance, auth.EntitlementCanExec case SnapshotCreate: - return auth.ObjectTypeInstance, auth.EntitlementCanManageSnapshots + return entity.TypeInstance, auth.EntitlementCanManageSnapshots case SnapshotRename: - return auth.ObjectTypeInstance, auth.EntitlementCanManageSnapshots + return entity.TypeInstance, auth.EntitlementCanManageSnapshots case SnapshotTransfer: - return auth.ObjectTypeInstance, auth.EntitlementCanManageSnapshots + return entity.TypeInstance, auth.EntitlementCanManageSnapshots case SnapshotUpdate: - return auth.ObjectTypeInstance, auth.EntitlementCanManageSnapshots + return entity.TypeInstance, auth.EntitlementCanManageSnapshots case SnapshotDelete: - return auth.ObjectTypeInstance, auth.EntitlementCanManageSnapshots + return entity.TypeInstance, auth.EntitlementCanManageSnapshots case InstanceCreate: - return auth.ObjectTypeProject, auth.EntitlementCanCreateInstances + return entity.TypeInstance, auth.EntitlementCanEdit case InstanceUpdate: - return auth.ObjectTypeInstance, auth.EntitlementCanEdit + return entity.TypeInstance, auth.EntitlementCanEdit case InstanceRename: - return auth.ObjectTypeInstance, auth.EntitlementCanEdit + return entity.TypeInstance, auth.EntitlementCanEdit case InstanceMigrate: - return auth.ObjectTypeInstance, auth.EntitlementCanEdit + return entity.TypeInstance, auth.EntitlementCanEdit case InstanceLiveMigrate: - return auth.ObjectTypeInstance, auth.EntitlementCanEdit + return entity.TypeInstance, auth.EntitlementCanEdit case InstanceDelete: - return auth.ObjectTypeInstance, auth.EntitlementCanEdit + return entity.TypeInstance, auth.EntitlementCanEdit case InstanceRebuild: - return auth.ObjectTypeInstance, auth.EntitlementCanEdit + return entity.TypeInstance, auth.EntitlementCanEdit case SnapshotRestore: - return auth.ObjectTypeInstance, auth.EntitlementCanEdit + return entity.TypeInstance, auth.EntitlementCanEdit case ImageDownload: - return auth.ObjectTypeImage, auth.EntitlementCanEdit + return entity.TypeImage, auth.EntitlementCanEdit case ImageDelete: - return auth.ObjectTypeImage, auth.EntitlementCanEdit + return entity.TypeImage, auth.EntitlementCanEdit case ImageToken: - return auth.ObjectTypeImage, auth.EntitlementCanEdit + return entity.TypeImage, auth.EntitlementCanEdit case ImageRefresh: - return auth.ObjectTypeImage, auth.EntitlementCanEdit + return entity.TypeImage, auth.EntitlementCanEdit case ImagesUpdate: - return auth.ObjectTypeImage, auth.EntitlementCanEdit + return entity.TypeImage, auth.EntitlementCanEdit case ImagesSynchronize: - return auth.ObjectTypeImage, auth.EntitlementCanEdit + return entity.TypeImage, auth.EntitlementCanEdit case CustomVolumeSnapshotsExpire: - return auth.ObjectTypeStorageVolume, auth.EntitlementCanEdit + return entity.TypeStorageVolume, auth.EntitlementCanEdit case CustomVolumeBackupCreate: - return auth.ObjectTypeStorageVolume, auth.EntitlementCanManageBackups + return entity.TypeStorageVolume, auth.EntitlementCanManageBackups case CustomVolumeBackupRemove: - return auth.ObjectTypeStorageVolume, auth.EntitlementCanManageBackups + return entity.TypeStorageVolume, auth.EntitlementCanManageBackups case CustomVolumeBackupRename: - return auth.ObjectTypeStorageVolume, auth.EntitlementCanManageBackups + return entity.TypeStorageVolume, auth.EntitlementCanManageBackups case CustomVolumeBackupRestore: - return auth.ObjectTypeStorageVolume, auth.EntitlementCanEdit + return entity.TypeStorageVolume, auth.EntitlementCanEdit } return "", "" diff --git a/lxd/db/warnings.go b/lxd/db/warnings.go index fb2282e0e13a..2e62365ef848 100644 --- a/lxd/db/warnings.go +++ b/lxd/db/warnings.go @@ -12,9 +12,9 @@ import ( "github.com/canonical/lxd/lxd/db/cluster" "github.com/canonical/lxd/lxd/db/warningtype" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" - "github.com/canonical/lxd/shared/entity" ) var warningCreate = cluster.RegisterStmt(` diff --git a/lxd/device/disk.go b/lxd/device/disk.go index b65108cd1e23..5127b1e3aa7f 100644 --- a/lxd/device/disk.go +++ b/lxd/device/disk.go @@ -18,6 +18,7 @@ import ( "github.com/canonical/lxd/lxd/db" "github.com/canonical/lxd/lxd/db/warningtype" deviceConfig "github.com/canonical/lxd/lxd/device/config" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/idmap" "github.com/canonical/lxd/lxd/instance" "github.com/canonical/lxd/lxd/instance/instancetype" @@ -28,7 +29,6 @@ import ( "github.com/canonical/lxd/lxd/warnings" "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" - "github.com/canonical/lxd/shared/entity" "github.com/canonical/lxd/shared/logger" "github.com/canonical/lxd/shared/revert" "github.com/canonical/lxd/shared/units" diff --git a/lxd/device/proxy.go b/lxd/device/proxy.go index 47ee1fb6d549..6fc565f4ae30 100644 --- a/lxd/device/proxy.go +++ b/lxd/device/proxy.go @@ -18,6 +18,7 @@ import ( "github.com/canonical/lxd/lxd/db/warningtype" deviceConfig "github.com/canonical/lxd/lxd/device/config" "github.com/canonical/lxd/lxd/device/nictype" + "github.com/canonical/lxd/lxd/entity" firewallDrivers "github.com/canonical/lxd/lxd/firewall/drivers" "github.com/canonical/lxd/lxd/instance" "github.com/canonical/lxd/lxd/instance/instancetype" @@ -29,7 +30,6 @@ import ( "github.com/canonical/lxd/lxd/warnings" "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" - "github.com/canonical/lxd/shared/entity" "github.com/canonical/lxd/shared/logger" "github.com/canonical/lxd/shared/validate" ) diff --git a/shared/entity/type.go b/lxd/entity/type.go similarity index 70% rename from shared/entity/type.go rename to lxd/entity/type.go index 6eae746addf6..1ed9f56193ad 100644 --- a/shared/entity/type.go +++ b/lxd/entity/type.go @@ -3,10 +3,12 @@ package entity import ( "fmt" "net/url" + "runtime" "strings" "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" + "github.com/canonical/lxd/shared/logger" "github.com/canonical/lxd/shared/version" ) @@ -70,6 +72,15 @@ const ( // TypeStorageBucket represents storage bucket resources. TypeStorageBucket Type = "storage_bucket" + + // TypeServer represents the top level /1.0 resource. + TypeServer Type = "server" + + // TypeImageAlias represents image alias resources. + TypeImageAlias Type = "image_alias" + + // TypeNetworkZone represents network zone resources. + TypeNetworkZone Type = "network_zone" ) const ( @@ -97,6 +108,9 @@ var entityTypes = []Type{ TypeWarning, TypeClusterGroup, TypeStorageBucket, + TypeServer, + TypeImageAlias, + TypeNetworkZone, } // String implements fmt.Stringer for Type. @@ -122,7 +136,7 @@ func (t Type) requiresProject() (bool, error) { return false, err } - return !shared.ValueInSlice(t, []Type{TypeProject, TypeCertificate, TypeNode, TypeOperation, TypeStoragePool, TypeWarning, TypeClusterGroup}), nil + return !shared.ValueInSlice(t, []Type{TypeProject, TypeCertificate, TypeNode, TypeOperation, TypeStoragePool, TypeWarning, TypeClusterGroup, TypeServer}), nil } // nRequiredPathArguments returns the number of path arguments (mux variables) that are required to create a unique URL @@ -245,6 +259,12 @@ func (t Type) path() ([]string, error) { return []string{"warnings", pathPlaceholder}, nil case TypeClusterGroup: return []string{"cluster", "groups", pathPlaceholder}, nil + case TypeServer: + return []string{}, nil + case TypeImageAlias: + return []string{"images", "aliases", pathPlaceholder}, nil + case TypeNetworkZone: + return []string{"network-zones", pathPlaceholder}, nil default: return nil, fmt.Errorf("Missing path definition for entity type %q", t) } @@ -256,6 +276,10 @@ func (t Type) path() ([]string, error) { // Type requires a project, then api.ProjectDefaultName is returned as the project name. The returned location is the // value of the "target" query parameter. All returned values are unescaped. func ParseURL(u url.URL) (entityType Type, projectName string, location string, pathArguments []string, err error) { + if u.Path == "/"+version.APIVersion { + return TypeServer, "", "", nil, nil + } + path := u.Path if u.RawPath != "" { path = u.RawPath @@ -316,3 +340,89 @@ entityTypeLoop: return entityType, projectName, u.Query().Get("target"), pathArguments, nil } + +// urlMust is used internally when we know that creation of an *api.URL ought to succeed. If an error does occur an +// empty string is return and the error is logged with as much context as possible, including the file and line number +// of the caller. +func (t Type) urlMust(projectName string, location string, pathArguments ...string) *api.URL { + ref, err := t.URL(projectName, location, pathArguments...) + if err != nil { + logCtx := logger.Ctx{"entity_type": t, "project_name": projectName, "location": location, "path_aguments": pathArguments} + + // Get the second caller (we expect the first caller to be internal to this package since this method is not exported). + _, file, line, ok := runtime.Caller(2) + if ok { + logCtx["caller"] = fmt.Sprintf("%s#%d", file, line) + } + + logger.Error("Failed to create entity URL", logCtx) + return api.NewURL() + } + + return ref +} + +// ProjectURL returns an *api.URL to a Project. +func ProjectURL(projectName string) *api.URL { + return TypeProject.urlMust("", "", projectName) +} + +// InstanceURL returns an *api.URL to an instance. +func InstanceURL(projectName string, instanceName string) *api.URL { + return TypeInstance.urlMust(projectName, "", instanceName) +} + +// ServerURL returns an *api.URL to the server. +func ServerURL() *api.URL { + return TypeServer.urlMust("", "") +} + +// CertificateURL returns an *api.URL to a certificate. +func CertificateURL(fingerprint string) *api.URL { + return TypeCertificate.urlMust("", "", fingerprint) +} + +// ImageURL returns an *api.URL to an image. +func ImageURL(projectName string, imageName string) *api.URL { + return TypeImage.urlMust(projectName, "", imageName) +} + +// ImageAliasURL returns an *api.URL to an image alias. +func ImageAliasURL(projectName string, imageAliasName string) *api.URL { + return TypeImageAlias.urlMust(projectName, "", imageAliasName) +} + +// ProfileURL returns an *api.URL to a profile. +func ProfileURL(projectName string, profileName string) *api.URL { + return TypeProfile.urlMust(projectName, "", profileName) +} + +// NetworkURL returns an *api.URL to a network. +func NetworkURL(projectName string, networkName string) *api.URL { + return TypeNetwork.urlMust(projectName, "", networkName) +} + +// NetworkACLURL returns an *api.URL to a network ACL. +func NetworkACLURL(projectName string, networkACLName string) *api.URL { + return TypeNetworkACL.urlMust(projectName, "", networkACLName) +} + +// NetworkZoneURL returns an *api.URL to a network zone. +func NetworkZoneURL(projectName string, networkZoneName string) *api.URL { + return TypeNetworkZone.urlMust(projectName, "", networkZoneName) +} + +// StoragePoolURL returns an *api.URL to a storage pool. +func StoragePoolURL(storagePoolName string) *api.URL { + return TypeStoragePool.urlMust("", "", storagePoolName) +} + +// StorageVolumeURL returns an *api.URL to a storage volume. +func StorageVolumeURL(projectName string, location string, storagePoolName string, storageVolumeType string, storageVolumeName string) *api.URL { + return TypeStorageVolume.urlMust(projectName, location, storagePoolName, storageVolumeType, storageVolumeName) +} + +// StorageBucketURL returns an *api.URL to a storage bucket. +func StorageBucketURL(projectName string, location string, storagePoolName string, storageBucketName string) *api.URL { + return TypeStorageBucket.urlMust(projectName, location, storagePoolName, storageBucketName) +} diff --git a/shared/entity/type_test.go b/lxd/entity/type_test.go similarity index 100% rename from shared/entity/type_test.go rename to lxd/entity/type_test.go diff --git a/lxd/events.go b/lxd/events.go index 26bc21d9d6f0..fe9b09ab8630 100644 --- a/lxd/events.go +++ b/lxd/events.go @@ -9,6 +9,7 @@ import ( "github.com/canonical/lxd/lxd/auth" "github.com/canonical/lxd/lxd/db" "github.com/canonical/lxd/lxd/db/cluster" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/events" "github.com/canonical/lxd/lxd/request" "github.com/canonical/lxd/lxd/response" @@ -61,19 +62,19 @@ func eventsSocket(s *state.State, r *http.Request, w http.ResponseWriter) error var projectPermissionFunc auth.PermissionChecker if projectName != "" { - err := s.Authorizer.CheckPermission(r.Context(), r, auth.ObjectProject(projectName), auth.EntitlementCanViewEvents) + err := s.Authorizer.CheckPermission(r.Context(), r, entity.ProjectURL(projectName), auth.EntitlementCanViewEvents) if err != nil { return err } } else if allProjects { var err error - projectPermissionFunc, err = s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanViewEvents, auth.ObjectTypeProject) + projectPermissionFunc, err = s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanViewEvents, entity.TypeProject) if err != nil { return err } } - canViewPrivilegedEvents := s.Authorizer.CheckPermission(r.Context(), r, auth.ObjectServer(), auth.EntitlementCanViewPrivilegedEvents) == nil + canViewPrivilegedEvents := s.Authorizer.CheckPermission(r.Context(), r, entity.ServerURL(), auth.EntitlementCanViewPrivilegedEvents) == nil types := strings.Split(r.FormValue("type"), ",") if len(types) == 1 && types[0] == "" { diff --git a/lxd/events/events.go b/lxd/events/events.go index 654dcb5ebbd7..61ce516d860e 100644 --- a/lxd/events/events.go +++ b/lxd/events/events.go @@ -9,6 +9,7 @@ import ( "github.com/google/uuid" "github.com/canonical/lxd/lxd/auth" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" "github.com/canonical/lxd/shared/cancel" @@ -72,7 +73,7 @@ func (s *Server) AddListener(projectName string, allProjects bool, projectPermis } if projectPermissionFunc == nil { - projectPermissionFunc = func(auth.Object) bool { + projectPermissionFunc = func(*api.URL) bool { return true } } @@ -188,7 +189,7 @@ func (s *Server) broadcast(event api.Event, eventSource EventSource) error { } // If the event is project specific, ensure we have permission to view it. - if event.Project != "" && !listener.projectPermissionFunc(auth.ObjectProject(event.Project)) { + if event.Project != "" && !listener.projectPermissionFunc(entity.ProjectURL(event.Project)) { continue } diff --git a/lxd/identity/cache.go b/lxd/identity/cache.go index cf03b6269d21..00d5d5fed484 100644 --- a/lxd/identity/cache.go +++ b/lxd/identity/cache.go @@ -8,7 +8,6 @@ import ( "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" - "github.com/canonical/lxd/shared/auth" ) // Cache represents a thread-safe in-memory cache of the identities in the database. @@ -67,7 +66,7 @@ func (c *Cache) GetByType(identityType string) map[string]CacheEntry { defer c.mu.RUnlock() // Explicitly ignore the error here. It is expected that the caller will use the constants defined in shared/api. - authenticationMethod, _ := auth.AuthenticationMethodFromIdentityType(identityType) + authenticationMethod, _ := AuthenticationMethodFromIdentityType(identityType) entriesByAuthMethod, ok := c.entries[authenticationMethod] if !ok { return nil diff --git a/shared/auth/method.go b/lxd/identity/util.go similarity index 98% rename from shared/auth/method.go rename to lxd/identity/util.go index de676c872398..1035181b9ffe 100644 --- a/shared/auth/method.go +++ b/lxd/identity/util.go @@ -1,4 +1,4 @@ -package auth +package identity import ( "fmt" diff --git a/lxd/images.go b/lxd/images.go index 5d3f14d87421..64de8829cfbb 100644 --- a/lxd/images.go +++ b/lxd/images.go @@ -33,6 +33,7 @@ import ( "github.com/canonical/lxd/lxd/db" dbCluster "github.com/canonical/lxd/lxd/db/cluster" "github.com/canonical/lxd/lxd/db/operationtype" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/instance" "github.com/canonical/lxd/lxd/instance/instancetype" "github.com/canonical/lxd/lxd/lifecycle" @@ -64,46 +65,46 @@ var imagesCmd = APIEndpoint{ var imageCmd = APIEndpoint{ Path: "images/{fingerprint}", - Delete: APIEndpointAction{Handler: imageDelete, AccessHandler: allowPermission(auth.ObjectTypeImage, auth.EntitlementCanEdit, "fingerprint")}, + Delete: APIEndpointAction{Handler: imageDelete, AccessHandler: allowPermission(entity.TypeImage, auth.EntitlementCanDelete, "fingerprint")}, Get: APIEndpointAction{Handler: imageGet, AllowUntrusted: true}, - Patch: APIEndpointAction{Handler: imagePatch, AccessHandler: allowPermission(auth.ObjectTypeImage, auth.EntitlementCanEdit, "fingerprint")}, - Put: APIEndpointAction{Handler: imagePut, AccessHandler: allowPermission(auth.ObjectTypeImage, auth.EntitlementCanEdit, "fingerprint")}, + Patch: APIEndpointAction{Handler: imagePatch, AccessHandler: allowPermission(entity.TypeImage, auth.EntitlementCanEdit, "fingerprint")}, + Put: APIEndpointAction{Handler: imagePut, AccessHandler: allowPermission(entity.TypeImage, auth.EntitlementCanEdit, "fingerprint")}, } var imageExportCmd = APIEndpoint{ Path: "images/{fingerprint}/export", Get: APIEndpointAction{Handler: imageExport, AllowUntrusted: true}, - Post: APIEndpointAction{Handler: imageExportPost, AccessHandler: allowPermission(auth.ObjectTypeImage, auth.EntitlementCanEdit, "fingerprint")}, + Post: APIEndpointAction{Handler: imageExportPost, AccessHandler: allowPermission(entity.TypeImage, auth.EntitlementCanEdit, "fingerprint")}, } var imageSecretCmd = APIEndpoint{ Path: "images/{fingerprint}/secret", - Post: APIEndpointAction{Handler: imageSecret, AccessHandler: allowPermission(auth.ObjectTypeImage, auth.EntitlementCanEdit, "fingerprint")}, + Post: APIEndpointAction{Handler: imageSecret, AccessHandler: allowPermission(entity.TypeImage, auth.EntitlementCanEdit, "fingerprint")}, } var imageRefreshCmd = APIEndpoint{ Path: "images/{fingerprint}/refresh", - Post: APIEndpointAction{Handler: imageRefresh, AccessHandler: allowPermission(auth.ObjectTypeImage, auth.EntitlementCanEdit, "fingerprint")}, + Post: APIEndpointAction{Handler: imageRefresh, AccessHandler: allowPermission(entity.TypeImage, auth.EntitlementCanEdit, "fingerprint")}, } var imageAliasesCmd = APIEndpoint{ Path: "images/aliases", Get: APIEndpointAction{Handler: imageAliasesGet, AccessHandler: allowAuthenticated}, - Post: APIEndpointAction{Handler: imageAliasesPost, AccessHandler: allowPermission(auth.ObjectTypeProject, auth.EntitlementCanCreateImageAliases)}, + Post: APIEndpointAction{Handler: imageAliasesPost, AccessHandler: allowPermission(entity.TypeProject, auth.EntitlementCanCreateImageAliases)}, } var imageAliasCmd = APIEndpoint{ Path: "images/aliases/{name:.*}", - Delete: APIEndpointAction{Handler: imageAliasDelete, AccessHandler: allowPermission(auth.ObjectTypeImageAlias, auth.EntitlementCanEdit, "name")}, + Delete: APIEndpointAction{Handler: imageAliasDelete, AccessHandler: allowPermission(entity.TypeImageAlias, auth.EntitlementCanDelete, "name")}, Get: APIEndpointAction{Handler: imageAliasGet, AllowUntrusted: true}, - Patch: APIEndpointAction{Handler: imageAliasPatch, AccessHandler: allowPermission(auth.ObjectTypeImageAlias, auth.EntitlementCanEdit, "name")}, - Post: APIEndpointAction{Handler: imageAliasPost, AccessHandler: allowPermission(auth.ObjectTypeImageAlias, auth.EntitlementCanEdit, "name")}, - Put: APIEndpointAction{Handler: imageAliasPut, AccessHandler: allowPermission(auth.ObjectTypeImageAlias, auth.EntitlementCanEdit, "name")}, + Patch: APIEndpointAction{Handler: imageAliasPatch, AccessHandler: allowPermission(entity.TypeImageAlias, auth.EntitlementCanEdit, "name")}, + Post: APIEndpointAction{Handler: imageAliasPost, AccessHandler: allowPermission(entity.TypeImageAlias, auth.EntitlementCanEdit, "name")}, + Put: APIEndpointAction{Handler: imageAliasPut, AccessHandler: allowPermission(entity.TypeImageAlias, auth.EntitlementCanEdit, "name")}, } /* @@ -919,7 +920,7 @@ func imagesPost(d *Daemon, r *http.Request) response.Response { projectName := request.ProjectParam(r) var userCanCreateImages bool - err := s.Authorizer.CheckPermission(r.Context(), r, auth.ObjectProject(projectName), auth.EntitlementCanCreateImages) + err := s.Authorizer.CheckPermission(r.Context(), r, entity.ProjectURL(projectName), auth.EntitlementCanCreateImages) if err == nil { userCanCreateImages = true } else if !api.StatusErrorCheck(err, http.StatusForbidden) { @@ -1315,7 +1316,7 @@ func doImagesGet(ctx context.Context, tx *db.ClusterTx, recursion bool, projectN continue } - if !image.Public && !hasPermission(auth.ObjectImage(projectName, fingerprint)) { + if !image.Public && !hasPermission(entity.ImageURL(projectName, fingerprint)) { continue } @@ -1559,7 +1560,7 @@ func imagesGet(d *Daemon, r *http.Request) response.Response { s := d.State() - hasPermission, authorizationErr := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, auth.ObjectTypeImage) + hasPermission, authorizationErr := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, entity.TypeImage) if authorizationErr != nil && !api.StatusErrorCheck(authorizationErr, http.StatusForbidden) { return response.SmartError(authorizationErr) } @@ -2797,7 +2798,7 @@ func imageGet(d *Daemon, r *http.Request) response.Response { } var userCanViewImage bool - err = s.Authorizer.CheckPermission(r.Context(), r, auth.ObjectImage(projectName, fingerprint), auth.EntitlementCanView) + err = s.Authorizer.CheckPermission(r.Context(), r, entity.ImageURL(projectName, fingerprint), auth.EntitlementCanView) if err == nil { userCanViewImage = true } else if !api.StatusErrorCheck(err, http.StatusForbidden) { @@ -3223,7 +3224,7 @@ func imageAliasesGet(d *Daemon, r *http.Request) response.Response { recursion := util.IsRecursionRequest(r) s := d.State() - userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, auth.ObjectTypeImageAlias) + userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, entity.TypeImageAlias) if err != nil { return response.InternalError(fmt.Errorf("Failed to get a permission checker: %w", err)) } @@ -3243,7 +3244,7 @@ func imageAliasesGet(d *Daemon, r *http.Request) response.Response { } for _, name := range names { - if !userHasPermission(auth.ObjectImageAlias(projectName, name)) { + if !userHasPermission(entity.ImageAliasURL(projectName, name)) { continue } @@ -3364,7 +3365,7 @@ func imageAliasGet(d *Daemon, r *http.Request) response.Response { s := d.State() var userCanViewImageAlias bool - err = s.Authorizer.CheckPermission(r.Context(), r, auth.ObjectImageAlias(projectName, name), auth.EntitlementCanView) + err = s.Authorizer.CheckPermission(r.Context(), r, entity.ImageAliasURL(projectName, name), auth.EntitlementCanView) if err == nil { userCanViewImageAlias = true } else if !api.StatusErrorCheck(err, http.StatusForbidden) { @@ -3796,7 +3797,7 @@ func imageExport(d *Daemon, r *http.Request) response.Response { } var userCanViewImage bool - err = s.Authorizer.CheckPermission(r.Context(), r, auth.ObjectImage(projectName, fingerprint), auth.EntitlementCanView) + err = s.Authorizer.CheckPermission(r.Context(), r, entity.ImageURL(projectName, fingerprint), auth.EntitlementCanView) if err == nil { userCanViewImage = true } else if !api.StatusErrorCheck(err, http.StatusForbidden) { diff --git a/lxd/instance/drivers/driver_common.go b/lxd/instance/drivers/driver_common.go index 3732d452435a..1ba06b32a458 100644 --- a/lxd/instance/drivers/driver_common.go +++ b/lxd/instance/drivers/driver_common.go @@ -21,6 +21,7 @@ import ( "github.com/canonical/lxd/lxd/device" deviceConfig "github.com/canonical/lxd/lxd/device/config" "github.com/canonical/lxd/lxd/device/nictype" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/instance" "github.com/canonical/lxd/lxd/instance/instancetype" "github.com/canonical/lxd/lxd/instance/operationlock" @@ -33,7 +34,6 @@ import ( storagePools "github.com/canonical/lxd/lxd/storage" "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" - "github.com/canonical/lxd/shared/entity" "github.com/canonical/lxd/shared/logger" "github.com/canonical/lxd/shared/revert" ) diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index 97e1d8b65ccd..8680a7e94b49 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -48,6 +48,7 @@ import ( "github.com/canonical/lxd/lxd/device" deviceConfig "github.com/canonical/lxd/lxd/device/config" "github.com/canonical/lxd/lxd/device/nictype" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/instance" "github.com/canonical/lxd/lxd/instance/drivers/qmp" "github.com/canonical/lxd/lxd/instance/instancetype" @@ -72,7 +73,6 @@ import ( "github.com/canonical/lxd/lxd/warnings" "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" - "github.com/canonical/lxd/shared/entity" "github.com/canonical/lxd/shared/logger" "github.com/canonical/lxd/shared/osarch" "github.com/canonical/lxd/shared/revert" diff --git a/lxd/instance_logs.go b/lxd/instance_logs.go index 5828870a96e9..654c4013ffbb 100644 --- a/lxd/instance_logs.go +++ b/lxd/instance_logs.go @@ -11,6 +11,7 @@ import ( "github.com/gorilla/mux" "github.com/canonical/lxd/lxd/auth" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/instance" "github.com/canonical/lxd/lxd/lifecycle" "github.com/canonical/lxd/lxd/project" @@ -30,8 +31,8 @@ var instanceLogCmd = APIEndpoint{ {Name: "vmLog", Path: "virtual-machines/{name}/logs/{file}"}, }, - Delete: APIEndpointAction{Handler: instanceLogDelete, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanEdit, "name")}, - Get: APIEndpointAction{Handler: instanceLogGet, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanView, "name")}, + Delete: APIEndpointAction{Handler: instanceLogDelete, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanEdit, "name")}, + Get: APIEndpointAction{Handler: instanceLogGet, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanView, "name")}, } var instanceLogsCmd = APIEndpoint{ @@ -42,7 +43,7 @@ var instanceLogsCmd = APIEndpoint{ {Name: "vmLogs", Path: "virtual-machines/{name}/logs"}, }, - Get: APIEndpointAction{Handler: instanceLogsGet, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanView, "name")}, + Get: APIEndpointAction{Handler: instanceLogsGet, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanView, "name")}, } var instanceExecOutputCmd = APIEndpoint{ @@ -53,8 +54,8 @@ var instanceExecOutputCmd = APIEndpoint{ {Name: "vmExecOutput", Path: "virtual-machines/{name}/logs/exec-output/{file}"}, }, - Delete: APIEndpointAction{Handler: instanceExecOutputDelete, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanExec, "name")}, - Get: APIEndpointAction{Handler: instanceExecOutputGet, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanExec, "name")}, + Delete: APIEndpointAction{Handler: instanceExecOutputDelete, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanExec, "name")}, + Get: APIEndpointAction{Handler: instanceExecOutputGet, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanExec, "name")}, } var instanceExecOutputsCmd = APIEndpoint{ @@ -65,7 +66,7 @@ var instanceExecOutputsCmd = APIEndpoint{ {Name: "vmExecOutputs", Path: "virtual-machines/{name}/logs/exec-output"}, }, - Get: APIEndpointAction{Handler: instanceExecOutputsGet, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanExec, "name")}, + Get: APIEndpointAction{Handler: instanceExecOutputsGet, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanExec, "name")}, } // swagger:operation GET /1.0/instances/{name}/logs instances instance_logs_get diff --git a/lxd/instance_post.go b/lxd/instance_post.go index 0bbd66d132e4..ab8a3a148aa9 100644 --- a/lxd/instance_post.go +++ b/lxd/instance_post.go @@ -17,6 +17,7 @@ import ( "github.com/canonical/lxd/lxd/db" dbCluster "github.com/canonical/lxd/lxd/db/cluster" "github.com/canonical/lxd/lxd/db/operationtype" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/instance" "github.com/canonical/lxd/lxd/instance/instancetype" "github.com/canonical/lxd/lxd/operations" @@ -323,7 +324,7 @@ func instancePost(d *Daemon, r *http.Request) response.Response { if req.Pool != "" || req.Project != "" { // Check if user has access to target project. if req.Project != "" { - err := s.Authorizer.CheckPermission(r.Context(), r, auth.ObjectProject(req.Project), auth.EntitlementCanCreateInstances) + err := s.Authorizer.CheckPermission(r.Context(), r, entity.ProjectURL(req.Project), auth.EntitlementCanCreateInstances) if err != nil { return response.SmartError(err) } diff --git a/lxd/instances.go b/lxd/instances.go index 5666650c66bd..734c52e5b15f 100644 --- a/lxd/instances.go +++ b/lxd/instances.go @@ -14,6 +14,7 @@ import ( "github.com/canonical/lxd/lxd/auth" "github.com/canonical/lxd/lxd/db" "github.com/canonical/lxd/lxd/db/warningtype" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/instance" "github.com/canonical/lxd/lxd/instance/instancetype" "github.com/canonical/lxd/lxd/project" @@ -21,7 +22,6 @@ import ( "github.com/canonical/lxd/lxd/warnings" "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" - "github.com/canonical/lxd/shared/entity" "github.com/canonical/lxd/shared/logger" ) @@ -34,7 +34,7 @@ var instancesCmd = APIEndpoint{ }, Get: APIEndpointAction{Handler: instancesGet, AccessHandler: allowAuthenticated}, - Post: APIEndpointAction{Handler: instancesPost, AccessHandler: allowPermission(auth.ObjectTypeProject, auth.EntitlementCanCreateInstances)}, + Post: APIEndpointAction{Handler: instancesPost, AccessHandler: allowPermission(entity.TypeProject, auth.EntitlementCanCreateInstances)}, Put: APIEndpointAction{Handler: instancesPut, AccessHandler: allowAuthenticated}, } @@ -46,11 +46,11 @@ var instanceCmd = APIEndpoint{ {Name: "vm", Path: "virtual-machines/{name}"}, }, - Get: APIEndpointAction{Handler: instanceGet, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanView, "name")}, - Put: APIEndpointAction{Handler: instancePut, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanEdit, "name")}, - Delete: APIEndpointAction{Handler: instanceDelete, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanEdit, "name")}, - Post: APIEndpointAction{Handler: instancePost, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanEdit, "name")}, - Patch: APIEndpointAction{Handler: instancePatch, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanEdit, "name")}, + Get: APIEndpointAction{Handler: instanceGet, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanView, "name")}, + Put: APIEndpointAction{Handler: instancePut, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanEdit, "name")}, + Delete: APIEndpointAction{Handler: instanceDelete, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanDelete, "name")}, + Post: APIEndpointAction{Handler: instancePost, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanEdit, "name")}, + Patch: APIEndpointAction{Handler: instancePatch, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanEdit, "name")}, } var instanceRebuildCmd = APIEndpoint{ @@ -61,7 +61,7 @@ var instanceRebuildCmd = APIEndpoint{ {Name: "vmRebuild", Path: "virtual-machines/{name}/rebuild"}, }, - Post: APIEndpointAction{Handler: instanceRebuildPost, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanEdit, "name")}, + Post: APIEndpointAction{Handler: instanceRebuildPost, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanEdit, "name")}, } var instanceStateCmd = APIEndpoint{ @@ -72,8 +72,8 @@ var instanceStateCmd = APIEndpoint{ {Name: "vmState", Path: "virtual-machines/{name}/state"}, }, - Get: APIEndpointAction{Handler: instanceState, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanView, "name")}, - Put: APIEndpointAction{Handler: instanceStatePut, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanUpdateState, "name")}, + Get: APIEndpointAction{Handler: instanceState, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanView, "name")}, + Put: APIEndpointAction{Handler: instanceStatePut, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanUpdateState, "name")}, } var instanceSFTPCmd = APIEndpoint{ @@ -84,7 +84,7 @@ var instanceSFTPCmd = APIEndpoint{ {Name: "vmFile", Path: "virtual-machines/{name}/sftp"}, }, - Get: APIEndpointAction{Handler: instanceSFTPHandler, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanConnectSFTP, "name")}, + Get: APIEndpointAction{Handler: instanceSFTPHandler, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanConnectSFTP, "name")}, } var instanceFileCmd = APIEndpoint{ @@ -95,10 +95,10 @@ var instanceFileCmd = APIEndpoint{ {Name: "vmFile", Path: "virtual-machines/{name}/files"}, }, - Get: APIEndpointAction{Handler: instanceFileHandler, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanAccessFiles, "name")}, - Head: APIEndpointAction{Handler: instanceFileHandler, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanAccessFiles, "name")}, - Post: APIEndpointAction{Handler: instanceFileHandler, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanAccessFiles, "name")}, - Delete: APIEndpointAction{Handler: instanceFileHandler, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanAccessFiles, "name")}, + Get: APIEndpointAction{Handler: instanceFileHandler, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanAccessFiles, "name")}, + Head: APIEndpointAction{Handler: instanceFileHandler, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanAccessFiles, "name")}, + Post: APIEndpointAction{Handler: instanceFileHandler, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanAccessFiles, "name")}, + Delete: APIEndpointAction{Handler: instanceFileHandler, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanAccessFiles, "name")}, } var instanceSnapshotsCmd = APIEndpoint{ @@ -109,8 +109,8 @@ var instanceSnapshotsCmd = APIEndpoint{ {Name: "vmSnapshots", Path: "virtual-machines/{name}/snapshots"}, }, - Get: APIEndpointAction{Handler: instanceSnapshotsGet, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanView, "name")}, - Post: APIEndpointAction{Handler: instanceSnapshotsPost, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanManageSnapshots, "name")}, + Get: APIEndpointAction{Handler: instanceSnapshotsGet, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanView, "name")}, + Post: APIEndpointAction{Handler: instanceSnapshotsPost, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanManageSnapshots, "name")}, } var instanceSnapshotCmd = APIEndpoint{ @@ -121,11 +121,11 @@ var instanceSnapshotCmd = APIEndpoint{ {Name: "vmSnapshot", Path: "virtual-machines/{name}/snapshots/{snapshotName}"}, }, - Get: APIEndpointAction{Handler: instanceSnapshotHandler, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanView, "name")}, - Post: APIEndpointAction{Handler: instanceSnapshotHandler, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanManageSnapshots, "name")}, - Delete: APIEndpointAction{Handler: instanceSnapshotHandler, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanManageSnapshots, "name")}, - Patch: APIEndpointAction{Handler: instanceSnapshotHandler, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanManageSnapshots, "name")}, - Put: APIEndpointAction{Handler: instanceSnapshotHandler, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanManageSnapshots, "name")}, + Get: APIEndpointAction{Handler: instanceSnapshotHandler, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanView, "name")}, + Post: APIEndpointAction{Handler: instanceSnapshotHandler, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanManageSnapshots, "name")}, + Delete: APIEndpointAction{Handler: instanceSnapshotHandler, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanManageSnapshots, "name")}, + Patch: APIEndpointAction{Handler: instanceSnapshotHandler, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanManageSnapshots, "name")}, + Put: APIEndpointAction{Handler: instanceSnapshotHandler, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanManageSnapshots, "name")}, } var instanceConsoleCmd = APIEndpoint{ @@ -136,9 +136,9 @@ var instanceConsoleCmd = APIEndpoint{ {Name: "vmConsole", Path: "virtual-machines/{name}/console"}, }, - Get: APIEndpointAction{Handler: instanceConsoleLogGet, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanView, "name")}, - Post: APIEndpointAction{Handler: instanceConsolePost, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanAccessConsole, "name")}, - Delete: APIEndpointAction{Handler: instanceConsoleLogDelete, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanEdit, "name")}, + Get: APIEndpointAction{Handler: instanceConsoleLogGet, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanView, "name")}, + Post: APIEndpointAction{Handler: instanceConsolePost, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanAccessConsole, "name")}, + Delete: APIEndpointAction{Handler: instanceConsoleLogDelete, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanEdit, "name")}, } var instanceExecCmd = APIEndpoint{ @@ -149,7 +149,7 @@ var instanceExecCmd = APIEndpoint{ {Name: "vmExec", Path: "virtual-machines/{name}/exec"}, }, - Post: APIEndpointAction{Handler: instanceExecPost, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanExec, "name")}, + Post: APIEndpointAction{Handler: instanceExecPost, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanExec, "name")}, } var instanceMetadataCmd = APIEndpoint{ @@ -160,9 +160,9 @@ var instanceMetadataCmd = APIEndpoint{ {Name: "vmMetadata", Path: "virtual-machines/{name}/metadata"}, }, - Get: APIEndpointAction{Handler: instanceMetadataGet, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanView, "name")}, - Patch: APIEndpointAction{Handler: instanceMetadataPatch, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanEdit, "name")}, - Put: APIEndpointAction{Handler: instanceMetadataPut, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanEdit, "name")}, + Get: APIEndpointAction{Handler: instanceMetadataGet, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanView, "name")}, + Patch: APIEndpointAction{Handler: instanceMetadataPatch, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanEdit, "name")}, + Put: APIEndpointAction{Handler: instanceMetadataPut, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanEdit, "name")}, } var instanceMetadataTemplatesCmd = APIEndpoint{ @@ -173,9 +173,9 @@ var instanceMetadataTemplatesCmd = APIEndpoint{ {Name: "vmMetadataTemplates", Path: "virtual-machines/{name}/metadata/templates"}, }, - Get: APIEndpointAction{Handler: instanceMetadataTemplatesGet, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanView, "name")}, - Post: APIEndpointAction{Handler: instanceMetadataTemplatesPost, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanEdit, "name")}, - Delete: APIEndpointAction{Handler: instanceMetadataTemplatesDelete, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanEdit, "name")}, + Get: APIEndpointAction{Handler: instanceMetadataTemplatesGet, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanView, "name")}, + Post: APIEndpointAction{Handler: instanceMetadataTemplatesPost, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanEdit, "name")}, + Delete: APIEndpointAction{Handler: instanceMetadataTemplatesDelete, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanEdit, "name")}, } var instanceBackupsCmd = APIEndpoint{ @@ -186,8 +186,8 @@ var instanceBackupsCmd = APIEndpoint{ {Name: "vmBackups", Path: "virtual-machines/{name}/backups"}, }, - Get: APIEndpointAction{Handler: instanceBackupsGet, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanView, "name")}, - Post: APIEndpointAction{Handler: instanceBackupsPost, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanManageBackups, "name")}, + Get: APIEndpointAction{Handler: instanceBackupsGet, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanView, "name")}, + Post: APIEndpointAction{Handler: instanceBackupsPost, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanManageBackups, "name")}, } var instanceBackupCmd = APIEndpoint{ @@ -198,9 +198,9 @@ var instanceBackupCmd = APIEndpoint{ {Name: "vmBackup", Path: "virtual-machines/{name}/backups/{backupName}"}, }, - Get: APIEndpointAction{Handler: instanceBackupGet, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanView, "name")}, - Post: APIEndpointAction{Handler: instanceBackupPost, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanManageBackups, "name")}, - Delete: APIEndpointAction{Handler: instanceBackupDelete, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanManageBackups, "name")}, + Get: APIEndpointAction{Handler: instanceBackupGet, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanView, "name")}, + Post: APIEndpointAction{Handler: instanceBackupPost, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanManageBackups, "name")}, + Delete: APIEndpointAction{Handler: instanceBackupDelete, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanManageBackups, "name")}, } var instanceBackupExportCmd = APIEndpoint{ @@ -211,7 +211,7 @@ var instanceBackupExportCmd = APIEndpoint{ {Name: "vmBackupExport", Path: "virtual-machines/{name}/backups/{backupName}/export"}, }, - Get: APIEndpointAction{Handler: instanceBackupExportGet, AccessHandler: allowPermission(auth.ObjectTypeInstance, auth.EntitlementCanManageBackups, "name")}, + Get: APIEndpointAction{Handler: instanceBackupExportGet, AccessHandler: allowPermission(entity.TypeInstance, auth.EntitlementCanManageBackups, "name")}, } type instanceAutostartList []instance.Instance diff --git a/lxd/instances_get.go b/lxd/instances_get.go index 851307eff6fe..1fa0d89d47bb 100644 --- a/lxd/instances_get.go +++ b/lxd/instances_get.go @@ -18,6 +18,7 @@ import ( "github.com/canonical/lxd/lxd/db" dbCluster "github.com/canonical/lxd/lxd/db/cluster" "github.com/canonical/lxd/lxd/db/query" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/instance" "github.com/canonical/lxd/lxd/instance/instancetype" "github.com/canonical/lxd/lxd/request" @@ -306,7 +307,7 @@ func doInstancesGet(s *state.State, r *http.Request) (any, error) { return nil, err } - userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, auth.ObjectTypeInstance) + userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, entity.TypeInstance) if err != nil { return nil, err } @@ -316,7 +317,7 @@ func doInstancesGet(s *state.State, r *http.Request) (any, error) { var filteredInstances []db.Instance for _, inst := range instances { - if !userHasPermission(auth.ObjectInstance(inst.Project, inst.Name)) { + if !userHasPermission(entity.InstanceURL(inst.Project, inst.Name)) { continue } diff --git a/lxd/instances_put.go b/lxd/instances_put.go index faa98ef91587..86fac22b1f1e 100644 --- a/lxd/instances_put.go +++ b/lxd/instances_put.go @@ -11,6 +11,7 @@ import ( "github.com/canonical/lxd/lxd/auth" "github.com/canonical/lxd/lxd/cluster" "github.com/canonical/lxd/lxd/db" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/instance" "github.com/canonical/lxd/lxd/instance/instancetype" "github.com/canonical/lxd/lxd/operations" @@ -96,7 +97,7 @@ func instancesPut(d *Daemon, r *http.Request) response.Response { action := instancetype.InstanceAction(req.State.Action) - userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanUpdateState, auth.ObjectTypeInstance) + userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanUpdateState, entity.TypeInstance) if err != nil { return response.SmartError(err) } @@ -109,7 +110,7 @@ func instancesPut(d *Daemon, r *http.Request) response.Response { } // Only allow changing the state of instances the user has permission for. - if !userHasPermission(auth.ObjectInstance(inst.Project().Name, inst.Name())) { + if !userHasPermission(entity.InstanceURL(inst.Project().Name, inst.Name())) { continue } diff --git a/lxd/metrics/metrics.go b/lxd/metrics/metrics.go index 42b3319655a4..6d9d0a8f8f33 100644 --- a/lxd/metrics/metrics.go +++ b/lxd/metrics/metrics.go @@ -6,8 +6,9 @@ import ( "strconv" "strings" - "github.com/canonical/lxd/lxd/auth" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/shared" + "github.com/canonical/lxd/shared/api" ) // NewMetricSet returns a new MetricSet. @@ -24,7 +25,7 @@ func NewMetricSet(labels map[string]string) *MetricSet { } // FilterSamples filters the existing MetricSet using the given permission checker. Samples not containing the "project" label are skipped. -func (m *MetricSet) FilterSamples(permissionChecker func(object auth.Object) bool) { +func (m *MetricSet) FilterSamples(permissionChecker func(entityURL *api.URL) bool) { for metricType, samples := range m.set { allowedSamples := make([]Sample, 0, len(samples)) for _, s := range samples { @@ -37,9 +38,9 @@ func (m *MetricSet) FilterSamples(permissionChecker func(object auth.Object) boo var hasPermission bool if instanceName != "" { - hasPermission = permissionChecker(auth.ObjectInstance(projectName, instanceName)) + hasPermission = permissionChecker(entity.InstanceURL(projectName, instanceName)) } else { - hasPermission = permissionChecker(auth.ObjectProject(projectName)) + hasPermission = permissionChecker(entity.ProjectURL(projectName)) } if hasPermission { diff --git a/lxd/metrics/metrics_test.go b/lxd/metrics/metrics_test.go index 2b6207d3f4fc..2e6dcb6a0e0d 100644 --- a/lxd/metrics/metrics_test.go +++ b/lxd/metrics/metrics_test.go @@ -5,7 +5,8 @@ import ( "github.com/stretchr/testify/require" - "github.com/canonical/lxd/lxd/auth" + "github.com/canonical/lxd/lxd/entity" + "github.com/canonical/lxd/shared/api" ) func TestMetricSet_FilterSamples(t *testing.T) { @@ -19,8 +20,8 @@ func TestMetricSet_FilterSamples(t *testing.T) { } m := newMetricSet() - permissionChecker := func(object auth.Object) bool { - return object == auth.ObjectInstance("default", "jammy") + permissionChecker := func(u *api.URL) bool { + return u.String() == entity.InstanceURL("default", "jammy").String() } m.FilterSamples(permissionChecker) @@ -29,8 +30,8 @@ func TestMetricSet_FilterSamples(t *testing.T) { require.Equal(t, []Sample{{Value: 10, Labels: labels}}, m.set[CPUSecondsTotal]) m = newMetricSet() - permissionChecker = func(object auth.Object) bool { - return object == auth.ObjectInstance("not-default", "not-jammy") + permissionChecker = func(u *api.URL) bool { + return u.String() == entity.InstanceURL("not-default", "jammy").String() } m.FilterSamples(permissionChecker) diff --git a/lxd/network/driver_bridge.go b/lxd/network/driver_bridge.go index f4e85795a688..183cc4450333 100644 --- a/lxd/network/driver_bridge.go +++ b/lxd/network/driver_bridge.go @@ -27,6 +27,7 @@ import ( "github.com/canonical/lxd/lxd/db/warningtype" "github.com/canonical/lxd/lxd/dnsmasq" "github.com/canonical/lxd/lxd/dnsmasq/dhcpalloc" + "github.com/canonical/lxd/lxd/entity" firewallDrivers "github.com/canonical/lxd/lxd/firewall/drivers" "github.com/canonical/lxd/lxd/ip" "github.com/canonical/lxd/lxd/network/acl" @@ -37,7 +38,6 @@ import ( "github.com/canonical/lxd/lxd/warnings" "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" - "github.com/canonical/lxd/shared/entity" "github.com/canonical/lxd/shared/logger" "github.com/canonical/lxd/shared/revert" "github.com/canonical/lxd/shared/validate" diff --git a/lxd/network/driver_common.go b/lxd/network/driver_common.go index 193d8d734315..97661d993292 100644 --- a/lxd/network/driver_common.go +++ b/lxd/network/driver_common.go @@ -14,12 +14,12 @@ import ( "github.com/canonical/lxd/lxd/cluster/request" "github.com/canonical/lxd/lxd/db" dbCluster "github.com/canonical/lxd/lxd/db/cluster" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/network/acl" "github.com/canonical/lxd/lxd/resources" "github.com/canonical/lxd/lxd/state" "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" - "github.com/canonical/lxd/shared/entity" "github.com/canonical/lxd/shared/logger" "github.com/canonical/lxd/shared/validate" "github.com/canonical/lxd/shared/version" diff --git a/lxd/network_acls.go b/lxd/network_acls.go index 3e79cfd49cc6..1c08313227da 100644 --- a/lxd/network_acls.go +++ b/lxd/network_acls.go @@ -12,6 +12,7 @@ import ( "github.com/canonical/lxd/lxd/auth" clusterRequest "github.com/canonical/lxd/lxd/cluster/request" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/lifecycle" "github.com/canonical/lxd/lxd/network/acl" "github.com/canonical/lxd/lxd/project" @@ -27,23 +28,23 @@ var networkACLsCmd = APIEndpoint{ Path: "network-acls", Get: APIEndpointAction{Handler: networkACLsGet, AccessHandler: allowAuthenticated}, - Post: APIEndpointAction{Handler: networkACLsPost, AccessHandler: allowPermission(auth.ObjectTypeProject, auth.EntitlementCanCreateNetworkACLs)}, + Post: APIEndpointAction{Handler: networkACLsPost, AccessHandler: allowPermission(entity.TypeProject, auth.EntitlementCanCreateNetworkACLs)}, } var networkACLCmd = APIEndpoint{ Path: "network-acls/{name}", - Delete: APIEndpointAction{Handler: networkACLDelete, AccessHandler: allowPermission(auth.ObjectTypeNetworkACL, auth.EntitlementCanEdit, "name")}, - Get: APIEndpointAction{Handler: networkACLGet, AccessHandler: allowPermission(auth.ObjectTypeNetworkACL, auth.EntitlementCanView, "name")}, - Put: APIEndpointAction{Handler: networkACLPut, AccessHandler: allowPermission(auth.ObjectTypeNetworkACL, auth.EntitlementCanEdit, "name")}, - Patch: APIEndpointAction{Handler: networkACLPut, AccessHandler: allowPermission(auth.ObjectTypeNetworkACL, auth.EntitlementCanEdit, "name")}, - Post: APIEndpointAction{Handler: networkACLPost, AccessHandler: allowPermission(auth.ObjectTypeNetworkACL, auth.EntitlementCanEdit, "name")}, + Delete: APIEndpointAction{Handler: networkACLDelete, AccessHandler: allowPermission(entity.TypeNetworkACL, auth.EntitlementCanDelete, "name")}, + Get: APIEndpointAction{Handler: networkACLGet, AccessHandler: allowPermission(entity.TypeNetworkACL, auth.EntitlementCanView, "name")}, + Put: APIEndpointAction{Handler: networkACLPut, AccessHandler: allowPermission(entity.TypeNetworkACL, auth.EntitlementCanEdit, "name")}, + Patch: APIEndpointAction{Handler: networkACLPut, AccessHandler: allowPermission(entity.TypeNetworkACL, auth.EntitlementCanEdit, "name")}, + Post: APIEndpointAction{Handler: networkACLPost, AccessHandler: allowPermission(entity.TypeNetworkACL, auth.EntitlementCanEdit, "name")}, } var networkACLLogCmd = APIEndpoint{ Path: "network-acls/{name}/log", - Get: APIEndpointAction{Handler: networkACLLogGet, AccessHandler: allowPermission(auth.ObjectTypeNetworkACL, auth.EntitlementCanView, "name")}, + Get: APIEndpointAction{Handler: networkACLLogGet, AccessHandler: allowPermission(entity.TypeNetworkACL, auth.EntitlementCanView, "name")}, } // API endpoints. @@ -156,7 +157,7 @@ func networkACLsGet(d *Daemon, r *http.Request) response.Response { return response.InternalError(err) } - userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, auth.ObjectTypeNetworkACL) + userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, entity.TypeNetworkACL) if err != nil { return response.SmartError(err) } @@ -164,7 +165,7 @@ func networkACLsGet(d *Daemon, r *http.Request) response.Response { resultString := []string{} resultMap := []api.NetworkACL{} for _, aclName := range aclNames { - if !userHasPermission(auth.ObjectNetworkACL(projectName, aclName)) { + if !userHasPermission(entity.NetworkACLURL(projectName, aclName)) { continue } diff --git a/lxd/network_allocations.go b/lxd/network_allocations.go index 026662cf1553..3464ca662402 100644 --- a/lxd/network_allocations.go +++ b/lxd/network_allocations.go @@ -11,6 +11,7 @@ import ( clusterRequest "github.com/canonical/lxd/lxd/cluster/request" "github.com/canonical/lxd/lxd/db" dbCluster "github.com/canonical/lxd/lxd/db/cluster" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/network" "github.com/canonical/lxd/lxd/project" "github.com/canonical/lxd/lxd/request" @@ -118,7 +119,7 @@ func networkAllocationsGet(d *Daemon, r *http.Request) response.Response { result := make([]api.NetworkAllocations, 0) - userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, auth.ObjectTypeNetwork) + userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, entity.TypeNetwork) if err != nil { return response.SmartError(err) } @@ -132,7 +133,7 @@ func networkAllocationsGet(d *Daemon, r *http.Request) response.Response { // Get all the networks, their attached instances, their network forwards and their network load balancers. for _, networkName := range networkNames { - if !userHasPermission(auth.ObjectNetwork(projectName, networkName)) { + if !userHasPermission(entity.NetworkURL(projectName, networkName)) { continue } diff --git a/lxd/network_forwards.go b/lxd/network_forwards.go index f4bab2a618a5..7530731fa01b 100644 --- a/lxd/network_forwards.go +++ b/lxd/network_forwards.go @@ -10,6 +10,7 @@ import ( "github.com/canonical/lxd/lxd/auth" clusterRequest "github.com/canonical/lxd/lxd/cluster/request" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/lifecycle" "github.com/canonical/lxd/lxd/network" "github.com/canonical/lxd/lxd/project" @@ -23,17 +24,17 @@ import ( var networkForwardsCmd = APIEndpoint{ Path: "networks/{networkName}/forwards", - Get: APIEndpointAction{Handler: networkForwardsGet, AccessHandler: allowPermission(auth.ObjectTypeNetwork, auth.EntitlementCanView, "networkName")}, - Post: APIEndpointAction{Handler: networkForwardsPost, AccessHandler: allowPermission(auth.ObjectTypeNetwork, auth.EntitlementCanEdit, "networkName")}, + Get: APIEndpointAction{Handler: networkForwardsGet, AccessHandler: allowPermission(entity.TypeNetwork, auth.EntitlementCanView, "networkName")}, + Post: APIEndpointAction{Handler: networkForwardsPost, AccessHandler: allowPermission(entity.TypeNetwork, auth.EntitlementCanEdit, "networkName")}, } var networkForwardCmd = APIEndpoint{ Path: "networks/{networkName}/forwards/{listenAddress}", - Delete: APIEndpointAction{Handler: networkForwardDelete, AccessHandler: allowPermission(auth.ObjectTypeNetwork, auth.EntitlementCanEdit, "networkName")}, - Get: APIEndpointAction{Handler: networkForwardGet, AccessHandler: allowPermission(auth.ObjectTypeNetwork, auth.EntitlementCanView, "networkName")}, - Put: APIEndpointAction{Handler: networkForwardPut, AccessHandler: allowPermission(auth.ObjectTypeNetwork, auth.EntitlementCanEdit, "networkName")}, - Patch: APIEndpointAction{Handler: networkForwardPut, AccessHandler: allowPermission(auth.ObjectTypeNetwork, auth.EntitlementCanEdit, "networkName")}, + Delete: APIEndpointAction{Handler: networkForwardDelete, AccessHandler: allowPermission(entity.TypeNetwork, auth.EntitlementCanDelete, "networkName")}, + Get: APIEndpointAction{Handler: networkForwardGet, AccessHandler: allowPermission(entity.TypeNetwork, auth.EntitlementCanView, "networkName")}, + Put: APIEndpointAction{Handler: networkForwardPut, AccessHandler: allowPermission(entity.TypeNetwork, auth.EntitlementCanEdit, "networkName")}, + Patch: APIEndpointAction{Handler: networkForwardPut, AccessHandler: allowPermission(entity.TypeNetwork, auth.EntitlementCanEdit, "networkName")}, } // API endpoints diff --git a/lxd/network_load_balancers.go b/lxd/network_load_balancers.go index 7182eeef511e..67293ea2929b 100644 --- a/lxd/network_load_balancers.go +++ b/lxd/network_load_balancers.go @@ -10,6 +10,7 @@ import ( "github.com/canonical/lxd/lxd/auth" clusterRequest "github.com/canonical/lxd/lxd/cluster/request" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/lifecycle" "github.com/canonical/lxd/lxd/network" "github.com/canonical/lxd/lxd/project" @@ -23,17 +24,17 @@ import ( var networkLoadBalancersCmd = APIEndpoint{ Path: "networks/{networkName}/load-balancers", - Get: APIEndpointAction{Handler: networkLoadBalancersGet, AccessHandler: allowPermission(auth.ObjectTypeNetwork, auth.EntitlementCanView, "networkName")}, - Post: APIEndpointAction{Handler: networkLoadBalancersPost, AccessHandler: allowPermission(auth.ObjectTypeNetwork, auth.EntitlementCanEdit, "networkName")}, + Get: APIEndpointAction{Handler: networkLoadBalancersGet, AccessHandler: allowPermission(entity.TypeNetwork, auth.EntitlementCanView, "networkName")}, + Post: APIEndpointAction{Handler: networkLoadBalancersPost, AccessHandler: allowPermission(entity.TypeNetwork, auth.EntitlementCanEdit, "networkName")}, } var networkLoadBalancerCmd = APIEndpoint{ Path: "networks/{networkName}/load-balancers/{listenAddress}", - Delete: APIEndpointAction{Handler: networkLoadBalancerDelete, AccessHandler: allowPermission(auth.ObjectTypeNetwork, auth.EntitlementCanEdit, "networkName")}, - Get: APIEndpointAction{Handler: networkLoadBalancerGet, AccessHandler: allowPermission(auth.ObjectTypeNetwork, auth.EntitlementCanView, "networkName")}, - Put: APIEndpointAction{Handler: networkLoadBalancerPut, AccessHandler: allowPermission(auth.ObjectTypeNetwork, auth.EntitlementCanEdit, "networkName")}, - Patch: APIEndpointAction{Handler: networkLoadBalancerPut, AccessHandler: allowPermission(auth.ObjectTypeNetwork, auth.EntitlementCanEdit, "networkName")}, + Delete: APIEndpointAction{Handler: networkLoadBalancerDelete, AccessHandler: allowPermission(entity.TypeNetwork, auth.EntitlementCanEdit, "networkName")}, + Get: APIEndpointAction{Handler: networkLoadBalancerGet, AccessHandler: allowPermission(entity.TypeNetwork, auth.EntitlementCanView, "networkName")}, + Put: APIEndpointAction{Handler: networkLoadBalancerPut, AccessHandler: allowPermission(entity.TypeNetwork, auth.EntitlementCanEdit, "networkName")}, + Patch: APIEndpointAction{Handler: networkLoadBalancerPut, AccessHandler: allowPermission(entity.TypeNetwork, auth.EntitlementCanEdit, "networkName")}, } // API endpoints diff --git a/lxd/network_peer.go b/lxd/network_peer.go index 154225d4647a..ed3d021c5916 100644 --- a/lxd/network_peer.go +++ b/lxd/network_peer.go @@ -9,6 +9,7 @@ import ( "github.com/gorilla/mux" "github.com/canonical/lxd/lxd/auth" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/lifecycle" "github.com/canonical/lxd/lxd/network" "github.com/canonical/lxd/lxd/project" @@ -22,17 +23,17 @@ import ( var networkPeersCmd = APIEndpoint{ Path: "networks/{networkName}/peers", - Get: APIEndpointAction{Handler: networkPeersGet, AccessHandler: allowPermission(auth.ObjectTypeNetwork, auth.EntitlementCanView, "networkName")}, - Post: APIEndpointAction{Handler: networkPeersPost, AccessHandler: allowPermission(auth.ObjectTypeNetwork, auth.EntitlementCanEdit, "networkName")}, + Get: APIEndpointAction{Handler: networkPeersGet, AccessHandler: allowPermission(entity.TypeNetwork, auth.EntitlementCanView, "networkName")}, + Post: APIEndpointAction{Handler: networkPeersPost, AccessHandler: allowPermission(entity.TypeNetwork, auth.EntitlementCanEdit, "networkName")}, } var networkPeerCmd = APIEndpoint{ Path: "networks/{networkName}/peers/{peerName}", - Delete: APIEndpointAction{Handler: networkPeerDelete, AccessHandler: allowPermission(auth.ObjectTypeNetwork, auth.EntitlementCanEdit, "networkName")}, - Get: APIEndpointAction{Handler: networkPeerGet, AccessHandler: allowPermission(auth.ObjectTypeNetwork, auth.EntitlementCanView, "networkName")}, - Put: APIEndpointAction{Handler: networkPeerPut, AccessHandler: allowPermission(auth.ObjectTypeNetwork, auth.EntitlementCanEdit, "networkName")}, - Patch: APIEndpointAction{Handler: networkPeerPut, AccessHandler: allowPermission(auth.ObjectTypeNetwork, auth.EntitlementCanEdit, "networkName")}, + Delete: APIEndpointAction{Handler: networkPeerDelete, AccessHandler: allowPermission(entity.TypeNetwork, auth.EntitlementCanEdit, "networkName")}, + Get: APIEndpointAction{Handler: networkPeerGet, AccessHandler: allowPermission(entity.TypeNetwork, auth.EntitlementCanView, "networkName")}, + Put: APIEndpointAction{Handler: networkPeerPut, AccessHandler: allowPermission(entity.TypeNetwork, auth.EntitlementCanEdit, "networkName")}, + Patch: APIEndpointAction{Handler: networkPeerPut, AccessHandler: allowPermission(entity.TypeNetwork, auth.EntitlementCanEdit, "networkName")}, } // API endpoints diff --git a/lxd/network_zones.go b/lxd/network_zones.go index 22613e4bfb26..5c1553c298bd 100644 --- a/lxd/network_zones.go +++ b/lxd/network_zones.go @@ -10,6 +10,7 @@ import ( "github.com/canonical/lxd/lxd/auth" clusterRequest "github.com/canonical/lxd/lxd/cluster/request" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/lifecycle" "github.com/canonical/lxd/lxd/network/zone" "github.com/canonical/lxd/lxd/project" @@ -25,16 +26,16 @@ var networkZonesCmd = APIEndpoint{ Path: "network-zones", Get: APIEndpointAction{Handler: networkZonesGet, AccessHandler: allowAuthenticated}, - Post: APIEndpointAction{Handler: networkZonesPost, AccessHandler: allowPermission(auth.ObjectTypeProject, auth.EntitlementCanCreateNetworkZones)}, + Post: APIEndpointAction{Handler: networkZonesPost, AccessHandler: allowPermission(entity.TypeProject, auth.EntitlementCanCreateNetworkZones)}, } var networkZoneCmd = APIEndpoint{ Path: "network-zones/{zone}", - Delete: APIEndpointAction{Handler: networkZoneDelete, AccessHandler: allowPermission(auth.ObjectTypeNetworkZone, auth.EntitlementCanEdit, "zone")}, - Get: APIEndpointAction{Handler: networkZoneGet, AccessHandler: allowPermission(auth.ObjectTypeNetworkZone, auth.EntitlementCanView, "zone")}, - Put: APIEndpointAction{Handler: networkZonePut, AccessHandler: allowPermission(auth.ObjectTypeNetworkZone, auth.EntitlementCanEdit, "zone")}, - Patch: APIEndpointAction{Handler: networkZonePut, AccessHandler: allowPermission(auth.ObjectTypeNetworkZone, auth.EntitlementCanEdit, "zone")}, + Delete: APIEndpointAction{Handler: networkZoneDelete, AccessHandler: allowPermission(entity.TypeNetworkZone, auth.EntitlementCanDelete, "zone")}, + Get: APIEndpointAction{Handler: networkZoneGet, AccessHandler: allowPermission(entity.TypeNetworkZone, auth.EntitlementCanView, "zone")}, + Put: APIEndpointAction{Handler: networkZonePut, AccessHandler: allowPermission(entity.TypeNetworkZone, auth.EntitlementCanEdit, "zone")}, + Patch: APIEndpointAction{Handler: networkZonePut, AccessHandler: allowPermission(entity.TypeNetworkZone, auth.EntitlementCanEdit, "zone")}, } // API endpoints. @@ -147,7 +148,7 @@ func networkZonesGet(d *Daemon, r *http.Request) response.Response { return response.InternalError(err) } - userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, auth.ObjectTypeNetworkZone) + userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, entity.TypeNetworkZone) if err != nil { return response.InternalError(err) } @@ -155,7 +156,7 @@ func networkZonesGet(d *Daemon, r *http.Request) response.Response { resultString := []string{} resultMap := []api.NetworkZone{} for _, zoneName := range zoneNames { - if !userHasPermission(auth.ObjectNetworkZone(projectName, zoneName)) { + if !userHasPermission(entity.NetworkZoneURL(projectName, zoneName)) { continue } diff --git a/lxd/network_zones_records.go b/lxd/network_zones_records.go index c3e45f6bc8d6..1547a952f1a4 100644 --- a/lxd/network_zones_records.go +++ b/lxd/network_zones_records.go @@ -9,6 +9,7 @@ import ( "github.com/canonical/lxd/lxd/auth" clusterRequest "github.com/canonical/lxd/lxd/cluster/request" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/lifecycle" "github.com/canonical/lxd/lxd/network/zone" "github.com/canonical/lxd/lxd/project" @@ -22,17 +23,17 @@ import ( var networkZoneRecordsCmd = APIEndpoint{ Path: "network-zones/{zone}/records", - Get: APIEndpointAction{Handler: networkZoneRecordsGet, AccessHandler: allowPermission(auth.ObjectTypeNetworkZone, auth.EntitlementCanView, "zone")}, - Post: APIEndpointAction{Handler: networkZoneRecordsPost, AccessHandler: allowPermission(auth.ObjectTypeNetworkZone, auth.EntitlementCanEdit, "zone")}, + Get: APIEndpointAction{Handler: networkZoneRecordsGet, AccessHandler: allowPermission(entity.TypeNetworkZone, auth.EntitlementCanView, "zone")}, + Post: APIEndpointAction{Handler: networkZoneRecordsPost, AccessHandler: allowPermission(entity.TypeNetworkZone, auth.EntitlementCanEdit, "zone")}, } var networkZoneRecordCmd = APIEndpoint{ Path: "network-zones/{zone}/records/{name}", - Delete: APIEndpointAction{Handler: networkZoneRecordDelete, AccessHandler: allowPermission(auth.ObjectTypeNetworkZone, auth.EntitlementCanEdit, "zone")}, - Get: APIEndpointAction{Handler: networkZoneRecordGet, AccessHandler: allowPermission(auth.ObjectTypeNetworkZone, auth.EntitlementCanView, "zone")}, - Put: APIEndpointAction{Handler: networkZoneRecordPut, AccessHandler: allowPermission(auth.ObjectTypeNetworkZone, auth.EntitlementCanEdit, "zone")}, - Patch: APIEndpointAction{Handler: networkZoneRecordPut, AccessHandler: allowPermission(auth.ObjectTypeNetworkZone, auth.EntitlementCanEdit, "zone")}, + Delete: APIEndpointAction{Handler: networkZoneRecordDelete, AccessHandler: allowPermission(entity.TypeNetworkZone, auth.EntitlementCanEdit, "zone")}, + Get: APIEndpointAction{Handler: networkZoneRecordGet, AccessHandler: allowPermission(entity.TypeNetworkZone, auth.EntitlementCanView, "zone")}, + Put: APIEndpointAction{Handler: networkZoneRecordPut, AccessHandler: allowPermission(entity.TypeNetworkZone, auth.EntitlementCanEdit, "zone")}, + Patch: APIEndpointAction{Handler: networkZoneRecordPut, AccessHandler: allowPermission(entity.TypeNetworkZone, auth.EntitlementCanEdit, "zone")}, } // API endpoints. diff --git a/lxd/networks.go b/lxd/networks.go index efe9ff74f851..93168bc9da2a 100644 --- a/lxd/networks.go +++ b/lxd/networks.go @@ -22,6 +22,7 @@ import ( "github.com/canonical/lxd/lxd/db" dbCluster "github.com/canonical/lxd/lxd/db/cluster" "github.com/canonical/lxd/lxd/db/warningtype" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/instance" "github.com/canonical/lxd/lxd/instance/instancetype" "github.com/canonical/lxd/lxd/lifecycle" @@ -36,7 +37,6 @@ import ( "github.com/canonical/lxd/lxd/warnings" "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" - "github.com/canonical/lxd/shared/entity" "github.com/canonical/lxd/shared/logger" "github.com/canonical/lxd/shared/revert" "github.com/canonical/lxd/shared/version" @@ -49,29 +49,29 @@ var networksCmd = APIEndpoint{ Path: "networks", Get: APIEndpointAction{Handler: networksGet, AccessHandler: allowAuthenticated}, - Post: APIEndpointAction{Handler: networksPost, AccessHandler: allowPermission(auth.ObjectTypeProject, auth.EntitlementCanCreateNetworks)}, + Post: APIEndpointAction{Handler: networksPost, AccessHandler: allowPermission(entity.TypeProject, auth.EntitlementCanCreateNetworks)}, } var networkCmd = APIEndpoint{ Path: "networks/{networkName}", - Delete: APIEndpointAction{Handler: networkDelete, AccessHandler: allowPermission(auth.ObjectTypeNetwork, auth.EntitlementCanEdit, "networkName")}, - Get: APIEndpointAction{Handler: networkGet, AccessHandler: allowPermission(auth.ObjectTypeNetwork, auth.EntitlementCanView, "networkName")}, - Patch: APIEndpointAction{Handler: networkPatch, AccessHandler: allowPermission(auth.ObjectTypeNetwork, auth.EntitlementCanEdit, "networkName")}, - Post: APIEndpointAction{Handler: networkPost, AccessHandler: allowPermission(auth.ObjectTypeNetwork, auth.EntitlementCanEdit, "networkName")}, - Put: APIEndpointAction{Handler: networkPut, AccessHandler: allowPermission(auth.ObjectTypeNetwork, auth.EntitlementCanEdit, "networkName")}, + Delete: APIEndpointAction{Handler: networkDelete, AccessHandler: allowPermission(entity.TypeNetwork, auth.EntitlementCanDelete, "networkName")}, + Get: APIEndpointAction{Handler: networkGet, AccessHandler: allowPermission(entity.TypeNetwork, auth.EntitlementCanView, "networkName")}, + Patch: APIEndpointAction{Handler: networkPatch, AccessHandler: allowPermission(entity.TypeNetwork, auth.EntitlementCanEdit, "networkName")}, + Post: APIEndpointAction{Handler: networkPost, AccessHandler: allowPermission(entity.TypeNetwork, auth.EntitlementCanEdit, "networkName")}, + Put: APIEndpointAction{Handler: networkPut, AccessHandler: allowPermission(entity.TypeNetwork, auth.EntitlementCanEdit, "networkName")}, } var networkLeasesCmd = APIEndpoint{ Path: "networks/{networkName}/leases", - Get: APIEndpointAction{Handler: networkLeasesGet, AccessHandler: allowPermission(auth.ObjectTypeNetwork, auth.EntitlementCanView, "networkName")}, + Get: APIEndpointAction{Handler: networkLeasesGet, AccessHandler: allowPermission(entity.TypeNetwork, auth.EntitlementCanView, "networkName")}, } var networkStateCmd = APIEndpoint{ Path: "networks/{networkName}/state", - Get: APIEndpointAction{Handler: networkStateGet, AccessHandler: allowPermission(auth.ObjectTypeNetwork, auth.EntitlementCanView, "networkName")}, + Get: APIEndpointAction{Handler: networkStateGet, AccessHandler: allowPermission(entity.TypeNetwork, auth.EntitlementCanView, "networkName")}, } // API endpoints @@ -204,7 +204,7 @@ func networksGet(d *Daemon, r *http.Request) response.Response { } } - userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, auth.ObjectTypeNetwork) + userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, entity.TypeNetwork) if err != nil { return response.InternalError(err) } @@ -212,7 +212,7 @@ func networksGet(d *Daemon, r *http.Request) response.Response { resultString := []string{} resultMap := []api.Network{} for _, networkName := range networkNames { - if !userHasPermission(auth.ObjectNetwork(projectName, networkName)) { + if !userHasPermission(entity.NetworkURL(projectName, networkName)) { continue } @@ -841,7 +841,7 @@ func doNetworkGet(s *state.State, r *http.Request, allNodes bool, projectName st apiNet.Description = n.Description() apiNet.Type = n.Type() - err = s.Authorizer.CheckPermission(r.Context(), r, auth.ObjectNetwork(projectName, networkName), auth.EntitlementCanEdit) + err = s.Authorizer.CheckPermission(r.Context(), r, entity.NetworkURL(projectName, networkName), auth.EntitlementCanEdit) if err == nil { // Only allow admins to see network config as sensitive info can be stored there. apiNet.Config = n.Config() diff --git a/lxd/operations.go b/lxd/operations.go index 515bd06cb04e..e97d8251f76d 100644 --- a/lxd/operations.go +++ b/lxd/operations.go @@ -16,6 +16,7 @@ import ( "github.com/canonical/lxd/lxd/db" dbCluster "github.com/canonical/lxd/lxd/db/cluster" "github.com/canonical/lxd/lxd/db/operationtype" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/lifecycle" "github.com/canonical/lxd/lxd/operations" "github.com/canonical/lxd/lxd/request" @@ -25,7 +26,6 @@ import ( "github.com/canonical/lxd/lxd/util" "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" - "github.com/canonical/lxd/shared/entity" "github.com/canonical/lxd/shared/logger" ) @@ -261,17 +261,7 @@ func operationDelete(d *Daemon, r *http.Request) response.Response { if objectType != "" { for _, v := range op.Resources() { for _, u := range v { - _, _, _, pathArgs, err := entity.ParseURL(u.URL) - if err != nil { - return response.InternalError(fmt.Errorf("Unable to parse operation resource URL: %w", err)) - } - - object, err := auth.NewObject(objectType, projectName, pathArgs...) - if err != nil { - return response.InternalError(fmt.Errorf("Unable to create authorization object for operation: %w", err)) - } - - err = s.Authorizer.CheckPermission(r.Context(), r, object, entitlement) + err = s.Authorizer.CheckPermission(r.Context(), r, &u, entitlement) if err != nil { return response.SmartError(err) } @@ -498,7 +488,7 @@ func operationsGet(d *Daemon, r *http.Request) response.Response { projectName = api.ProjectDefaultName } - userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanViewOperations, auth.ObjectTypeProject) + userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanViewOperations, entity.TypeProject) if err != nil { return response.InternalError(fmt.Errorf("Failed to get operation permission checker: %w", err)) } @@ -515,7 +505,7 @@ func operationsGet(d *Daemon, r *http.Request) response.Response { continue } - if !userHasPermission(auth.ObjectProject(v.Project())) { + if !userHasPermission(entity.ProjectURL(v.Project())) { continue } @@ -543,7 +533,7 @@ func operationsGet(d *Daemon, r *http.Request) response.Response { continue } - if !userHasPermission(auth.ObjectProject(v.Project())) { + if !userHasPermission(entity.ProjectURL(v.Project())) { continue } diff --git a/lxd/operations/operations.go b/lxd/operations/operations.go index 275337a48923..980d1ea097c0 100644 --- a/lxd/operations/operations.go +++ b/lxd/operations/operations.go @@ -11,6 +11,7 @@ import ( "github.com/canonical/lxd/lxd/auth" "github.com/canonical/lxd/lxd/db/operationtype" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/events" "github.com/canonical/lxd/lxd/request" "github.com/canonical/lxd/lxd/response" @@ -104,7 +105,7 @@ type Operation struct { readonly bool canceler *cancel.HTTPRequestCanceller description string - objectType auth.ObjectType + entityType entity.Type entitlement auth.Entitlement dbOpType operationtype.Type requestor *api.EventLifecycleRequestor @@ -138,7 +139,7 @@ func OperationCreate(s *state.State, projectName string, opClass OperationClass, op.projectName = projectName op.id = uuid.New().String() op.description = opType.Description() - op.objectType, op.entitlement = opType.Permission() + op.entityType, op.entitlement = opType.Permission() op.dbOpType = opType op.class = opClass op.createdAt = time.Now() @@ -661,9 +662,9 @@ func (op *Operation) SetCanceler(canceler *cancel.HTTPRequestCanceller) { op.canceler = canceler } -// Permission returns the operations auth.ObjectType and auth.Entitlement. -func (op *Operation) Permission() (auth.ObjectType, auth.Entitlement) { - return op.objectType, op.entitlement +// Permission returns the operations entity.Type and auth.Entitlement. +func (op *Operation) Permission() (entity.Type, auth.Entitlement) { + return op.entityType, op.entitlement } // Project returns the operation project. diff --git a/lxd/profiles.go b/lxd/profiles.go index 98a1ea0856c0..4cfafde2755e 100644 --- a/lxd/profiles.go +++ b/lxd/profiles.go @@ -19,6 +19,7 @@ import ( "github.com/canonical/lxd/lxd/db" dbCluster "github.com/canonical/lxd/lxd/db/cluster" deviceConfig "github.com/canonical/lxd/lxd/device/config" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/instance" "github.com/canonical/lxd/lxd/instance/instancetype" "github.com/canonical/lxd/lxd/lifecycle" @@ -36,17 +37,17 @@ var profilesCmd = APIEndpoint{ Path: "profiles", Get: APIEndpointAction{Handler: profilesGet, AccessHandler: allowAuthenticated}, - Post: APIEndpointAction{Handler: profilesPost, AccessHandler: allowPermission(auth.ObjectTypeProject, auth.EntitlementCanCreateProfiles)}, + Post: APIEndpointAction{Handler: profilesPost, AccessHandler: allowPermission(entity.TypeProject, auth.EntitlementCanCreateProfiles)}, } var profileCmd = APIEndpoint{ Path: "profiles/{name}", - Delete: APIEndpointAction{Handler: profileDelete, AccessHandler: allowPermission(auth.ObjectTypeProfile, auth.EntitlementCanEdit, "name")}, - Get: APIEndpointAction{Handler: profileGet, AccessHandler: allowPermission(auth.ObjectTypeProfile, auth.EntitlementCanView, "name")}, - Patch: APIEndpointAction{Handler: profilePatch, AccessHandler: allowPermission(auth.ObjectTypeProfile, auth.EntitlementCanEdit, "name")}, - Post: APIEndpointAction{Handler: profilePost, AccessHandler: allowPermission(auth.ObjectTypeProfile, auth.EntitlementCanEdit, "name")}, - Put: APIEndpointAction{Handler: profilePut, AccessHandler: allowPermission(auth.ObjectTypeProfile, auth.EntitlementCanEdit, "name")}, + Delete: APIEndpointAction{Handler: profileDelete, AccessHandler: allowPermission(entity.TypeProfile, auth.EntitlementCanDelete, "name")}, + Get: APIEndpointAction{Handler: profileGet, AccessHandler: allowPermission(entity.TypeProfile, auth.EntitlementCanView, "name")}, + Patch: APIEndpointAction{Handler: profilePatch, AccessHandler: allowPermission(entity.TypeProfile, auth.EntitlementCanEdit, "name")}, + Post: APIEndpointAction{Handler: profilePost, AccessHandler: allowPermission(entity.TypeProfile, auth.EntitlementCanEdit, "name")}, + Put: APIEndpointAction{Handler: profilePut, AccessHandler: allowPermission(entity.TypeProfile, auth.EntitlementCanEdit, "name")}, } // swagger:operation GET /1.0/profiles profiles profiles_get @@ -151,7 +152,7 @@ func profilesGet(d *Daemon, r *http.Request) response.Response { recursion := util.IsRecursionRequest(r) - userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, auth.ObjectTypeProfile) + userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, entity.TypeProfile) if err != nil { return response.InternalError(err) } @@ -169,7 +170,7 @@ func profilesGet(d *Daemon, r *http.Request) response.Response { apiProfiles := make([]*api.Profile, 0, len(profiles)) for _, profile := range profiles { - if !userHasPermission(auth.ObjectProfile(p.Name, profile.Name)) { + if !userHasPermission(entity.ProfileURL(p.Name, profile.Name)) { continue } diff --git a/lxd/project/permissions.go b/lxd/project/permissions.go index 457dbbfa0a87..d372d404cd94 100644 --- a/lxd/project/permissions.go +++ b/lxd/project/permissions.go @@ -13,11 +13,11 @@ import ( "github.com/canonical/lxd/lxd/db" "github.com/canonical/lxd/lxd/db/cluster" deviceconfig "github.com/canonical/lxd/lxd/device/config" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/idmap" "github.com/canonical/lxd/lxd/instance/instancetype" "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" - "github.com/canonical/lxd/shared/entity" "github.com/canonical/lxd/shared/logger" "github.com/canonical/lxd/shared/units" "github.com/canonical/lxd/shared/validate" @@ -1430,37 +1430,10 @@ func FilterUsedBy(authorizer auth.Authorizer, r *http.Request, entries []string) for _, entry := range entries { u, err := url.Parse(entry) if err != nil { - logger.Error("Invalid URL found when filtering used-by entries", logger.Ctx{"entry": entry, "error": err}) - continue - } - - entityType, projectName, _, pathArgs, err := entity.ParseURL(*u) - if err != nil { - logger.Error("Encountered unrecognized URL found when filtering used-by entries", logger.Ctx{"entry": entry, "error": err}) - continue - } - - var object auth.Object - switch entityType { - case entity.TypeImage: - object = auth.ObjectImage(projectName, pathArgs[0]) - case entity.TypeInstance: - object = auth.ObjectInstance(projectName, pathArgs[0]) - case entity.TypeNetwork: - object = auth.ObjectNetwork(projectName, pathArgs[0]) - case entity.TypeProfile: - object = auth.ObjectProfile(projectName, pathArgs[0]) - case entity.TypeStoragePool: - object = auth.ObjectStoragePool(pathArgs[0]) - case entity.TypeStorageVolume: - object = auth.ObjectStorageVolume(projectName, pathArgs[0], pathArgs[1], pathArgs[2]) - case entity.TypeStorageBucket: - object = auth.ObjectStorageBucket(projectName, pathArgs[0], pathArgs[1]) - default: - continue + logger.Warn("Failed to parse project used-by entity URL", logger.Ctx{"url": entry, "error": err}) } - err = authorizer.CheckPermission(r.Context(), r, object, auth.EntitlementCanView) + err = authorizer.CheckPermission(r.Context(), r, &api.URL{URL: *u}, auth.EntitlementCanView) if err != nil { continue } @@ -1493,7 +1466,7 @@ func projectHasRestriction(project *api.Project, restrictionKey string, blockVal func CheckClusterTargetRestriction(authorizer auth.Authorizer, r *http.Request, project *api.Project, targetFlag string) error { if projectHasRestriction(project, "restricted.cluster.target", "block") && targetFlag != "" { // Allow server administrators to move instances around even when restricted (node evacuation, ...) - err := authorizer.CheckPermission(r.Context(), r, auth.ObjectServer(), auth.EntitlementCanOverrideClusterTargetRestriction) + err := authorizer.CheckPermission(r.Context(), r, entity.ServerURL(), auth.EntitlementCanOverrideClusterTargetRestriction) if err != nil && api.StatusErrorCheck(err, http.StatusForbidden) { return api.StatusErrorf(http.StatusForbidden, "This project doesn't allow cluster member targeting") } else if err != nil { diff --git a/lxd/resources.go b/lxd/resources.go index 197b0c910eb8..7a2d2379cc39 100644 --- a/lxd/resources.go +++ b/lxd/resources.go @@ -7,6 +7,7 @@ import ( "github.com/gorilla/mux" "github.com/canonical/lxd/lxd/auth" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/resources" "github.com/canonical/lxd/lxd/response" storagePools "github.com/canonical/lxd/lxd/storage" @@ -16,13 +17,13 @@ import ( var api10ResourcesCmd = APIEndpoint{ Path: "resources", - Get: APIEndpointAction{Handler: api10ResourcesGet, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanViewResources)}, + Get: APIEndpointAction{Handler: api10ResourcesGet, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanViewResources)}, } var storagePoolResourcesCmd = APIEndpoint{ Path: "storage-pools/{name}/resources", - Get: APIEndpointAction{Handler: storagePoolResourcesGet, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanViewResources)}, + Get: APIEndpointAction{Handler: storagePoolResourcesGet, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanViewResources)}, } // swagger:operation GET /1.0/resources server resources_get diff --git a/lxd/storage.go b/lxd/storage.go index 4e964315ab1d..d2a4e376c82f 100644 --- a/lxd/storage.go +++ b/lxd/storage.go @@ -7,6 +7,7 @@ import ( "time" "github.com/canonical/lxd/lxd/db/warningtype" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/instance" "github.com/canonical/lxd/lxd/instance/instancetype" "github.com/canonical/lxd/lxd/response" @@ -16,7 +17,6 @@ import ( "github.com/canonical/lxd/lxd/warnings" "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" - "github.com/canonical/lxd/shared/entity" "github.com/canonical/lxd/shared/logger" "github.com/canonical/lxd/shared/version" ) diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go index 9c626e8f790f..39c183ab7428 100644 --- a/lxd/storage/backend_lxd.go +++ b/lxd/storage/backend_lxd.go @@ -28,6 +28,7 @@ import ( "github.com/canonical/lxd/lxd/cluster/request" "github.com/canonical/lxd/lxd/db" "github.com/canonical/lxd/lxd/db/cluster" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/instance" "github.com/canonical/lxd/lxd/instance/instancetype" "github.com/canonical/lxd/lxd/instancewriter" @@ -46,7 +47,6 @@ import ( "github.com/canonical/lxd/lxd/util" "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" - "github.com/canonical/lxd/shared/entity" "github.com/canonical/lxd/shared/ioprogress" "github.com/canonical/lxd/shared/logger" "github.com/canonical/lxd/shared/revert" diff --git a/lxd/storage_buckets.go b/lxd/storage_buckets.go index cc83519f7ab3..11d9175cec9f 100644 --- a/lxd/storage_buckets.go +++ b/lxd/storage_buckets.go @@ -12,6 +12,7 @@ import ( "github.com/canonical/lxd/lxd/auth" "github.com/canonical/lxd/lxd/db" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/lifecycle" "github.com/canonical/lxd/lxd/project" "github.com/canonical/lxd/lxd/request" @@ -28,31 +29,31 @@ var storagePoolBucketsCmd = APIEndpoint{ Path: "storage-pools/{poolName}/buckets", Get: APIEndpointAction{Handler: storagePoolBucketsGet, AccessHandler: allowAuthenticated}, - Post: APIEndpointAction{Handler: storagePoolBucketsPost, AccessHandler: allowPermission(auth.ObjectTypeProject, auth.EntitlementCanCreateStorageBuckets)}, + Post: APIEndpointAction{Handler: storagePoolBucketsPost, AccessHandler: allowPermission(entity.TypeProject, auth.EntitlementCanCreateStorageBuckets)}, } var storagePoolBucketCmd = APIEndpoint{ Path: "storage-pools/{poolName}/buckets/{bucketName}", - Delete: APIEndpointAction{Handler: storagePoolBucketDelete, AccessHandler: allowPermission(auth.ObjectTypeStorageBucket, auth.EntitlementCanEdit, "poolName", "bucketName")}, - Get: APIEndpointAction{Handler: storagePoolBucketGet, AccessHandler: allowPermission(auth.ObjectTypeStorageBucket, auth.EntitlementCanView, "poolName", "bucketName")}, - Patch: APIEndpointAction{Handler: storagePoolBucketPut, AccessHandler: allowPermission(auth.ObjectTypeStorageBucket, auth.EntitlementCanEdit, "poolName", "bucketName")}, - Put: APIEndpointAction{Handler: storagePoolBucketPut, AccessHandler: allowPermission(auth.ObjectTypeStorageBucket, auth.EntitlementCanEdit, "poolName", "bucketName")}, + Delete: APIEndpointAction{Handler: storagePoolBucketDelete, AccessHandler: allowPermission(entity.TypeStorageBucket, auth.EntitlementCanDelete, "poolName", "bucketName")}, + Get: APIEndpointAction{Handler: storagePoolBucketGet, AccessHandler: allowPermission(entity.TypeStorageBucket, auth.EntitlementCanView, "poolName", "bucketName")}, + Patch: APIEndpointAction{Handler: storagePoolBucketPut, AccessHandler: allowPermission(entity.TypeStorageBucket, auth.EntitlementCanEdit, "poolName", "bucketName")}, + Put: APIEndpointAction{Handler: storagePoolBucketPut, AccessHandler: allowPermission(entity.TypeStorageBucket, auth.EntitlementCanEdit, "poolName", "bucketName")}, } var storagePoolBucketKeysCmd = APIEndpoint{ Path: "storage-pools/{poolName}/buckets/{bucketName}/keys", - Get: APIEndpointAction{Handler: storagePoolBucketKeysGet, AccessHandler: allowPermission(auth.ObjectTypeStorageBucket, auth.EntitlementCanView, "poolName", "bucketName")}, - Post: APIEndpointAction{Handler: storagePoolBucketKeysPost, AccessHandler: allowPermission(auth.ObjectTypeStorageBucket, auth.EntitlementCanEdit, "poolName", "bucketName")}, + Get: APIEndpointAction{Handler: storagePoolBucketKeysGet, AccessHandler: allowPermission(entity.TypeStorageBucket, auth.EntitlementCanView, "poolName", "bucketName")}, + Post: APIEndpointAction{Handler: storagePoolBucketKeysPost, AccessHandler: allowPermission(entity.TypeStorageBucket, auth.EntitlementCanEdit, "poolName", "bucketName")}, } var storagePoolBucketKeyCmd = APIEndpoint{ Path: "storage-pools/{poolName}/buckets/{bucketName}/keys/{keyName}", - Delete: APIEndpointAction{Handler: storagePoolBucketKeyDelete, AccessHandler: allowPermission(auth.ObjectTypeStorageBucket, auth.EntitlementCanEdit, "poolName", "bucketName")}, - Get: APIEndpointAction{Handler: storagePoolBucketKeyGet, AccessHandler: allowPermission(auth.ObjectTypeStorageBucket, auth.EntitlementCanView, "poolName", "bucketName")}, - Put: APIEndpointAction{Handler: storagePoolBucketKeyPut, AccessHandler: allowPermission(auth.ObjectTypeStorageBucket, auth.EntitlementCanEdit, "poolName", "bucketName")}, + Delete: APIEndpointAction{Handler: storagePoolBucketKeyDelete, AccessHandler: allowPermission(entity.TypeStorageBucket, auth.EntitlementCanEdit, "poolName", "bucketName")}, + Get: APIEndpointAction{Handler: storagePoolBucketKeyGet, AccessHandler: allowPermission(entity.TypeStorageBucket, auth.EntitlementCanView, "poolName", "bucketName")}, + Put: APIEndpointAction{Handler: storagePoolBucketKeyPut, AccessHandler: allowPermission(entity.TypeStorageBucket, auth.EntitlementCanEdit, "poolName", "bucketName")}, } // API endpoints @@ -195,7 +196,7 @@ func storagePoolBucketsGet(d *Daemon, r *http.Request) response.Response { return response.SmartError(err) } - userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, auth.ObjectTypeStorageBucket) + userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, entity.TypeStorageBucket) if err != nil { return response.SmartError(err) } @@ -203,7 +204,7 @@ func storagePoolBucketsGet(d *Daemon, r *http.Request) response.Response { var filteredDBBuckets []*db.StorageBucket for _, bucket := range dbBuckets { - if !userHasPermission(auth.ObjectStorageBucket(requestProjectName, poolName, bucket.Name)) { + if !userHasPermission(entity.StorageBucketURL(requestProjectName, "", poolName, bucket.Name)) { continue } diff --git a/lxd/storage_pools.go b/lxd/storage_pools.go index 4768d3b39a47..59a358dc8e71 100644 --- a/lxd/storage_pools.go +++ b/lxd/storage_pools.go @@ -16,6 +16,7 @@ import ( "github.com/canonical/lxd/lxd/cluster" clusterRequest "github.com/canonical/lxd/lxd/cluster/request" "github.com/canonical/lxd/lxd/db" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/lifecycle" "github.com/canonical/lxd/lxd/project" "github.com/canonical/lxd/lxd/request" @@ -36,16 +37,16 @@ var storagePoolsCmd = APIEndpoint{ Path: "storage-pools", Get: APIEndpointAction{Handler: storagePoolsGet, AccessHandler: allowAuthenticated}, - Post: APIEndpointAction{Handler: storagePoolsPost, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanCreateStoragePools)}, + Post: APIEndpointAction{Handler: storagePoolsPost, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanCreateStoragePools)}, } var storagePoolCmd = APIEndpoint{ Path: "storage-pools/{poolName}", - Delete: APIEndpointAction{Handler: storagePoolDelete, AccessHandler: allowPermission(auth.ObjectTypeStoragePool, auth.EntitlementCanEdit, "poolName")}, - Get: APIEndpointAction{Handler: storagePoolGet, AccessHandler: allowPermission(auth.ObjectTypeStoragePool, auth.EntitlementCanView, "poolName")}, - Patch: APIEndpointAction{Handler: storagePoolPatch, AccessHandler: allowPermission(auth.ObjectTypeStoragePool, auth.EntitlementCanEdit, "poolName")}, - Put: APIEndpointAction{Handler: storagePoolPut, AccessHandler: allowPermission(auth.ObjectTypeStoragePool, auth.EntitlementCanEdit, "poolName")}, + Delete: APIEndpointAction{Handler: storagePoolDelete, AccessHandler: allowPermission(entity.TypeStoragePool, auth.EntitlementCanDelete, "poolName")}, + Get: APIEndpointAction{Handler: storagePoolGet, AccessHandler: allowPermission(entity.TypeStoragePool, auth.EntitlementCanView, "poolName")}, + Patch: APIEndpointAction{Handler: storagePoolPatch, AccessHandler: allowPermission(entity.TypeStoragePool, auth.EntitlementCanEdit, "poolName")}, + Put: APIEndpointAction{Handler: storagePoolPut, AccessHandler: allowPermission(entity.TypeStoragePool, auth.EntitlementCanEdit, "poolName")}, } // swagger:operation GET /1.0/storage-pools storage storage_pools_get @@ -150,7 +151,7 @@ func storagePoolsGet(d *Daemon, r *http.Request) response.Response { return response.SmartError(err) } - hasEditPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanEdit, auth.ObjectTypeStoragePool) + hasEditPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanEdit, entity.TypeStoragePool) if err != nil { return response.InternalError(err) } @@ -175,7 +176,7 @@ func storagePoolsGet(d *Daemon, r *http.Request) response.Response { poolAPI := pool.ToAPI() poolAPI.UsedBy = project.FilterUsedBy(s.Authorizer, r, poolUsedBy) - if !hasEditPermission(auth.ObjectStoragePool(poolName)) { + if !hasEditPermission(entity.StoragePoolURL(poolName)) { // Don't allow non-admins to see pool config as sensitive info can be stored there. poolAPI.Config = nil } @@ -611,7 +612,7 @@ func storagePoolGet(d *Daemon, r *http.Request) response.Response { poolAPI := pool.ToAPI() poolAPI.UsedBy = project.FilterUsedBy(s.Authorizer, r, poolUsedBy) - err = s.Authorizer.CheckPermission(r.Context(), r, auth.ObjectStoragePool(poolName), auth.EntitlementCanEdit) + err = s.Authorizer.CheckPermission(r.Context(), r, entity.StoragePoolURL(poolName), auth.EntitlementCanEdit) if err != nil && api.StatusErrorCheck(err, http.StatusForbidden) { // Don't allow non-admins to see pool config as sensitive info can be stored there. poolAPI.Config = nil diff --git a/lxd/storage_volumes.go b/lxd/storage_volumes.go index 5407fb7c9e98..282e647a994d 100644 --- a/lxd/storage_volumes.go +++ b/lxd/storage_volumes.go @@ -27,6 +27,7 @@ import ( "github.com/canonical/lxd/lxd/db/cluster" dbCluster "github.com/canonical/lxd/lxd/db/cluster" "github.com/canonical/lxd/lxd/db/operationtype" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/instance" "github.com/canonical/lxd/lxd/instance/instancetype" "github.com/canonical/lxd/lxd/operations" @@ -48,24 +49,24 @@ var storagePoolVolumesCmd = APIEndpoint{ Path: "storage-pools/{poolName}/volumes", Get: APIEndpointAction{Handler: storagePoolVolumesGet, AccessHandler: allowAuthenticated}, - Post: APIEndpointAction{Handler: storagePoolVolumesPost, AccessHandler: allowPermission(auth.ObjectTypeProject, auth.EntitlementCanCreateStorageVolumes)}, + Post: APIEndpointAction{Handler: storagePoolVolumesPost, AccessHandler: allowPermission(entity.TypeProject, auth.EntitlementCanCreateStorageVolumes)}, } var storagePoolVolumesTypeCmd = APIEndpoint{ Path: "storage-pools/{poolName}/volumes/{type}", Get: APIEndpointAction{Handler: storagePoolVolumesGet, AccessHandler: allowAuthenticated}, - Post: APIEndpointAction{Handler: storagePoolVolumesPost, AccessHandler: allowPermission(auth.ObjectTypeProject, auth.EntitlementCanCreateStorageVolumes)}, + Post: APIEndpointAction{Handler: storagePoolVolumesPost, AccessHandler: allowPermission(entity.TypeProject, auth.EntitlementCanCreateStorageVolumes)}, } var storagePoolVolumeTypeCmd = APIEndpoint{ Path: "storage-pools/{poolName}/volumes/{type}/{volumeName}", - Delete: APIEndpointAction{Handler: storagePoolVolumeDelete, AccessHandler: allowPermission(auth.ObjectTypeStorageVolume, auth.EntitlementCanEdit, "poolName", "type", "volumeName")}, - Get: APIEndpointAction{Handler: storagePoolVolumeGet, AccessHandler: allowPermission(auth.ObjectTypeStorageVolume, auth.EntitlementCanView, "poolName", "type", "volumeName")}, - Patch: APIEndpointAction{Handler: storagePoolVolumePatch, AccessHandler: allowPermission(auth.ObjectTypeStorageVolume, auth.EntitlementCanEdit, "poolName", "type", "volumeName")}, - Post: APIEndpointAction{Handler: storagePoolVolumePost, AccessHandler: allowPermission(auth.ObjectTypeStorageVolume, auth.EntitlementCanEdit, "poolName", "type", "volumeName")}, - Put: APIEndpointAction{Handler: storagePoolVolumePut, AccessHandler: allowPermission(auth.ObjectTypeStorageVolume, auth.EntitlementCanEdit, "poolName", "type", "volumeName")}, + Delete: APIEndpointAction{Handler: storagePoolVolumeDelete, AccessHandler: allowPermission(entity.TypeStorageVolume, auth.EntitlementCanDelete, "poolName", "type", "volumeName")}, + Get: APIEndpointAction{Handler: storagePoolVolumeGet, AccessHandler: allowPermission(entity.TypeStorageVolume, auth.EntitlementCanView, "poolName", "type", "volumeName")}, + Patch: APIEndpointAction{Handler: storagePoolVolumePatch, AccessHandler: allowPermission(entity.TypeStorageVolume, auth.EntitlementCanEdit, "poolName", "type", "volumeName")}, + Post: APIEndpointAction{Handler: storagePoolVolumePost, AccessHandler: allowPermission(entity.TypeStorageVolume, auth.EntitlementCanEdit, "poolName", "type", "volumeName")}, + Put: APIEndpointAction{Handler: storagePoolVolumePut, AccessHandler: allowPermission(entity.TypeStorageVolume, auth.EntitlementCanEdit, "poolName", "type", "volumeName")}, } // swagger:operation GET /1.0/storage-pools/{poolName}/volumes storage storage_pool_volumes_get @@ -448,7 +449,7 @@ func storagePoolVolumesGet(d *Daemon, r *http.Request) response.Response { return volA.Name < volB.Name }) - userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, auth.ObjectTypeStorageVolume) + userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, entity.TypeStorageVolume) if err != nil { return response.SmartError(err) } @@ -459,7 +460,7 @@ func storagePoolVolumesGet(d *Daemon, r *http.Request) response.Response { vol := &dbVol.StorageVolume volumeName, _, _ := api.GetParentAndSnapshotName(vol.Name) - if !userHasPermission(auth.ObjectStorageVolume(vol.Project, poolName, dbVol.Type, volumeName)) { + if !userHasPermission(entity.StorageVolumeURL(vol.Project, "", poolName, dbVol.Type, volumeName)) { continue } @@ -483,7 +484,7 @@ func storagePoolVolumesGet(d *Daemon, r *http.Request) response.Response { for _, dbVol := range dbVolumes { volumeName, _, _ := api.GetParentAndSnapshotName(dbVol.Name) - if !userHasPermission(auth.ObjectStorageVolume(dbVol.Project, poolName, dbVol.Type, volumeName)) { + if !userHasPermission(entity.StorageVolumeURL(dbVol.Project, "", poolName, dbVol.Type, volumeName)) { continue } @@ -1069,7 +1070,7 @@ func storagePoolVolumePost(d *Daemon, r *http.Request) response.Response { } // Check if user has access to effective storage target project - err := s.Authorizer.CheckPermission(r.Context(), r, auth.ObjectProject(targetProjectName), auth.EntitlementCanCreateStorageVolumes) + err := s.Authorizer.CheckPermission(r.Context(), r, entity.ProjectURL(targetProjectName), auth.EntitlementCanCreateStorageVolumes) if err != nil { return response.SmartError(err) } diff --git a/lxd/storage_volumes_backup.go b/lxd/storage_volumes_backup.go index 0ed2b0d5a248..d62c10ba8132 100644 --- a/lxd/storage_volumes_backup.go +++ b/lxd/storage_volumes_backup.go @@ -15,6 +15,7 @@ import ( "github.com/canonical/lxd/lxd/backup" "github.com/canonical/lxd/lxd/db" "github.com/canonical/lxd/lxd/db/operationtype" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/lifecycle" "github.com/canonical/lxd/lxd/operations" "github.com/canonical/lxd/lxd/project" @@ -31,22 +32,22 @@ import ( var storagePoolVolumeTypeCustomBackupsCmd = APIEndpoint{ Path: "storage-pools/{poolName}/volumes/{type}/{volumeName}/backups", - Get: APIEndpointAction{Handler: storagePoolVolumeTypeCustomBackupsGet, AccessHandler: allowPermission(auth.ObjectTypeStorageVolume, auth.EntitlementCanView, "poolName", "type", "volumeName")}, - Post: APIEndpointAction{Handler: storagePoolVolumeTypeCustomBackupsPost, AccessHandler: allowPermission(auth.ObjectTypeStorageVolume, auth.EntitlementCanManageBackups, "poolName", "type", "volumeName")}, + Get: APIEndpointAction{Handler: storagePoolVolumeTypeCustomBackupsGet, AccessHandler: allowPermission(entity.TypeStorageVolume, auth.EntitlementCanView, "poolName", "type", "volumeName")}, + Post: APIEndpointAction{Handler: storagePoolVolumeTypeCustomBackupsPost, AccessHandler: allowPermission(entity.TypeStorageVolume, auth.EntitlementCanManageBackups, "poolName", "type", "volumeName")}, } var storagePoolVolumeTypeCustomBackupCmd = APIEndpoint{ Path: "storage-pools/{poolName}/volumes/{type}/{volumeName}/backups/{backupName}", - Get: APIEndpointAction{Handler: storagePoolVolumeTypeCustomBackupGet, AccessHandler: allowPermission(auth.ObjectTypeStorageVolume, auth.EntitlementCanView, "poolName", "type", "volumeName")}, - Post: APIEndpointAction{Handler: storagePoolVolumeTypeCustomBackupPost, AccessHandler: allowPermission(auth.ObjectTypeStorageVolume, auth.EntitlementCanManageBackups, "poolName", "type", "volumeName")}, - Delete: APIEndpointAction{Handler: storagePoolVolumeTypeCustomBackupDelete, AccessHandler: allowPermission(auth.ObjectTypeStorageVolume, auth.EntitlementCanManageBackups, "poolName", "type", "volumeName")}, + Get: APIEndpointAction{Handler: storagePoolVolumeTypeCustomBackupGet, AccessHandler: allowPermission(entity.TypeStorageVolume, auth.EntitlementCanView, "poolName", "type", "volumeName")}, + Post: APIEndpointAction{Handler: storagePoolVolumeTypeCustomBackupPost, AccessHandler: allowPermission(entity.TypeStorageVolume, auth.EntitlementCanManageBackups, "poolName", "type", "volumeName")}, + Delete: APIEndpointAction{Handler: storagePoolVolumeTypeCustomBackupDelete, AccessHandler: allowPermission(entity.TypeStorageVolume, auth.EntitlementCanManageBackups, "poolName", "type", "volumeName")}, } var storagePoolVolumeTypeCustomBackupExportCmd = APIEndpoint{ Path: "storage-pools/{poolName}/volumes/{type}/{volumeName}/backups/{backupName}/export", - Get: APIEndpointAction{Handler: storagePoolVolumeTypeCustomBackupExportGet, AccessHandler: allowPermission(auth.ObjectTypeStorageVolume, auth.EntitlementCanView, "poolName", "type", "volumeName")}, + Get: APIEndpointAction{Handler: storagePoolVolumeTypeCustomBackupExportGet, AccessHandler: allowPermission(entity.TypeStorageVolume, auth.EntitlementCanView, "poolName", "type", "volumeName")}, } // swagger:operation GET /1.0/storage-pools/{poolName}/volumes/{type}/{volumeName}/backups storage storage_pool_volumes_type_backups_get diff --git a/lxd/storage_volumes_snapshot.go b/lxd/storage_volumes_snapshot.go index 47ecf46940b1..2de3b7d1f4e5 100644 --- a/lxd/storage_volumes_snapshot.go +++ b/lxd/storage_volumes_snapshot.go @@ -18,6 +18,7 @@ import ( "github.com/canonical/lxd/lxd/db" dbCluster "github.com/canonical/lxd/lxd/db/cluster" "github.com/canonical/lxd/lxd/db/operationtype" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/instance" "github.com/canonical/lxd/lxd/operations" "github.com/canonical/lxd/lxd/project" @@ -36,18 +37,18 @@ import ( var storagePoolVolumeSnapshotsTypeCmd = APIEndpoint{ Path: "storage-pools/{poolName}/volumes/{type}/{volumeName}/snapshots", - Get: APIEndpointAction{Handler: storagePoolVolumeSnapshotsTypeGet, AccessHandler: allowPermission(auth.ObjectTypeStorageVolume, auth.EntitlementCanView, "poolName", "type", "volumeName")}, - Post: APIEndpointAction{Handler: storagePoolVolumeSnapshotsTypePost, AccessHandler: allowPermission(auth.ObjectTypeStorageVolume, auth.EntitlementCanManageSnapshots, "poolName", "type", "volumeName")}, + Get: APIEndpointAction{Handler: storagePoolVolumeSnapshotsTypeGet, AccessHandler: allowPermission(entity.TypeStorageVolume, auth.EntitlementCanView, "poolName", "type", "volumeName")}, + Post: APIEndpointAction{Handler: storagePoolVolumeSnapshotsTypePost, AccessHandler: allowPermission(entity.TypeStorageVolume, auth.EntitlementCanManageSnapshots, "poolName", "type", "volumeName")}, } var storagePoolVolumeSnapshotTypeCmd = APIEndpoint{ Path: "storage-pools/{poolName}/volumes/{type}/{volumeName}/snapshots/{snapshotName}", - Delete: APIEndpointAction{Handler: storagePoolVolumeSnapshotTypeDelete, AccessHandler: allowPermission(auth.ObjectTypeStorageVolume, auth.EntitlementCanManageSnapshots, "poolName", "type", "volumeName")}, - Get: APIEndpointAction{Handler: storagePoolVolumeSnapshotTypeGet, AccessHandler: allowPermission(auth.ObjectTypeStorageVolume, auth.EntitlementCanView, "poolName", "type", "volumeName")}, - Post: APIEndpointAction{Handler: storagePoolVolumeSnapshotTypePost, AccessHandler: allowPermission(auth.ObjectTypeStorageVolume, auth.EntitlementCanManageSnapshots, "poolName", "type", "volumeName")}, - Patch: APIEndpointAction{Handler: storagePoolVolumeSnapshotTypePatch, AccessHandler: allowPermission(auth.ObjectTypeStorageVolume, auth.EntitlementCanManageSnapshots, "poolName", "type", "volumeName")}, - Put: APIEndpointAction{Handler: storagePoolVolumeSnapshotTypePut, AccessHandler: allowPermission(auth.ObjectTypeStorageVolume, auth.EntitlementCanManageSnapshots, "poolName", "type", "volumeName")}, + Delete: APIEndpointAction{Handler: storagePoolVolumeSnapshotTypeDelete, AccessHandler: allowPermission(entity.TypeStorageVolume, auth.EntitlementCanManageSnapshots, "poolName", "type", "volumeName")}, + Get: APIEndpointAction{Handler: storagePoolVolumeSnapshotTypeGet, AccessHandler: allowPermission(entity.TypeStorageVolume, auth.EntitlementCanView, "poolName", "type", "volumeName")}, + Post: APIEndpointAction{Handler: storagePoolVolumeSnapshotTypePost, AccessHandler: allowPermission(entity.TypeStorageVolume, auth.EntitlementCanManageSnapshots, "poolName", "type", "volumeName")}, + Patch: APIEndpointAction{Handler: storagePoolVolumeSnapshotTypePatch, AccessHandler: allowPermission(entity.TypeStorageVolume, auth.EntitlementCanManageSnapshots, "poolName", "type", "volumeName")}, + Put: APIEndpointAction{Handler: storagePoolVolumeSnapshotTypePut, AccessHandler: allowPermission(entity.TypeStorageVolume, auth.EntitlementCanManageSnapshots, "poolName", "type", "volumeName")}, } // swagger:operation POST /1.0/storage-pools/{poolName}/volumes/{type}/{volumeName}/snapshots storage storage_pool_volumes_type_snapshots_post diff --git a/lxd/storage_volumes_state.go b/lxd/storage_volumes_state.go index f466187ae656..9ac50067e920 100644 --- a/lxd/storage_volumes_state.go +++ b/lxd/storage_volumes_state.go @@ -9,6 +9,7 @@ import ( "github.com/canonical/lxd/lxd/auth" "github.com/canonical/lxd/lxd/db" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/instance" "github.com/canonical/lxd/lxd/instance/instancetype" "github.com/canonical/lxd/lxd/project" @@ -22,7 +23,7 @@ import ( var storagePoolVolumeTypeStateCmd = APIEndpoint{ Path: "storage-pools/{poolName}/volumes/{type}/{volumeName}/state", - Get: APIEndpointAction{Handler: storagePoolVolumeTypeStateGet, AccessHandler: allowPermission(auth.ObjectTypeStorageVolume, auth.EntitlementCanView, "poolName", "type", "volumeName")}, + Get: APIEndpointAction{Handler: storagePoolVolumeTypeStateGet, AccessHandler: allowPermission(entity.TypeStorageVolume, auth.EntitlementCanView, "poolName", "type", "volumeName")}, } // swagger:operation GET /1.0/storage-pools/{poolName}/volumes/{type}/{volumeName}/state storage storage_pool_volume_type_state_get diff --git a/lxd/warnings.go b/lxd/warnings.go index f4b00127fe31..72e545fa37e6 100644 --- a/lxd/warnings.go +++ b/lxd/warnings.go @@ -17,6 +17,7 @@ import ( "github.com/canonical/lxd/lxd/db/cluster" "github.com/canonical/lxd/lxd/db/operationtype" "github.com/canonical/lxd/lxd/db/warningtype" + "github.com/canonical/lxd/lxd/entity" "github.com/canonical/lxd/lxd/lifecycle" "github.com/canonical/lxd/lxd/operations" "github.com/canonical/lxd/lxd/request" @@ -24,7 +25,6 @@ import ( "github.com/canonical/lxd/lxd/state" "github.com/canonical/lxd/lxd/task" "github.com/canonical/lxd/shared/api" - "github.com/canonical/lxd/shared/entity" "github.com/canonical/lxd/shared/filter" "github.com/canonical/lxd/shared/logger" "github.com/canonical/lxd/shared/version" @@ -33,16 +33,16 @@ import ( var warningsCmd = APIEndpoint{ Path: "warnings", - Get: APIEndpointAction{Handler: warningsGet, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Get: APIEndpointAction{Handler: warningsGet, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } var warningCmd = APIEndpoint{ Path: "warnings/{id}", - Get: APIEndpointAction{Handler: warningGet, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, - Patch: APIEndpointAction{Handler: warningPatch, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, - Put: APIEndpointAction{Handler: warningPut, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, - Delete: APIEndpointAction{Handler: warningDelete, AccessHandler: allowPermission(auth.ObjectTypeServer, auth.EntitlementCanEdit)}, + Get: APIEndpointAction{Handler: warningGet, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, + Patch: APIEndpointAction{Handler: warningPatch, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, + Put: APIEndpointAction{Handler: warningPut, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, + Delete: APIEndpointAction{Handler: warningDelete, AccessHandler: allowPermission(entity.TypeServer, auth.EntitlementCanEdit)}, } func filterWarnings(warnings []api.Warning, clauses *filter.ClauseSet) ([]api.Warning, error) { diff --git a/lxd/warnings/warnings.go b/lxd/warnings/warnings.go index 38d843d58fd2..ec7c12bce0f5 100644 --- a/lxd/warnings/warnings.go +++ b/lxd/warnings/warnings.go @@ -8,7 +8,7 @@ import ( "github.com/canonical/lxd/lxd/db" "github.com/canonical/lxd/lxd/db/cluster" "github.com/canonical/lxd/lxd/db/warningtype" - "github.com/canonical/lxd/shared/entity" + "github.com/canonical/lxd/lxd/entity" ) // ResolveWarningsByLocalNodeOlderThan resolves all warnings which are older than the provided time.