Skip to content

Commit

Permalink
lxd: Entitlement enrichment for remaining API entities (#14880)
Browse files Browse the repository at this point in the history
This should fix the issues discussed here:
https://chat.canonical.com/canonical/pl/c3bjkis1g3nmbytao6q5gcaxuc

This also add entitlement enrichment for API entities that weren't added
as part as #14748
  • Loading branch information
tomponline authored Jan 31, 2025
2 parents 3cdcc02 + ce7b04e commit e51283c
Show file tree
Hide file tree
Showing 20 changed files with 264 additions and 36 deletions.
63 changes: 63 additions & 0 deletions doc/rest-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1101,6 +1101,15 @@ definitions:
ImageAliasesEntry:
description: ImageAliasesEntry represents a LXD image alias
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
description:
description: Alias description
example: Our preferred Ubuntu image
Expand Down Expand Up @@ -1151,6 +1160,15 @@ definitions:
ImageAliasesPost:
description: ImageAliasesPost represents a new LXD image alias
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
description:
description: Alias description
example: Our preferred Ubuntu image
Expand Down Expand Up @@ -1785,6 +1803,15 @@ 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 @@ -2254,6 +2281,15 @@ 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 @@ -5851,6 +5887,15 @@ definitions:
Server:
description: Server represents a LXD server
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
api_extensions:
description: List of supported API extensions
example:
Expand Down Expand Up @@ -6427,6 +6472,15 @@ 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 @@ -6682,6 +6736,15 @@ 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
14 changes: 13 additions & 1 deletion lxd/api_1.0.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,11 @@ func api10Get(d *Daemon, r *http.Request) response.Response {
return response.SyncResponseETag(true, srv, nil)
}

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

// If a target was specified, forward the request to the relevant node.
resp := forwardedResponseIfTargetIsRemote(s, r)
if resp != nil {
Expand Down Expand Up @@ -396,7 +401,7 @@ func api10Get(d *Daemon, r *http.Request) response.Response {

env.StorageSupportedDrivers = supportedStorageDrivers

fullSrv := api.Server{ServerUntrusted: srv}
fullSrv := &api.Server{ServerUntrusted: srv}
fullSrv.Environment = env
requestor := request.CreateRequestor(r)
fullSrv.AuthUserName = requestor.Username
Expand All @@ -413,6 +418,13 @@ func api10Get(d *Daemon, r *http.Request) response.Response {
}
}

if len(withEntitlements) > 0 {
err = reportEntitlements(r.Context(), s.Authorizer, s.IdentityCache, entity.TypeServer, withEntitlements, map[*api.URL]auth.EntitlementReporter{entity.ServerURL(): fullSrv})
if err != nil {
return response.SmartError(err)
}
}

return response.SyncResponseETag(true, fullSrv, fullSrv.Config)
}

Expand Down
10 changes: 6 additions & 4 deletions lxd/auth_groups.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,10 @@ func getAuthGroups(d *Daemon, r *http.Request) response.Response {
for _, permission := range authGroupPermissions {
authGroupPermissionsByGroupID[permission.GroupID] = append(authGroupPermissionsByGroupID[permission.GroupID], permission)
}

apiGroups := make([]api.AuthGroup, 0, len(groups))
// We need to allocate a slice of pointer to api.AuthGroup because
// these records will be modified in place by the reportEntitlements function.
// We'll then return a slice of api.AuthGroup as an API response.
apiGroups := make([]*api.AuthGroup, 0, len(groups))
urlToGroup := make(map[*api.URL]auth.EntitlementReporter, len(groups))
for _, group := range groups {
var apiPermissions []api.Permission
Expand Down Expand Up @@ -278,7 +280,7 @@ func getAuthGroups(d *Daemon, r *http.Request) response.Response {
}
}

group := api.AuthGroup{
group := &api.AuthGroup{
Name: group.Name,
Description: group.Description,
Permissions: apiPermissions,
Expand All @@ -287,7 +289,7 @@ func getAuthGroups(d *Daemon, r *http.Request) response.Response {
}

apiGroups = append(apiGroups, group)
urlToGroup[entity.AuthGroupURL(group.Name)] = &group
urlToGroup[entity.AuthGroupURL(group.Name)] = group
}

if len(withEntitlements) > 0 {
Expand Down
6 changes: 3 additions & 3 deletions lxd/certificates.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ func certificatesGet(d *Daemon, r *http.Request) response.Response {
}

if recursion {
var certResponses []api.Certificate
var certResponses []*api.Certificate
var baseCerts []dbCluster.Certificate
urlToCertificate := make(map[*api.URL]auth.EntitlementReporter)
var err error
Expand All @@ -159,7 +159,7 @@ func certificatesGet(d *Daemon, r *http.Request) response.Response {
return err
}

certResponses = make([]api.Certificate, 0, len(baseCerts))
certResponses = make([]*api.Certificate, 0, len(baseCerts))
for _, baseCert := range baseCerts {
if !userHasPermission(entity.CertificateURL(baseCert.Fingerprint)) {
continue
Expand All @@ -170,7 +170,7 @@ func certificatesGet(d *Daemon, r *http.Request) response.Response {
return err
}

certResponses = append(certResponses, *apiCert)
certResponses = append(certResponses, apiCert)
urlToCertificate[entity.CertificateURL(apiCert.Fingerprint)] = apiCert
}

Expand Down
6 changes: 3 additions & 3 deletions lxd/identities.go
Original file line number Diff line number Diff line change
Expand Up @@ -894,7 +894,7 @@ func getIdentities(authenticationMethod string) func(d *Daemon, r *http.Request)
}
}

apiIdentities := make([]api.Identity, 0, len(identities))
apiIdentities := make([]*api.Identity, 0, len(identities))
urlToIdentity := make(map[*api.URL]auth.EntitlementReporter, len(identities))
for _, id := range identities {
var certificate string
Expand All @@ -907,7 +907,7 @@ func getIdentities(authenticationMethod string) func(d *Daemon, r *http.Request)
certificate = metadata.Certificate
}

identity := api.Identity{
identity := &api.Identity{
AuthenticationMethod: string(id.AuthMethod),
Type: string(id.Type),
Identifier: id.Identifier,
Expand All @@ -917,7 +917,7 @@ func getIdentities(authenticationMethod string) func(d *Daemon, r *http.Request)
}

apiIdentities = append(apiIdentities, identity)
urlToIdentity[entity.IdentityURL(string(id.AuthMethod), id.Identifier)] = &identity
urlToIdentity[entity.IdentityURL(string(id.AuthMethod), id.Identifier)] = identity
}

if len(withEntitlements) > 0 {
Expand Down
37 changes: 32 additions & 5 deletions lxd/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -3628,9 +3628,14 @@ func imageAliasesGet(d *Daemon, r *http.Request) response.Response {

s := d.State()

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

projectName := request.ProjectParam(r)
var effectiveProjectName string
err := s.DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error {
err = s.DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error {
var err error
effectiveProjectName, err = projectutils.ImageProject(ctx, tx.Tx(), projectName)
return err
Expand All @@ -3646,15 +3651,16 @@ func imageAliasesGet(d *Daemon, r *http.Request) response.Response {
}

var responseStr []string
var responseMap []api.ImageAliasesEntry
var responseMap []*api.ImageAliasesEntry
urlToImageAlias := make(map[*api.URL]auth.EntitlementReporter)
err = s.DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error {
names, err := tx.GetImageAliases(ctx, projectName)
if err != nil {
return err
}

if recursion {
responseMap = make([]api.ImageAliasesEntry, 0, len(names))
responseMap = make([]*api.ImageAliasesEntry, 0, len(names))
} else {
responseStr = make([]string, 0, len(names))
}
Expand All @@ -3672,7 +3678,8 @@ func imageAliasesGet(d *Daemon, r *http.Request) response.Response {
continue
}

responseMap = append(responseMap, alias)
responseMap = append(responseMap, &alias)
urlToImageAlias[entity.ImageAliasURL(projectName, name)] = &alias
}
}

Expand All @@ -3686,6 +3693,13 @@ func imageAliasesGet(d *Daemon, r *http.Request) response.Response {
return response.SyncResponse(true, responseStr)
}

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

return response.SyncResponse(true, responseMap)
}

Expand Down Expand Up @@ -3779,6 +3793,12 @@ func imageAliasGet(d *Daemon, r *http.Request) response.Response {
}

s := d.State()

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

var effectiveProjectName string
err = s.DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error {
effectiveProjectName, err = projectutils.ImageProject(ctx, tx.Tx(), projectName)
Expand Down Expand Up @@ -3810,7 +3830,14 @@ func imageAliasGet(d *Daemon, r *http.Request) response.Response {
return response.NotFound(nil)
}

return response.SyncResponseETag(true, alias, alias)
if len(withEntitlements) > 0 {
err = reportEntitlements(r.Context(), s.Authorizer, s.IdentityCache, entity.TypeImageAlias, withEntitlements, map[*api.URL]auth.EntitlementReporter{entity.ImageAliasURL(projectName, name): &alias})
if err != nil {
return response.SmartError(err)
}
}

return response.SyncResponseETag(true, &alias, alias)
}

// swagger:operation DELETE /1.0/images/aliases/{name} images image_alias_delete
Expand Down
30 changes: 28 additions & 2 deletions lxd/instance_backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ 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 @@ -164,7 +169,7 @@ 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 @@ -188,13 +193,21 @@ 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 @@ -429,6 +442,11 @@ 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 @@ -454,7 +472,15 @@ func instanceBackupGet(d *Daemon, r *http.Request) response.Response {
return response.SmartError(err)
}

return response.SyncResponse(true, backup.Render())
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)
}

// swagger:operation POST /1.0/instances/{name}/backups/{backup} instances instance_backup_post
Expand Down
Loading

0 comments on commit e51283c

Please sign in to comment.