Skip to content

Commit

Permalink
lxd: Update storage volume snapshot and backup access checks.
Browse files Browse the repository at this point in the history
We can now check `can_view`, `can_edit`, and `can_delete` against
the backup/snapshot itself. We should do so to more accurately reflect
the authorization model.

Signed-off-by: Mark Laing <mark.laing@canonical.com>
  • Loading branch information
markylaing committed Oct 25, 2024
1 parent 8559cea commit d012dcc
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 14 deletions.
34 changes: 27 additions & 7 deletions lxd/storage_volumes_backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,24 @@ var storagePoolVolumeTypeCustomBackupsCmd = APIEndpoint{
Path: "storage-pools/{poolName}/volumes/{type}/{volumeName}/backups",
MetricsType: entity.TypeStoragePool,

Get: APIEndpointAction{Handler: storagePoolVolumeTypeCustomBackupsGet, AccessHandler: storagePoolVolumeTypeAccessHandler(auth.EntitlementCanView)},
Post: APIEndpointAction{Handler: storagePoolVolumeTypeCustomBackupsPost, AccessHandler: storagePoolVolumeTypeAccessHandler(auth.EntitlementCanManageBackups)},
Get: APIEndpointAction{Handler: storagePoolVolumeTypeCustomBackupsGet, AccessHandler: allowProjectResourceList},
Post: APIEndpointAction{Handler: storagePoolVolumeTypeCustomBackupsPost, AccessHandler: storagePoolVolumeTypeAccessHandler(entity.TypeStorageVolume, auth.EntitlementCanManageBackups)},
}

var storagePoolVolumeTypeCustomBackupCmd = APIEndpoint{
Path: "storage-pools/{poolName}/volumes/{type}/{volumeName}/backups/{backupName}",
MetricsType: entity.TypeStoragePool,

Get: APIEndpointAction{Handler: storagePoolVolumeTypeCustomBackupGet, AccessHandler: storagePoolVolumeTypeAccessHandler(auth.EntitlementCanView)},
Post: APIEndpointAction{Handler: storagePoolVolumeTypeCustomBackupPost, AccessHandler: storagePoolVolumeTypeAccessHandler(auth.EntitlementCanManageBackups)},
Delete: APIEndpointAction{Handler: storagePoolVolumeTypeCustomBackupDelete, AccessHandler: storagePoolVolumeTypeAccessHandler(auth.EntitlementCanManageBackups)},
Get: APIEndpointAction{Handler: storagePoolVolumeTypeCustomBackupGet, AccessHandler: storagePoolVolumeTypeAccessHandler(entity.TypeStorageVolumeBackup, auth.EntitlementCanView)},
Post: APIEndpointAction{Handler: storagePoolVolumeTypeCustomBackupPost, AccessHandler: storagePoolVolumeTypeAccessHandler(entity.TypeStorageVolumeBackup, auth.EntitlementCanEdit)},
Delete: APIEndpointAction{Handler: storagePoolVolumeTypeCustomBackupDelete, AccessHandler: storagePoolVolumeTypeAccessHandler(entity.TypeStorageVolumeBackup, auth.EntitlementCanDelete)},
}

var storagePoolVolumeTypeCustomBackupExportCmd = APIEndpoint{
Path: "storage-pools/{poolName}/volumes/{type}/{volumeName}/backups/{backupName}/export",
MetricsType: entity.TypeStoragePool,

Get: APIEndpointAction{Handler: storagePoolVolumeTypeCustomBackupExportGet, AccessHandler: storagePoolVolumeTypeAccessHandler(auth.EntitlementCanView)},
Get: APIEndpointAction{Handler: storagePoolVolumeTypeCustomBackupExportGet, AccessHandler: storagePoolVolumeTypeAccessHandler(entity.TypeStorageVolumeBackup, auth.EntitlementCanView)},
}

