Skip to content

Commit

Permalink
auth: Add more tests for entity enrichment with entitlements (#14973)
Browse files Browse the repository at this point in the history
This adds tests for the enrichment of the remaining LXD entities that
are eligible. This also remove support for entitlement enrichment for
instance<snapshot|backup> and volume<snapshot|backup>. These are managed
by the parent instance/volume `can_mange_<snapshots|backups>`
  • Loading branch information
tomponline authored Feb 24, 2025
2 parents ec09b24 + b0a63b9 commit ead711f
Show file tree
Hide file tree
Showing 12 changed files with 299 additions and 192 deletions.
36 changes: 0 additions & 36 deletions doc/rest-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1803,15 +1803,6 @@ definitions:
x-go-package: github.com/canonical/lxd/shared/api
InstanceBackup:
properties:
access_entitlements:
description: AccessEntitlements represents the entitlements that are granted to the requesting user on the attached entity.
example:
- can_view
- can_edit
items:
type: string
type: array
x-go-name: AccessEntitlements
container_only:
description: Whether to ignore snapshots (deprecated, use instance_only)
example: false
Expand Down Expand Up @@ -2281,15 +2272,6 @@ definitions:
x-go-package: github.com/canonical/lxd/shared/api
InstanceSnapshot:
properties:
access_entitlements:
description: AccessEntitlements represents the entitlements that are granted to the requesting user on the attached entity.
example:
- can_view
- can_edit
items:
type: string
type: array
x-go-name: AccessEntitlements
architecture:
description: Architecture name
example: x86_64
Expand Down Expand Up @@ -6490,15 +6472,6 @@ definitions:
StoragePoolVolumeBackup:
description: StoragePoolVolumeBackup represents a LXD volume backup
properties:
access_entitlements:
description: AccessEntitlements represents the entitlements that are granted to the requesting user on the attached entity.
example:
- can_view
- can_edit
items:
type: string
type: array
x-go-name: AccessEntitlements
created_at:
description: When the backup was created
example: "2021-03-23T16:38:37.753398689-04:00"
Expand Down Expand Up @@ -6754,15 +6727,6 @@ definitions:
StorageVolumeSnapshot:
description: StorageVolumeSnapshot represents a LXD storage volume snapshot
properties:
access_entitlements:
description: AccessEntitlements represents the entitlements that are granted to the requesting user on the attached entity.
example:
- can_view
- can_edit
items:
type: string
type: array
x-go-name: AccessEntitlements
config:
additionalProperties:
type: string
Expand Down
32 changes: 3 additions & 29 deletions lxd/instance_backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,6 @@ func instanceBackupsGet(d *Daemon, r *http.Request) response.Response {
return response.SmartError(err)
}

withEntitlements, err := extractEntitlementsFromQuery(r, entity.TypeInstanceBackup, true)
if err != nil {
return response.SmartError(err)
}

if shared.IsSnapshot(cname) {
return response.BadRequest(fmt.Errorf("Invalid instance name"))
}
Expand Down Expand Up @@ -169,7 +164,6 @@ func instanceBackupsGet(d *Daemon, r *http.Request) response.Response {

resultString := []string{}
resultMap := []*api.InstanceBackup{}
urlToBackup := make(map[*api.URL]auth.EntitlementReporter, len(backups))
canView, err := s.Authorizer.GetPermissionChecker(r.Context(), auth.EntitlementCanView, entity.TypeInstanceBackup)
if err != nil {
return response.SmartError(err)
Expand All @@ -193,21 +187,13 @@ func instanceBackupsGet(d *Daemon, r *http.Request) response.Response {
} else {
render := backup.Render()
resultMap = append(resultMap, render)
urlToBackup[entity.InstanceBackupURL(projectName, c.Name(), backupName)] = render
}
}

if !recursion {
return response.SyncResponse(true, resultString)
}

if len(withEntitlements) > 0 {
err = reportEntitlements(r.Context(), s.Authorizer, s.IdentityCache, entity.TypeInstanceBackup, withEntitlements, urlToBackup)
if err != nil {
return response.SmartError(err)
}
}

return response.SyncResponse(true, resultMap)
}

Expand Down Expand Up @@ -349,7 +335,8 @@ func instanceBackupsPost(d *Daemon, r *http.Request) response.Response {
}

fullName := name + shared.SnapshotDelimiter + backupName
instanceOnly := req.InstanceOnly || req.ContainerOnly
// We keep the req.ContainerOnly for backward compatibility.
instanceOnly := req.InstanceOnly || req.ContainerOnly //nolint:staticcheck,unused

backup := func(op *operations.Operation) error {
args := db.InstanceBackup{
Expand Down Expand Up @@ -442,11 +429,6 @@ func instanceBackupGet(d *Daemon, r *http.Request) response.Response {
return response.SmartError(err)
}

withEntitlements, err := extractEntitlementsFromQuery(r, entity.TypeInstanceBackup, false)
if err != nil {
return response.SmartError(err)
}

if shared.IsSnapshot(name) {
return response.BadRequest(fmt.Errorf("Invalid instance name"))
}
Expand All @@ -472,15 +454,7 @@ func instanceBackupGet(d *Daemon, r *http.Request) response.Response {
return response.SmartError(err)
}

renderedBackup := backup.Render()
if len(withEntitlements) > 0 {
err = reportEntitlements(r.Context(), s.Authorizer, s.IdentityCache, entity.TypeInstanceBackup, withEntitlements, map[*api.URL]auth.EntitlementReporter{entity.InstanceBackupURL(projectName, name, backupName): renderedBackup})
if err != nil {
return response.SmartError(err)
}
}

return response.SyncResponse(true, renderedBackup)
return response.SyncResponse(true, backup.Render())
}

// swagger:operation POST /1.0/instances/{name}/backups/{backup} instances instance_backup_post
Expand Down
26 changes: 0 additions & 26 deletions lxd/instance_snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,15 +157,9 @@ func instanceSnapshotsGet(d *Daemon, r *http.Request) response.Response {
return response.SmartError(err)
}

withEntitlements, err := extractEntitlementsFromQuery(r, entity.TypeInstanceSnapshot, true)
if err != nil {
return response.SmartError(err)
}

recursion := util.IsRecursionRequest(r)
resultString := []string{}
resultMap := []*api.InstanceSnapshot{}
urlToSnaps := make(map[*api.URL]auth.EntitlementReporter)

if !recursion {
var snaps []string
Expand Down Expand Up @@ -225,21 +219,13 @@ func instanceSnapshotsGet(d *Daemon, r *http.Request) response.Response {
}

resultMap = append(resultMap, renderedSnap)
urlToSnaps[entity.InstanceSnapshotURL(projectName, cname, snapName)] = renderedSnap
}
}

if !recursion {
return response.SyncResponse(true, resultString)
}

if len(withEntitlements) > 0 {
err = reportEntitlements(r.Context(), s.Authorizer, s.IdentityCache, entity.TypeInstanceSnapshot, withEntitlements, urlToSnaps)
if err != nil {
return response.SmartError(err)
}
}

return response.SyncResponse(true, resultMap)
}

