diff --git a/internal/server/auth/driver_scriptlet.go b/internal/server/auth/driver_scriptlet.go index cbc7dc2829..731990726c 100644 --- a/internal/server/auth/driver_scriptlet.go +++ b/internal/server/auth/driver_scriptlet.go @@ -26,7 +26,7 @@ func (s *Scriptlet) CheckPermission(ctx context.Context, r *http.Request, object return nil } - authorized, err := authScriptlet.AuthorizationRun(ctx, logger.Log, details, object.String(), string(entitlement)) + authorized, err := authScriptlet.AuthorizationRun(logger.Log, details, object.String(), string(entitlement)) if err != nil { return api.StatusErrorf(http.StatusForbidden, "Authorization scriptlet execution failed with error: %v", err) } @@ -40,7 +40,7 @@ func (s *Scriptlet) CheckPermission(ctx context.Context, r *http.Request, object // GetInstanceAccess returns the list of entities who have access to the instance. func (s *Scriptlet) GetInstanceAccess(ctx context.Context, projectName string, instanceName string) (*api.Access, error) { - return &api.Access{}, nil + return authScriptlet.GetInstanceAccessRun(logger.Log, projectName, instanceName) } // GetPermissionChecker returns a function that can be used to check whether a user has the required entitlement on an authorization object. @@ -61,7 +61,7 @@ func (s *Scriptlet) GetPermissionChecker(ctx context.Context, r *http.Request, e } permissionChecker := func(o Object) bool { - authorized, err := authScriptlet.AuthorizationRun(ctx, logger.Log, details, o.String(), string(entitlement)) + authorized, err := authScriptlet.AuthorizationRun(logger.Log, details, o.String(), string(entitlement)) if err != nil { logger.Error("Authorization scriptlet execution failed", logger.Ctx{"err": err}) return false @@ -75,7 +75,7 @@ func (s *Scriptlet) GetPermissionChecker(ctx context.Context, r *http.Request, e // GetProjectAccess returns the list of entities who have access to the project. func (s *Scriptlet) GetProjectAccess(ctx context.Context, projectName string) (*api.Access, error) { - return &api.Access{}, nil + return authScriptlet.GetProjectAccessRun(logger.Log, projectName) } func (s *Scriptlet) load(ctx context.Context, certificateCache *certificate.Cache, opts Opts) error { diff --git a/internal/server/scriptlet/auth/auth.go b/internal/server/scriptlet/auth/auth.go index 71bb2e6a18..577a8aead0 100644 --- a/internal/server/scriptlet/auth/auth.go +++ b/internal/server/scriptlet/auth/auth.go @@ -1,7 +1,6 @@ package auth import ( - "context" "fmt" "go.starlark.net/starlark" @@ -10,11 +9,12 @@ import ( scriptletLoad "github.com/lxc/incus/v6/internal/server/scriptlet/load" "github.com/lxc/incus/v6/internal/server/scriptlet/log" "github.com/lxc/incus/v6/internal/server/scriptlet/marshal" + "github.com/lxc/incus/v6/shared/api" "github.com/lxc/incus/v6/shared/logger" ) // AuthorizationRun runs the authorization scriptlet. -func AuthorizationRun(ctx context.Context, l logger.Logger, details *common.RequestDetails, object string, entitlement string) (bool, error) { +func AuthorizationRun(l logger.Logger, details *common.RequestDetails, object string, entitlement string) (bool, error) { logFunc := log.CreateLogger(l, "Authorization scriptlet") // Remember to match the entries in scriptletLoad.AuthorizationCompile() with this list so Starlark can @@ -71,3 +71,89 @@ func AuthorizationRun(ctx context.Context, l logger.Logger, details *common.Requ return bool(v.(starlark.Bool)), nil } + +func getAccess(l logger.Logger, fun string, args []starlark.Tuple) (*api.Access, error) { + access := &api.Access{} + emptyAccess := &api.Access{} + logFunc := log.CreateLogger(l, fmt.Sprintf("Authorization scriptlet (%s)", fun)) + + // Remember to match the entries in scriptletLoad.AuthorizationCompile() with this list so Starlark can + // perform compile time validation of functions used. + env := starlark.StringDict{ + "log_info": starlark.NewBuiltin("log_info", logFunc), + "log_warn": starlark.NewBuiltin("log_warn", logFunc), + "log_error": starlark.NewBuiltin("log_error", logFunc), + } + + prog, thread, err := scriptletLoad.AuthorizationProgram() + if err != nil { + return emptyAccess, err + } + + globals, err := prog.Init(thread, env) + if err != nil { + return emptyAccess, fmt.Errorf("Failed initializing: %w", err) + } + + globals.Freeze() + + // Retrieve a global variable from starlark environment. + getter := globals[fun] + if getter == nil { + return emptyAccess, nil + } + + // Call starlark function from Go. + v, err := starlark.Call(thread, getter, nil, args) + if err != nil { + return emptyAccess, fmt.Errorf("Failed to run: %w", err) + } + + value, err := marshal.StarlarkUnmarshal(v) + if err != nil { + return emptyAccess, err + } + + identifiers, ok := value.([]any) + if !ok { + return emptyAccess, fmt.Errorf("Failed with unexpected return value: %v", v) + } + + for _, id := range identifiers { + identifier, ok := id.(string) + if !ok { + return emptyAccess, fmt.Errorf("Failed with unexpected return value: %v", v) + } + + *access = append(*access, api.AccessEntry{ + Identifier: identifier, + Role: "unknown", + Provider: "scriptlet", + }) + } + + return access, nil +} + +// GetInstanceAccessRun runs the optional get_instance_access scriptlet function. +func GetInstanceAccessRun(l logger.Logger, projectName string, instanceName string) (*api.Access, error) { + return getAccess(l, "get_instance_access", []starlark.Tuple{ + { + starlark.String("project_name"), + starlark.String(projectName), + }, { + starlark.String("instance_name"), + starlark.String(instanceName), + }, + }) +} + +// GetProjectAccessRun runs the optional get_project_access scriptlet function. +func GetProjectAccessRun(l logger.Logger, projectName string) (*api.Access, error) { + return getAccess(l, "get_project_access", []starlark.Tuple{ + { + starlark.String("project_name"), + starlark.String(projectName), + }, + }) +}