Skip to content

Commit

Permalink
lxd/auth: Add GetEntitlements and GetEntitlementsToEntities to th…
Browse files Browse the repository at this point in the history
…e `Authorizer` interface

These methods return the entitlements corresponding to the entity/entities through calls to the
OpenFGA datastore. These functions should be called at the end of a LXD API handler so that the
OpenFGA per-request cache can be hit.

Signed-off-by: Gabriel Mougard <[email protected]>
  • Loading branch information
gabrielmougard committed Jan 8, 2025
1 parent 243c70b commit 929e682
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 0 deletions.
71 changes: 71 additions & 0 deletions lxd/auth/drivers/openfga.go
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,77 @@ func (e *embeddedOpenFGA) GetPermissionChecker(ctx context.Context, entitlement
}, nil
}

// GetEntitlements gets entitlements to an entity using the embedded OpenFGA server.
func (e *embeddedOpenFGA) GetEntitlements(ctx context.Context, entityType entity.Type, entityURL *api.URL, queriedEntitlementCandidates []auth.Entitlement) ([]string, error) {
id, err := auth.GetIdentityFromCtx(ctx, e.identityCache)
if err != nil {
return nil, fmt.Errorf("Failed to get caller identity: %w", err)
}

if !identity.IsFineGrainedIdentityType(id.IdentityType) {
return e.tlsAuthorizer.GetEntitlements(ctx, entityType, entityURL, queriedEntitlementCandidates)
}

entitlements := make([]string, 0)
for _, entitlement := range queriedEntitlementCandidates {
err = e.CheckPermission(ctx, entityURL, entitlement)
if err != nil {
if err == api.NewGenericStatusError(http.StatusNotFound) || err == api.NewGenericStatusError(http.StatusForbidden) {
continue
}

return nil, fmt.Errorf("Failed to add entitlements %q for entity URL %q: %w", entitlement, entityURL.String(), err)
}

entitlements = append(entitlements, string(entitlement))
}

return entitlements, nil
}

// GetEntitlementsToEntities adds entitlements to multiple entities using the embedded OpenFGA server.
func (e *embeddedOpenFGA) GetEntitlementsToEntities(ctx context.Context, entityType entity.Type, entityURLs []*api.URL, queriedEntitlementCandidates []auth.Entitlement) (map[string][]string, error) {
if len(entityURLs) == 0 {
return nil, nil
}

id, err := auth.GetIdentityFromCtx(ctx, e.identityCache)
if err != nil {
return nil, fmt.Errorf("Failed to get caller identity: %w", err)
}

if !identity.IsFineGrainedIdentityType(id.IdentityType) {
return e.tlsAuthorizer.GetEntitlementsToEntities(ctx, entityType, entityURLs, queriedEntitlementCandidates)
}

checkersByEntitlement := make(map[auth.Entitlement]auth.PermissionChecker)
for _, entitlement := range queriedEntitlementCandidates {
checker, err := e.GetPermissionChecker(ctx, entitlement, entityType)
if err != nil {
return nil, fmt.Errorf("Failed to get a permission checker for entitlement %q and for entity type %q: %w", entitlement, entityType, err)
}

checkersByEntitlement[entitlement] = checker
}

entities := make(map[string][]string)
for entitlement, checker := range checkersByEntitlement {
for _, url := range entityURLs {
if checker(url) {
urlStr := url.String()
_, ok := entities[urlStr]
if !ok {
entities[urlStr] = make([]string, 0)
}

entities[urlStr] = append(entities[urlStr], string(entitlement))
}
}
}

return entities, nil
}

// openfgaLogger implements OpenFGAs logger.Logger interface but delegates to our logger.
type openfgaLogger struct {
l logger.Logger
Expand Down
10 changes: 10 additions & 0 deletions lxd/auth/drivers/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,16 @@ func (t *tls) GetPermissionChecker(ctx context.Context, entitlement auth.Entitle
}, nil
}

// GetEntitlements is not supported by the TLS authorization driver.
func (t *tls) GetEntitlements(ctx context.Context, entityType entity.Type, entityURL *api.URL, queriedEntitlementCandidates []auth.Entitlement) ([]string, error) {
return nil, api.StatusErrorf(http.StatusNotImplemented, "Getting entitlements to an entity is not supported by the TLS authorization driver")
}

// GetEntitlementsToEntities is not supported by the TLS authorization driver.
func (t *tls) GetEntitlementsToEntities(ctx context.Context, entityType entity.Type, entityURLs []*api.URL, queriedEntitlementCandidates []auth.Entitlement) (map[string][]string, error) {
return nil, api.StatusErrorf(http.StatusNotImplemented, "Getting entitlements to a list of entities is not supported by the TLS authorization driver")
}

func (t *tls) allowProjectUnspecificEntityType(entitlement auth.Entitlement, entityType entity.Type, id *identity.CacheEntry, projectName string, pathArguments []string) bool {
switch entityType {
case entity.TypeServer:
Expand Down
6 changes: 6 additions & 0 deletions lxd/auth/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ type Authorizer interface {
// the entity. The effective project for the entity must be set in the request context as request.CtxEffectiveProjectName
// *before* the call to GetPermissionChecker.
GetPermissionChecker(ctx context.Context, entitlement Entitlement, entityType entity.Type) (PermissionChecker, error)

// GetEntitlements gets entitlements for an entity from a list of entitlement candidates.
GetEntitlements(ctx context.Context, entityType entity.Type, entityURL *api.URL, queriedEntitlementCandidates []Entitlement) ([]string, error)

// GetEntitlementsToEntities returns the entitlements for multiple entity URLs and types given a list of entitlement candidates.
GetEntitlementsToEntities(ctx context.Context, entityType entity.Type, entityURLs []*api.URL, queriedEntitlementCandidates []Entitlement) (map[string][]string, error)
}

// IsDeniedError returns true if the error is not found or forbidden. This is because the CheckPermission method on
Expand Down

0 comments on commit 929e682

Please sign in to comment.