Expand Down Expand Up @@ -629,11 +615,6 @@ func snapshotPut(s *state.State, r *http.Request, snapInst instance.Instance) re
// "500":
// $ref: "#/responses/InternalServerError"
func snapshotGet(s *state.State, r *http.Request, snapInst instance.Instance) response.Response {
withEntitlements, err := extractEntitlementsFromQuery(r, entity.TypeInstanceSnapshot, false)
if err != nil {
return response.SmartError(err)
}

render, _, err := snapInst.Render(storagePools.RenderSnapshotUsage(s, snapInst))
if err != nil {
return response.SmartError(err)
Expand All @@ -644,13 +625,6 @@ func snapshotGet(s *state.State, r *http.Request, snapInst instance.Instance) re
return response.InternalError(fmt.Errorf("Render didn't return a snapshot"))
}

if len(withEntitlements) > 0 {
err = reportEntitlements(r.Context(), s.Authorizer, s.IdentityCache, entity.TypeInstanceSnapshot, withEntitlements, map[*api.URL]auth.EntitlementReporter{entity.InstanceSnapshotURL(snapInst.Project().Name, snapInst.Name(), renderedSnap.Name): renderedSnap})
if err != nil {
return response.SmartError(err)
}
}

etag := []any{snapInst.ExpiryDate()}
return response.SyncResponseETag(true, renderedSnap, etag)
}
Expand Down
4 changes: 2 additions & 2 deletions lxd/network_acls.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ func networkACLsGet(d *Daemon, r *http.Request) response.Response {
}