// swagger:operation GET /1.0/storage-pools/{poolName}/volumes/{type}/{volumeName}/backups storage storage_pool_volumes_type_backups_get
Expand Down Expand Up @@ -159,6 +159,11 @@ var storagePoolVolumeTypeCustomBackupExportCmd = APIEndpoint{
func storagePoolVolumeTypeCustomBackupsGet(d *Daemon, r *http.Request) response.Response {
s := d.State()

err := addStoragePoolVolumeDetailsToRequestContext(s, r)
if err != nil {
return response.SmartError(err)
}

effectiveProjectName, err := request.GetCtxValue[string](r.Context(), request.CtxEffectiveProjectName)
if err != nil {
return response.SmartError(err)
Expand Down Expand Up @@ -201,9 +206,24 @@ func storagePoolVolumeTypeCustomBackupsGet(d *Daemon, r *http.Request) response.
resultString := []string{}
resultMap := []*api.StoragePoolVolumeBackup{}

canView, err := s.Authorizer.GetPermissionChecker(r.Context(), auth.EntitlementCanView, entity.TypeStorageVolumeBackup)
if err != nil {
return response.SmartError(err)
}

for _, backup := range backups {
_, backupName, ok := strings.Cut(backup.Name(), "/")
if !ok {
// Not adding the name to the error response here because we were unable to check if the caller is allowed to view it.
return response.InternalError(fmt.Errorf("Storage volume backup has invalid name"))
}

if !canView(entity.StorageVolumeBackupURL(request.ProjectParam(r), details.location, details.pool.Name(), details.volumeTypeName, details.volumeName, backupName)) {
continue
}

if !recursion {
url := api.NewURL().Path(version.APIVersion, "storage-pools", details.pool.Name(), "volumes", "custom", details.volumeName, "backups", strings.Split(backup.Name(), "/")[1]).String()
url := api.NewURL().Path(version.APIVersion, "storage-pools", details.pool.Name(), "volumes", "custom", details.volumeName, "backups", backupName).String()
resultString = append(resultString, url)
} else {
render := backup.Render()
Expand Down
28 changes: 21 additions & 7 deletions lxd/storage_volumes_snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,19 @@ var storagePoolVolumeSnapshotsTypeCmd = APIEndpoint{
Path: "storage-pools/{poolName}/volumes/{type}/{volumeName}/snapshots",
MetricsType: entity.TypeStoragePool,

Get: APIEndpointAction{Handler: storagePoolVolumeSnapshotsTypeGet, AccessHandler: storagePoolVolumeTypeAccessHandler(auth.EntitlementCanView)},
Post: APIEndpointAction{Handler: storagePoolVolumeSnapshotsTypePost, AccessHandler: storagePoolVolumeTypeAccessHandler(auth.EntitlementCanManageSnapshots)},
Get: APIEndpointAction{Handler: storagePoolVolumeSnapshotsTypeGet, AccessHandler: allowProjectResourceList},
Post: APIEndpointAction{Handler: storagePoolVolumeSnapshotsTypePost, AccessHandler: storagePoolVolumeTypeAccessHandler(entity.TypeStorageVolume, auth.EntitlementCanManageSnapshots)},
}

var storagePoolVolumeSnapshotTypeCmd = APIEndpoint{
Path: "storage-pools/{poolName}/volumes/{type}/{volumeName}/snapshots/{snapshotName}",
MetricsType: entity.TypeStoragePool,

Delete: APIEndpointAction{Handler: storagePoolVolumeSnapshotTypeDelete, AccessHandler: storagePoolVolumeTypeAccessHandler(auth.EntitlementCanManageSnapshots)},
Get: APIEndpointAction{Handler: storagePoolVolumeSnapshotTypeGet, AccessHandler: storagePoolVolumeTypeAccessHandler(auth.EntitlementCanView)},
Post: APIEndpointAction{Handler: storagePoolVolumeSnapshotTypePost, AccessHandler: storagePoolVolumeTypeAccessHandler(auth.EntitlementCanManageSnapshots)},
Patch: APIEndpointAction{Handler: storagePoolVolumeSnapshotTypePatch, AccessHandler: storagePoolVolumeTypeAccessHandler(auth.EntitlementCanManageSnapshots)},
Put: APIEndpointAction{Handler: storagePoolVolumeSnapshotTypePut, AccessHandler: storagePoolVolumeTypeAccessHandler(auth.EntitlementCanManageSnapshots)},
Delete: APIEndpointAction{Handler: storagePoolVolumeSnapshotTypeDelete, AccessHandler: storagePoolVolumeTypeAccessHandler(entity.TypeStorageVolumeSnapshot, auth.EntitlementCanDelete)},
Get: APIEndpointAction{Handler: storagePoolVolumeSnapshotTypeGet, AccessHandler: storagePoolVolumeTypeAccessHandler(entity.TypeStorageVolumeSnapshot, auth.EntitlementCanView)},
Post: APIEndpointAction{Handler: storagePoolVolumeSnapshotTypePost, AccessHandler: storagePoolVolumeTypeAccessHandler(entity.TypeStorageVolumeSnapshot, auth.EntitlementCanEdit)},
Patch: APIEndpointAction{Handler: storagePoolVolumeSnapshotTypePatch, AccessHandler: storagePoolVolumeTypeAccessHandler(entity.TypeStorageVolumeSnapshot, auth.EntitlementCanEdit)},
Put: APIEndpointAction{Handler: storagePoolVolumeSnapshotTypePut, AccessHandler: storagePoolVolumeTypeAccessHandler(entity.TypeStorageVolumeSnapshot, auth.EntitlementCanEdit)},
}

// swagger:operation POST /1.0/storage-pools/{poolName}/volumes/{type}/{volumeName}/snapshots storage storage_pool_volumes_type_snapshots_post
Expand Down Expand Up @@ -346,6 +346,11 @@ func storagePoolVolumeSnapshotsTypePost(d *Daemon, r *http.Request) response.Res
func storagePoolVolumeSnapshotsTypeGet(d *Daemon, r *http.Request) response.Response {
s := d.State()

err := addStoragePoolVolumeDetailsToRequestContext(s, r)
if err != nil {
return response.SmartError(err)
}

details, err := request.GetCtxValue[storageVolumeDetails](r.Context(), ctxStorageVolumeDetails)
if err != nil {
return response.SmartError(err)
Expand Down Expand Up @@ -380,12 +385,21 @@ func storagePoolVolumeSnapshotsTypeGet(d *Daemon, r *http.Request) response.Resp
return response.SmartError(err)
}

canView, err := s.Authorizer.GetPermissionChecker(r.Context(), auth.EntitlementCanView, entity.TypeStorageVolumeSnapshot)
if err != nil {
return response.SmartError(err)
}

// Prepare the response.
resultString := []string{}
resultMap := []*api.StorageVolumeSnapshot{}
for _, volume := range volumes {
_, snapshotName, _ := api.GetParentAndSnapshotName(volume.Name)

if !canView(entity.StorageVolumeSnapshotURL(request.ProjectParam(r), details.location, details.pool.Name(), details.volumeTypeName, details.volumeName, snapshotName)) {
continue
}

if !recursion {
resultString = append(resultString, fmt.Sprintf("/%s/storage-pools/%s/volumes/%s/%s/snapshots/%s", version.APIVersion, details.pool.Name(), details.volumeTypeName, details.volumeName, snapshotName))
} else {
Expand Down

0 comments on commit d012dcc

Please sign in to comment.