diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go index 51ba0a2bcd5c..39d84f4ca3c7 100644 --- a/lxd/api_1.0.go +++ b/lxd/api_1.0.go @@ -10,9 +10,9 @@ import ( "strings" "github.com/canonical/lxd/client" + "github.com/canonical/lxd/lxd/auth" "github.com/canonical/lxd/lxd/auth/candid" "github.com/canonical/lxd/lxd/auth/oidc" - "github.com/canonical/lxd/lxd/auth/rbac" "github.com/canonical/lxd/lxd/cluster" clusterConfig "github.com/canonical/lxd/lxd/cluster/config" "github.com/canonical/lxd/lxd/config" @@ -375,7 +375,7 @@ func api10Get(d *Daemon, r *http.Request) response.Response { fullSrv.AuthUserName = requestor.Username fullSrv.AuthUserMethod = requestor.Protocol - if rbac.UserIsAdmin(r) { + if d.authorizer.UserIsAdmin(r) { fullSrv.Config, err = daemonConfigRender(s) if err != nil { return response.InternalError(err) @@ -911,12 +911,10 @@ func doApi10UpdateTriggers(d *Daemon, nodeChanged, clusterChanged map[string]str if rbacChanged { apiURL, apiKey, apiExpiry, agentURL, agentUsername, agentPrivateKey, agentPublicKey := clusterConfig.RBACServer() - // Since RBAC seems to have been set up already, we need to disable it temporarily - if d.rbac != nil { - d.candidVerifier = nil - d.rbac.StopStatusCheck() - d.rbac = nil - } + d.candidVerifier = nil + d.authorizer.StopStatusCheck() + // Reset to default authorizer in case rbac fails. + d.authorizer = auth.DefaultAuthorizer() err := d.setupRBACServer(apiURL, apiKey, apiExpiry, agentURL, agentUsername, agentPrivateKey, agentPublicKey) if err != nil { diff --git a/lxd/api_project.go b/lxd/api_project.go index df8fedb764a6..bf23bea04006 100644 --- a/lxd/api_project.go +++ b/lxd/api_project.go @@ -13,7 +13,6 @@ import ( "github.com/gorilla/mux" - "github.com/canonical/lxd/lxd/auth/rbac" "github.com/canonical/lxd/lxd/db" "github.com/canonical/lxd/lxd/db/cluster" "github.com/canonical/lxd/lxd/db/operationtype" @@ -147,7 +146,7 @@ func projectsGet(d *Daemon, r *http.Request) response.Response { filtered := []api.Project{} for _, project := range projects { - if !rbac.UserHasPermission(r, project.Name, "view") { + if !d.State().Authorizer.UserHasPermission(r, project.Name, "view") { continue } @@ -334,11 +333,9 @@ func projectsPost(d *Daemon, r *http.Request) response.Response { return response.SmartError(fmt.Errorf("Failed creating project %q: %w", project.Name, err)) } - if d.rbac != nil { - err = d.rbac.AddProject(id, project.Name) - if err != nil { - return response.SmartError(err) - } + err = d.authorizer.AddProject(id, project.Name) + if err != nil { + return response.SmartError(err) } requestor := request.CreateRequestor(r) @@ -405,7 +402,7 @@ func projectGet(d *Daemon, r *http.Request) response.Response { } // Check user permissions - if !rbac.UserHasPermission(r, name, "view") { + if !d.State().Authorizer.UserHasPermission(r, name, "view") { return response.Forbidden(nil) } @@ -475,7 +472,7 @@ func projectPut(d *Daemon, r *http.Request) response.Response { } // Check user permissions - if !rbac.UserHasPermission(r, name, "manage-projects") { + if !s.Authorizer.UserHasPermission(r, name, "manage-projects") { return response.Forbidden(nil) } @@ -566,7 +563,7 @@ func projectPatch(d *Daemon, r *http.Request) response.Response { } // Check user permissions - if !rbac.UserHasPermission(r, name, "manage-projects") { + if !s.Authorizer.UserHasPermission(r, name, "manage-projects") { return response.Forbidden(nil) } @@ -843,11 +840,9 @@ func projectPost(d *Daemon, r *http.Request) response.Response { return err } - if d.rbac != nil { - err = d.rbac.RenameProject(id, req.Name) - if err != nil { - return err - } + err = d.authorizer.RenameProject(id, req.Name) + if err != nil { + return err } requestor := request.CreateRequestor(r) @@ -923,11 +918,9 @@ func projectDelete(d *Daemon, r *http.Request) response.Response { return response.SmartError(err) } - if d.rbac != nil { - err = d.rbac.DeleteProject(id) - if err != nil { - return response.SmartError(err) - } + err = d.authorizer.DeleteProject(id) + if err != nil { + return response.SmartError(err) } requestor := request.CreateRequestor(r) @@ -977,7 +970,7 @@ func projectStateGet(d *Daemon, r *http.Request) response.Response { } // Check user permissions. - if !rbac.UserHasPermission(r, name, "view") { + if !d.State().Authorizer.UserHasPermission(r, name, "view") { return response.Forbidden(nil) } diff --git a/lxd/auth/rbac/server.go b/lxd/auth/rbac/rbac.go similarity index 85% rename from lxd/auth/rbac/server.go rename to lxd/auth/rbac/rbac.go index 599eba31f7b8..796bd9f8752f 100644 --- a/lxd/auth/rbac/server.go +++ b/lxd/auth/rbac/rbac.go @@ -19,6 +19,8 @@ import ( "github.com/go-macaroon-bakery/macaroon-bakery/v3/httpbakery" "github.com/go-macaroon-bakery/macaroon-bakery/v3/httpbakery/agent" + "github.com/canonical/lxd/lxd/auth" + "github.com/canonical/lxd/lxd/request" "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/logger" ) @@ -45,14 +47,8 @@ type rbacStatus struct { // Errors. var errUnknownUser = fmt.Errorf("Unknown RBAC user") -// UserAccess struct for permission checks. -type UserAccess struct { - Admin bool - Projects map[string][]string -} - -// Server represents an RBAC server. -type Server struct { +// server represents an RBAC server. +type server struct { apiURL string apiKey string @@ -74,8 +70,8 @@ type Server struct { } // NewServer returns a new RBAC server instance. -func NewServer(apiURL string, apiKey string, agentAuthURL string, agentUsername string, agentPrivateKey string, agentPublicKey string) (*Server, error) { - r := Server{ +func NewServer(apiURL string, apiKey string, agentAuthURL string, agentUsername string, agentPrivateKey string, agentPublicKey string, getProjects func() (map[int64]string, error)) (auth.Authorizer, error) { + r := server{ apiURL: apiURL, apiKey: apiKey, lastSyncID: "", @@ -121,11 +117,52 @@ func NewServer(apiURL string, apiKey string, agentAuthURL string, agentUsername return nil, err } + // Perform full sync when online + go func() { + for { + err = r.SyncProjects() + if err != nil { + time.Sleep(time.Minute) + continue + } + + break + } + }() + + r.StartStatusCheck() + return &r, nil } +// UserIsAdmin checks whether the requestor is a global admin. +func (r *server) UserIsAdmin(req *http.Request) bool { + val := req.Context().Value(request.CtxAccess) + if val == nil { + return false + } + + ua := val.(*auth.UserAccess) + return ua.Admin +} + +// UserHasPermission checks whether the requestor has a specific permission on a project. +func (r *server) UserHasPermission(req *http.Request, projectName string, permission string) bool { + val := req.Context().Value(request.CtxAccess) + if val == nil { + return false + } + + ua := val.(*auth.UserAccess) + if ua.Admin { + return true + } + + return shared.StringInSlice(permission, ua.Projects[projectName]) +} + // StartStatusCheck runs a status checking loop. -func (r *Server) StartStatusCheck() { +func (r *server) StartStatusCheck() { var status rbacStatus // Figure out the new URL. @@ -201,12 +238,12 @@ func (r *Server) StartStatusCheck() { } // StopStatusCheck stops the periodic status checker. -func (r *Server) StopStatusCheck() { +func (r *server) StopStatusCheck() { r.ctxCancel() } // SyncProjects updates the list of projects in RBAC. -func (r *Server) SyncProjects() error { +func (r *server) SyncProjects() error { if r.projectsFunc == nil { return fmt.Errorf("ProjectsFunc isn't configured yet, cannot sync") } @@ -245,7 +282,7 @@ func (r *Server) SyncProjects() error { } // AddProject adds a new project resource to RBAC. -func (r *Server) AddProject(id int64, name string) error { +func (r *server) AddProject(id int64, name string) error { resource := rbacResource{ Name: name, Identifier: strconv.FormatInt(id, 10), @@ -266,7 +303,7 @@ func (r *Server) AddProject(id int64, name string) error { } // DeleteProject adds a new project resource to RBAC. -func (r *Server) DeleteProject(id int64) error { +func (r *server) DeleteProject(id int64) error { // Update RBAC err := r.postResources(nil, []string{strconv.FormatInt(id, 10)}, false) if err != nil { @@ -287,12 +324,12 @@ func (r *Server) DeleteProject(id int64) error { } // RenameProject renames an existing project resource in RBAC. -func (r *Server) RenameProject(id int64, name string) error { +func (r *server) RenameProject(id int64, name string) error { return r.AddProject(id, name) } // UserAccess returns a UserAccess struct for the user. -func (r *Server) UserAccess(username string) (*UserAccess, error) { +func (r *server) UserAccess(username string) (*auth.UserAccess, error) { r.permissionsLock.Lock() defer r.permissionsLock.Unlock() @@ -310,7 +347,7 @@ func (r *Server) UserAccess(username string) (*UserAccess, error) { } // Prepare the response. - access := UserAccess{ + access := auth.UserAccess{ Admin: shared.StringInSlice("admin", permissions[""]), Projects: map[string][]string{}, } @@ -337,7 +374,7 @@ func (r *Server) UserAccess(username string) (*UserAccess, error) { return &access, nil } -func (r *Server) flushCache() { +func (r *server) flushCache() { r.permissionsLock.Lock() defer r.permissionsLock.Unlock() @@ -354,7 +391,7 @@ func (r *Server) flushCache() { logger.Info("Flushed RBAC permissions cache") } -func (r *Server) syncAdmin(username string) bool { +func (r *server) syncAdmin(username string) bool { u, err := url.Parse(r.apiURL) if err != nil { return false @@ -387,7 +424,7 @@ func (r *Server) syncAdmin(username string) bool { return shared.StringInSlice("admin", permissions[""]) } -func (r *Server) syncPermissions(username string) error { +func (r *server) syncPermissions(username string) error { u, err := url.Parse(r.apiURL) if err != nil { return err @@ -427,7 +464,7 @@ func (r *Server) syncPermissions(username string) error { return nil } -func (r *Server) postResources(updates []rbacResource, removals []string, force bool) error { +func (r *server) postResources(updates []rbacResource, removals []string, force bool) error { // Make sure that we have a baseline sync in place if !force && r.lastSyncID == "" { return r.SyncProjects() diff --git a/lxd/auth/rbac/requests.go b/lxd/auth/rbac/requests.go deleted file mode 100644 index feb71a6ca97f..000000000000 --- a/lxd/auth/rbac/requests.go +++ /dev/null @@ -1,34 +0,0 @@ -package rbac - -import ( - "net/http" - - "github.com/canonical/lxd/lxd/request" - "github.com/canonical/lxd/shared" -) - -// UserIsAdmin checks whether the requestor is a global admin. -func UserIsAdmin(r *http.Request) bool { - val := r.Context().Value(request.CtxAccess) - if val == nil { - return false - } - - ua := val.(*UserAccess) - return ua.Admin -} - -// UserHasPermission checks whether the requestor has a specific permission on a project. -func UserHasPermission(r *http.Request, project string, permission string) bool { - val := r.Context().Value(request.CtxAccess) - if val == nil { - return false - } - - ua := val.(*UserAccess) - if ua.Admin { - return true - } - - return shared.StringInSlice(permission, ua.Projects[project]) -} diff --git a/lxd/certificates.go b/lxd/certificates.go index 5651c88f9a47..8d4caa7ae9f6 100644 --- a/lxd/certificates.go +++ b/lxd/certificates.go @@ -17,7 +17,6 @@ import ( "github.com/gorilla/mux" "github.com/canonical/lxd/client" - "github.com/canonical/lxd/lxd/auth/rbac" "github.com/canonical/lxd/lxd/cluster" clusterConfig "github.com/canonical/lxd/lxd/cluster/config" clusterRequest "github.com/canonical/lxd/lxd/cluster/request" @@ -541,7 +540,7 @@ func certificatesPost(d *Daemon, r *http.Request) response.Response { } // Handle requests by non-admin users. - if !rbac.UserIsAdmin(r) { + if !d.authorizer.UserIsAdmin(r) { // Non-admin cannot issue tokens. if req.Token { return response.Forbidden(nil) @@ -983,7 +982,7 @@ func doCertificateUpdate(d *Daemon, dbInfo api.Certificate, req api.CertificateP // In order to prevent possible future security issues, the certificate information is // reset in case a non-admin user is performing the update. certProjects := req.Projects - if !rbac.UserIsAdmin(r) { + if !d.authorizer.UserIsAdmin(r) { if r.TLS == nil { response.Forbidden(fmt.Errorf("Cannot update certificate information")) } @@ -1128,7 +1127,7 @@ func certificateDelete(d *Daemon, r *http.Request) response.Response { } // Non-admins are able to delete only their own certificate. - if !rbac.UserIsAdmin(r) { + if !d.authorizer.UserIsAdmin(r) { if r.TLS == nil { response.Forbidden(fmt.Errorf("Cannot delete certificate")) } diff --git a/lxd/daemon.go b/lxd/daemon.go index 5b0dd14e243c..150eaba667db 100644 --- a/lxd/daemon.go +++ b/lxd/daemon.go @@ -81,7 +81,6 @@ type Daemon struct { maas *maas.Controller bgp *bgp.Server dns *dns.Server - rbac *rbac.Server // Event servers devlxdEvents *events.DevLXDServer @@ -240,7 +239,7 @@ func allowAuthenticated(d *Daemon, r *http.Request) response.Response { func allowProjectPermission(feature string, permission string) func(d *Daemon, r *http.Request) response.Response { return func(d *Daemon, r *http.Request) response.Response { // Shortcut for speed - if rbac.UserIsAdmin(r) { + if d.authorizer.UserIsAdmin(r) { return response.EmptySyncResponse } @@ -248,7 +247,7 @@ func allowProjectPermission(feature string, permission string) func(d *Daemon, r projectName := projectParam(r) // Validate whether the user has the needed permission - if !rbac.UserHasPermission(r, projectName, permission) { + if !d.State().Authorizer.UserHasPermission(r, projectName, permission) { return response.Forbidden(nil) } @@ -500,8 +499,8 @@ func (d *Daemon) createCmd(restAPI *mux.Router, version string, c APIEndpoint) { logger.Debug("Handling API request", logCtx) // Get user access data. - userAccess, err := func() (*rbac.UserAccess, error) { - ua := &rbac.UserAccess{} + userAccess, err := func() (*auth.UserAccess, error) { + ua := &auth.UserAccess{} ua.Admin = true // Internal cluster communications. @@ -539,12 +538,12 @@ func (d *Daemon) createCmd(restAPI *mux.Router, version string, c APIEndpoint) { } // If no external authentication configured, we're done now. - if d.candidVerifier == nil || d.rbac == nil || r.RemoteAddr == "@" { + if d.candidVerifier == nil || r.RemoteAddr == "@" { return ua, nil } // Validate RBAC permissions. - ua, err = d.rbac.UserAccess(username) + ua, err = d.authorizer.UserAccess(username) if err != nil { return nil, err } @@ -646,7 +645,7 @@ func (d *Daemon) createCmd(restAPI *mux.Router, version string, c APIEndpoint) { } } else if !action.AllowUntrusted { // Require admin privileges - if !rbac.UserIsAdmin(r) { + if !d.authorizer.UserIsAdmin(r) { return response.Forbidden(nil) } } @@ -1819,7 +1818,7 @@ func (d *Daemon) Stop(ctx context.Context, sig os.Signal) error { // Setup RBAC. func (d *Daemon) setupRBACServer(rbacURL string, rbacKey string, rbacExpiry int64, rbacAgentURL string, rbacAgentUsername string, rbacAgentPrivateKey string, rbacAgentPublicKey string) error { - if d.rbac != nil || rbacURL == "" || rbacAgentURL == "" || rbacAgentUsername == "" || rbacAgentPrivateKey == "" || rbacAgentPublicKey == "" { + if rbacURL == "" || rbacAgentURL == "" || rbacAgentUsername == "" || rbacAgentPrivateKey == "" || rbacAgentPublicKey == "" { return nil } @@ -1841,29 +1840,14 @@ func (d *Daemon) setupRBACServer(rbacURL string, rbacKey string, rbacExpiry int6 return err } - // Perform full sync when online - go func() { - for { - err = server.SyncProjects() - if err != nil { - time.Sleep(time.Minute) - continue - } - - break - } - }() - - server.StartStatusCheck() - - d.rbac = server - // Enable candid authentication d.candidVerifier, err = candid.NewVerifier(fmt.Sprintf("%s/auth", rbacURL), rbacKey, rbacExpiry, "") if err != nil { return err } + d.authorizer = server + return nil } diff --git a/lxd/events.go b/lxd/events.go index 6277e71bb790..52573d23ed11 100644 --- a/lxd/events.go +++ b/lxd/events.go @@ -6,7 +6,6 @@ import ( "net/http" "strings" - "github.com/canonical/lxd/lxd/auth/rbac" "github.com/canonical/lxd/lxd/db" "github.com/canonical/lxd/lxd/db/cluster" "github.com/canonical/lxd/lxd/events" @@ -64,7 +63,7 @@ func eventsSocket(s *state.State, r *http.Request, w http.ResponseWriter) error if len(types) == 1 && types[0] == "" { types = []string{} for _, entry := range eventTypes { - if !rbac.UserIsAdmin(r) && shared.StringInSlice(entry, privilegedEventTypes) { + if !s.Authorizer.UserIsAdmin(r) && shared.StringInSlice(entry, privilegedEventTypes) { continue } @@ -79,7 +78,7 @@ func eventsSocket(s *state.State, r *http.Request, w http.ResponseWriter) error } } - if shared.StringInSlice(api.EventTypeLogging, types) && !rbac.UserIsAdmin(r) { + if shared.StringInSlice(api.EventTypeLogging, types) && !s.Authorizer.UserIsAdmin(r) { return api.StatusErrorf(http.StatusForbidden, "Forbidden") } diff --git a/lxd/instance_post.go b/lxd/instance_post.go index 87fdf45c145c..708defb85d9f 100644 --- a/lxd/instance_post.go +++ b/lxd/instance_post.go @@ -11,7 +11,6 @@ import ( "github.com/gorilla/mux" - "github.com/canonical/lxd/lxd/auth/rbac" "github.com/canonical/lxd/lxd/cluster" "github.com/canonical/lxd/lxd/db" dbCluster "github.com/canonical/lxd/lxd/db/cluster" @@ -214,7 +213,7 @@ func instancePost(d *Daemon, r *http.Request) response.Response { var targetGroupName string - targetMemberInfo, targetGroupName, err = project.CheckTarget(ctx, r, tx, targetProject, target, allMembers) + targetMemberInfo, targetGroupName, err = project.CheckTarget(ctx, s.Authorizer, r, tx, targetProject, target, allMembers) if err != nil { return err } @@ -333,7 +332,7 @@ func instancePost(d *Daemon, r *http.Request) response.Response { // Server-side project migration. if req.Project != "" { // Check if user has access to target project - if !rbac.UserHasPermission(r, req.Project, "manage-containers") { + if !s.Authorizer.UserHasPermission(r, req.Project, "manage-containers") { return response.Forbidden(nil) } diff --git a/lxd/instances_get.go b/lxd/instances_get.go index bd7bdec4e84e..4126aae85235 100644 --- a/lxd/instances_get.go +++ b/lxd/instances_get.go @@ -13,7 +13,6 @@ import ( "github.com/gorilla/mux" - "github.com/canonical/lxd/lxd/auth/rbac" "github.com/canonical/lxd/lxd/cluster" "github.com/canonical/lxd/lxd/db" dbCluster "github.com/canonical/lxd/lxd/db/cluster" @@ -287,7 +286,7 @@ func doInstancesGet(s *state.State, r *http.Request) (any, error) { } for _, project := range projects { - if !rbac.UserHasPermission(r, project.Name, "view") { + if !s.Authorizer.UserHasPermission(r, project.Name, "view") { continue } diff --git a/lxd/instances_post.go b/lxd/instances_post.go index d470ea3a781e..2b2c81de01b1 100644 --- a/lxd/instances_post.go +++ b/lxd/instances_post.go @@ -897,7 +897,7 @@ func instancesPost(d *Daemon, r *http.Request) response.Response { } // Check if the given target is allowed and try to resolve the right member or group - targetMemberInfo, targetGroupName, err = project.CheckTarget(ctx, r, tx, targetProject, target, allMembers) + targetMemberInfo, targetGroupName, err = project.CheckTarget(ctx, s.Authorizer, r, tx, targetProject, target, allMembers) if err != nil { return err } diff --git a/lxd/networks.go b/lxd/networks.go index c768a9e87570..2adb2e2c5f78 100644 --- a/lxd/networks.go +++ b/lxd/networks.go @@ -16,7 +16,6 @@ import ( "github.com/gorilla/mux" "github.com/canonical/lxd/client" - "github.com/canonical/lxd/lxd/auth/rbac" "github.com/canonical/lxd/lxd/cluster" clusterRequest "github.com/canonical/lxd/lxd/cluster/request" "github.com/canonical/lxd/lxd/db" @@ -836,7 +835,7 @@ func doNetworkGet(s *state.State, r *http.Request, allNodes bool, projectName st apiNet.Description = n.Description() apiNet.Type = n.Type() - if rbac.UserIsAdmin(r) { + if s.Authorizer.UserIsAdmin(r) { // Only allow admins to see network config as sensitive info can be stored there. apiNet.Config = n.Config() @@ -879,7 +878,7 @@ func doNetworkGet(s *state.State, r *http.Request, allNodes bool, projectName st return api.Network{}, err } - apiNet.UsedBy = project.FilterUsedBy(r, usedBy) + apiNet.UsedBy = project.FilterUsedBy(s.Authorizer, r, usedBy) } if n != nil { diff --git a/lxd/operations.go b/lxd/operations.go index 23b47e3682db..c32232d09ddc 100644 --- a/lxd/operations.go +++ b/lxd/operations.go @@ -11,7 +11,6 @@ import ( "github.com/gorilla/mux" - "github.com/canonical/lxd/lxd/auth/rbac" "github.com/canonical/lxd/lxd/cluster" "github.com/canonical/lxd/lxd/db" dbCluster "github.com/canonical/lxd/lxd/db/cluster" @@ -258,7 +257,7 @@ func operationDelete(d *Daemon, r *http.Request) response.Response { projectName = project.Default } - if !rbac.UserHasPermission(r, projectName, op.Permission()) { + if !s.Authorizer.UserHasPermission(r, projectName, op.Permission()) { return response.Forbidden(nil) } } diff --git a/lxd/profiles.go b/lxd/profiles.go index 53d7d0a380b6..14eb7a8cf0b3 100644 --- a/lxd/profiles.go +++ b/lxd/profiles.go @@ -173,7 +173,7 @@ func profilesGet(d *Daemon, r *http.Request) response.Response { return err } - apiProfiles[i].UsedBy = project.FilterUsedBy(r, apiProfiles[i].UsedBy) + apiProfiles[i].UsedBy = project.FilterUsedBy(s.Authorizer, r, apiProfiles[i].UsedBy) } if recursion { @@ -399,7 +399,7 @@ func profileGet(d *Daemon, r *http.Request) response.Response { return err } - resp.UsedBy = project.FilterUsedBy(r, resp.UsedBy) + resp.UsedBy = project.FilterUsedBy(s.Authorizer, r, resp.UsedBy) return nil }) diff --git a/lxd/project/permissions.go b/lxd/project/permissions.go index 1ea4b5ce13d6..06312efb817c 100644 --- a/lxd/project/permissions.go +++ b/lxd/project/permissions.go @@ -9,7 +9,7 @@ import ( "strconv" "strings" - "github.com/canonical/lxd/lxd/auth/rbac" + "github.com/canonical/lxd/lxd/auth" "github.com/canonical/lxd/lxd/db" "github.com/canonical/lxd/lxd/db/cluster" deviceconfig "github.com/canonical/lxd/lxd/device/config" @@ -1418,9 +1418,9 @@ var aggregateLimitConfigValuePrinters = map[string]func(int64) string{ } // FilterUsedBy filters a UsedBy list based on project access. -func FilterUsedBy(r *http.Request, entries []string) []string { +func FilterUsedBy(authorizer auth.Authorizer, r *http.Request, entries []string) []string { // Shortcut for admins and non-RBAC environments. - if rbac.UserIsAdmin(r) { + if authorizer.UserIsAdmin(r) { return entries } @@ -1442,7 +1442,7 @@ func FilterUsedBy(r *http.Request, entries []string) []string { projectName = val } - if !rbac.UserHasPermission(r, projectName, "view") { + if !authorizer.UserHasPermission(r, projectName, "view") { continue } @@ -1471,9 +1471,9 @@ func projectHasRestriction(project *api.Project, restrictionKey string, blockVal } // CheckClusterTargetRestriction check if user is allowed to use cluster member targeting. -func CheckClusterTargetRestriction(r *http.Request, project *api.Project, targetFlag string) error { +func CheckClusterTargetRestriction(authorizer auth.Authorizer, r *http.Request, project *api.Project, targetFlag string) error { // Allow server administrators to move instances around even when restricted (node evacuation, ...) - if rbac.UserIsAdmin(r) { + if authorizer.UserIsAdmin(r) { return nil } @@ -1598,11 +1598,11 @@ func CheckTargetGroup(ctx context.Context, tx *db.ClusterTx, p *api.Project, gro // If target is a cluster member and is found in allMembers it returns the resolved node information object. // If target is a cluster group it returns the cluster group name. // In case of error, neither node information nor cluster group name gets returned. -func CheckTarget(ctx context.Context, r *http.Request, tx *db.ClusterTx, p *api.Project, target string, allMembers []db.NodeInfo) (*db.NodeInfo, string, error) { +func CheckTarget(ctx context.Context, authorizer auth.Authorizer, r *http.Request, tx *db.ClusterTx, p *api.Project, target string, allMembers []db.NodeInfo) (*db.NodeInfo, string, error) { targetMemberName, targetGroupName := shared.TargetDetect(target) // Check manual cluster member targeting restrictions. - err := CheckClusterTargetRestriction(r, p, target) + err := CheckClusterTargetRestriction(authorizer, r, p, target) if err != nil { return nil, "", err } diff --git a/lxd/project/permissions_test.go b/lxd/project/permissions_test.go index 8d7d8a6ebbe0..7635fb1c2f19 100644 --- a/lxd/project/permissions_test.go +++ b/lxd/project/permissions_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/canonical/lxd/lxd/auth" "github.com/canonical/lxd/lxd/db" "github.com/canonical/lxd/lxd/db/cluster" "github.com/canonical/lxd/lxd/instance/instancetype" @@ -171,8 +172,9 @@ func TestCheckClusterTargetRestriction_RestrictedTrue(t *testing.T) { require.NoError(t, err) req := &http.Request{} + authorizer := auth.DefaultAuthorizer() - err = project.CheckClusterTargetRestriction(req, p, "n1") + err = project.CheckClusterTargetRestriction(authorizer, req, p, "n1") assert.EqualError(t, err, "This project doesn't allow cluster member targeting") } @@ -195,7 +197,8 @@ func TestCheckClusterTargetRestriction_RestrictedFalse(t *testing.T) { require.NoError(t, err) req := &http.Request{} + authorizer := auth.DefaultAuthorizer() - err = project.CheckClusterTargetRestriction(req, p, "n1") + err = project.CheckClusterTargetRestriction(authorizer, req, p, "n1") assert.NoError(t, err) } diff --git a/lxd/storage_pools.go b/lxd/storage_pools.go index 7f0d7becc186..ecec42a766d5 100644 --- a/lxd/storage_pools.go +++ b/lxd/storage_pools.go @@ -12,7 +12,6 @@ import ( "github.com/gorilla/mux" "github.com/canonical/lxd/client" - "github.com/canonical/lxd/lxd/auth/rbac" "github.com/canonical/lxd/lxd/cluster" clusterRequest "github.com/canonical/lxd/lxd/cluster/request" "github.com/canonical/lxd/lxd/db" @@ -173,9 +172,9 @@ func storagePoolsGet(d *Daemon, r *http.Request) response.Response { } poolAPI := pool.ToAPI() - poolAPI.UsedBy = project.FilterUsedBy(r, poolUsedBy) + poolAPI.UsedBy = project.FilterUsedBy(s.Authorizer, r, poolUsedBy) - if !rbac.UserIsAdmin(r) { + if !s.Authorizer.UserIsAdmin(r) { // Don't allow non-admins to see pool config as sensitive info can be stored there. poolAPI.Config = nil } @@ -608,9 +607,9 @@ func storagePoolGet(d *Daemon, r *http.Request) response.Response { } poolAPI := pool.ToAPI() - poolAPI.UsedBy = project.FilterUsedBy(r, poolUsedBy) + poolAPI.UsedBy = project.FilterUsedBy(s.Authorizer, r, poolUsedBy) - if !rbac.UserIsAdmin(r) { + if !s.Authorizer.UserIsAdmin(r) { // 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 6b7c8351c8af..f5089b90bb2a 100644 --- a/lxd/storage_volumes.go +++ b/lxd/storage_volumes.go @@ -20,7 +20,6 @@ import ( "github.com/gorilla/websocket" "github.com/canonical/lxd/lxd/archive" - "github.com/canonical/lxd/lxd/auth/rbac" "github.com/canonical/lxd/lxd/backup" lxdCluster "github.com/canonical/lxd/lxd/cluster" "github.com/canonical/lxd/lxd/db" @@ -442,7 +441,7 @@ func storagePoolVolumesGet(d *Daemon, r *http.Request) response.Response { return response.InternalError(err) } - vol.UsedBy = project.FilterUsedBy(r, volumeUsedBy) + vol.UsedBy = project.FilterUsedBy(s.Authorizer, r, volumeUsedBy) volumes = append(volumes, vol) } @@ -1042,7 +1041,7 @@ func storagePoolVolumePost(d *Daemon, r *http.Request) response.Response { } // Check if user has access to effective storage target project - if !rbac.UserHasPermission(r, targetProjectName, "manage-storage-volumes") { + if !s.Authorizer.UserHasPermission(r, targetProjectName, "manage-storage-volumes") { return response.Forbidden(nil) } } @@ -1394,7 +1393,7 @@ func storagePoolVolumeGet(d *Daemon, r *http.Request) response.Response { return response.SmartError(err) } - dbVolume.UsedBy = project.FilterUsedBy(r, volumeUsedBy) + dbVolume.UsedBy = project.FilterUsedBy(s.Authorizer, r, volumeUsedBy) etag := []any{volumeName, dbVolume.Type, dbVolume.Config} diff --git a/lxd/storage_volumes_snapshot.go b/lxd/storage_volumes_snapshot.go index 9aea629415a4..71185a5143f2 100644 --- a/lxd/storage_volumes_snapshot.go +++ b/lxd/storage_volumes_snapshot.go @@ -417,7 +417,7 @@ func storagePoolVolumeSnapshotsTypeGet(d *Daemon, r *http.Request) response.Resp return response.SmartError(err) } - vol.UsedBy = project.FilterUsedBy(r, volumeUsedBy) + vol.UsedBy = project.FilterUsedBy(s.Authorizer, r, volumeUsedBy) tmp := &api.StorageVolumeSnapshot{} tmp.Config = vol.Config