resultString := []string{}
resultMap := []api.NetworkACL{}
resultMap := []*api.NetworkACL{}
urlToNetworkACL := make(map[*api.URL]auth.EntitlementReporter)
for _, aclName := range aclNames {
if !userHasPermission(entity.NetworkACLURL(requestProjectName, aclName)) {
Expand All @@ -201,7 +201,7 @@ func networkACLsGet(d *Daemon, r *http.Request) response.Response {
netACLInfo.UsedBy, _ = netACL.UsedBy() // Ignore errors in UsedBy, will return nil.
netACLInfo.UsedBy = project.FilterUsedBy(s.Authorizer, r, netACLInfo.UsedBy)

resultMap = append(resultMap, *netACLInfo)
resultMap = append(resultMap, netACLInfo)
urlToNetworkACL[entity.NetworkACLURL(requestProjectName, aclName)] = netACLInfo
}
}
Expand Down
4 changes: 2 additions & 2 deletions lxd/network_zones.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ func networkZonesGet(d *Daemon, r *http.Request) response.Response {
}

resultString := []string{}
resultMap := []api.NetworkZone{}
resultMap := []*api.NetworkZone{}
urlToNetworkZone := make(map[*api.URL]auth.EntitlementReporter)
for zoneName, projectName := range zoneNamesMap {
// Check permission for each network zone against the requested project.
Expand All @@ -296,7 +296,7 @@ func networkZonesGet(d *Daemon, r *http.Request) response.Response {
netzoneInfo.UsedBy = project.FilterUsedBy(s.Authorizer, r, netzoneInfo.UsedBy)
netzoneInfo.Project = projectName

resultMap = append(resultMap, *netzoneInfo)
resultMap = append(resultMap, netzoneInfo)
urlToNetworkZone[entity.NetworkZoneURL(projectName, zoneName)] = netzoneInfo
}
}
Expand Down
29 changes: 1 addition & 28 deletions lxd/storage_volumes_backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,6 @@ func storagePoolVolumeTypeCustomBackupsGet(d *Daemon, r *http.Request) response.
return response.SmartError(err)
}

withEntitlements, err := extractEntitlementsFromQuery(r, entity.TypeStorageVolumeBackup, true)
if err != nil {
return response.SmartError(err)
}

// Check that the storage volume type is valid.
if details.volumeType != cluster.StoragePoolVolumeTypeCustom {
return response.BadRequest(fmt.Errorf("Invalid storage volume type %q", details.volumeTypeName))
Expand Down Expand Up @@ -210,7 +205,6 @@ func storagePoolVolumeTypeCustomBackupsGet(d *Daemon, r *http.Request) response.

resultString := []string{}
resultMap := []*api.StoragePoolVolumeBackup{}
urlToBackup := make(map[*api.URL]auth.EntitlementReporter)

canView, err := s.Authorizer.GetPermissionChecker(r.Context(), auth.EntitlementCanView, entity.TypeStorageVolumeBackup)
if err != nil {
Expand All @@ -234,21 +228,13 @@ func storagePoolVolumeTypeCustomBackupsGet(d *Daemon, r *http.Request) response.
} else {
render := backup.Render()
resultMap = append(resultMap, render)
urlToBackup[entity.StorageVolumeBackupURL(request.ProjectParam(r), details.location, details.pool.Name(), details.volumeTypeName, details.volumeName, backupName)] = render
}
}

if !recursion {
return response.SyncResponse(true, resultString)
}

if len(withEntitlements) > 0 {
err = reportEntitlements(r.Context(), s.Authorizer, s.IdentityCache, entity.TypeStorageVolumeBackup, withEntitlements, urlToBackup)
if err != nil {
return response.SmartError(err)
}
}

return response.SyncResponse(true, resultMap)
}

Expand Down Expand Up @@ -493,11 +479,6 @@ func storagePoolVolumeTypeCustomBackupGet(d *Daemon, r *http.Request) response.R
return response.SmartError(err)
}

withEntitlements, err := extractEntitlementsFromQuery(r, entity.TypeStorageVolumeBackup, false)
if err != nil {
return response.SmartError(err)
}

// Get backup name.
backupName, err := url.PathUnescape(mux.Vars(r)["backupName"])
if err != nil {
Expand Down Expand Up @@ -531,15 +512,7 @@ func storagePoolVolumeTypeCustomBackupGet(d *Daemon, r *http.Request) response.R
return response.SmartError(err)
}

renderedBackup := backup.Render()
if len(withEntitlements) > 0 {
err = reportEntitlements(r.Context(), s.Authorizer, s.IdentityCache, entity.TypeStorageVolumeBackup, withEntitlements, map[*api.URL]auth.EntitlementReporter{entity.StorageVolumeBackupURL(request.ProjectParam(r), details.location, details.pool.Name(), details.volumeTypeName, details.volumeName, backupName): renderedBackup})
if err != nil {
return response.SmartError(err)
}
}

return response.SyncResponse(true, renderedBackup)
return response.SyncResponse(true, backup.Render())
}

// swagger:operation POST /1.0/storage-pools/{poolName}/volumes/{type}/{volumeName}/backups/{backupName} storage storage_pool_volumes_type_backup_post
Expand Down
Loading

0 comments on commit ead711f

Please sign in to comment.