Skip to content

Add function checks to scriptlet validation #1484

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 10, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 80 additions & 10 deletions internal/server/scriptlet/load/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package load
import (
"fmt"
"slices"
"sort"
"sync"

"go.starlark.net/starlark"
Expand All @@ -15,8 +16,8 @@ const nameInstancePlacement = "instance_placement"
// prefixQEMU is the prefix used in Starlark for the QEMU scriptlet.
const prefixQEMU = "qemu"

// prefixAuthorization is the prefix used in Starlark for the Authorization scriptlet.
const prefixAuthorization = "authorization"
// nameAuthorization is the name used in Starlark for the Authorization scriptlet.
const nameAuthorization = "authorization"

// compile compiles a scriptlet.
func compile(programName string, src string, preDeclared []string) (*starlark.Program, error) {
Expand All @@ -33,6 +34,72 @@ func compile(programName string, src string, preDeclared []string) (*starlark.Pr
return mod, nil
}

// validate validates a scriptlet by compiling it and checking the presence of required functions.
func validate(compiler func(string, string) (*starlark.Program, error), programName string, src string, requiredFunctions map[string][]string) error {
prog, err := compiler(programName, src)
if err != nil {
return err
}

thread := &starlark.Thread{Name: programName}
globals, err := prog.Init(thread, nil)
if err != nil {
return err
}

globals.Freeze()

var notFound []string
for funName, requiredArgs := range requiredFunctions {
// The function is missing if its name is not found in the globals.
funv := globals[funName]
if funv == nil {
notFound = append(notFound, funName)
continue
}

// The function is missing if its name is not bound to a function.
fun, ok := funv.(*starlark.Function)
if !ok {
notFound = append(notFound, funName)
}

// Get the function arguments.
argc := fun.NumParams()
var args []string
for i := range argc {
arg, _ := fun.Param(i)
args = append(args, arg)
}

// Return an error early if the function does not have the right arguments.
match := len(args) == len(requiredArgs)
if match {
sort.Strings(args)
sort.Strings(requiredArgs)
for i := range args {
if args[i] != requiredArgs[i] {
match = false
break
}
}
}

if !match {
return fmt.Errorf("The function %q defines arguments %q (expected: %q)", funName, args, requiredArgs)
}
}

switch len(notFound) {
case 0:
return nil
case 1:
return fmt.Errorf("The function %q is required but has not been found in the scriptlet", notFound[0])
default:
return fmt.Errorf("The functions %q are required but have not been found in the scriptlet", notFound)
}
}

var programsMu sync.Mutex
var programs = make(map[string]*starlark.Program)

Expand Down Expand Up @@ -89,8 +156,9 @@ func InstancePlacementCompile(name string, src string) (*starlark.Program, error

// InstancePlacementValidate validates the instance placement scriptlet.
func InstancePlacementValidate(src string) error {
_, err := InstancePlacementCompile(nameInstancePlacement, src)
return err
return validate(InstancePlacementCompile, nameInstancePlacement, src, map[string][]string{
"instance_placement": {"request", "candidate_members"},
})
}

// InstancePlacementSet compiles the instance placement scriptlet into memory for use with InstancePlacementRun.
Expand Down Expand Up @@ -131,8 +199,9 @@ func QEMUCompile(name string, src string) (*starlark.Program, error) {

// QEMUValidate validates the QEMU scriptlet.
func QEMUValidate(src string) error {
_, err := QEMUCompile(prefixQEMU, src)
return err
return validate(QEMUCompile, prefixQEMU, src, map[string][]string{
"qemu_hook": {"hook_name"},
})
}

// QEMUSet compiles the QEMU scriptlet into memory for use with QEMURun.
Expand All @@ -157,17 +226,18 @@ func AuthorizationCompile(name string, src string) (*starlark.Program, error) {

// AuthorizationValidate validates the authorization scriptlet.
func AuthorizationValidate(src string) error {
_, err := AuthorizationCompile(prefixAuthorization, src)
return err
return validate(AuthorizationCompile, nameAuthorization, src, map[string][]string{
"authorize": {"details", "object", "entitlement"},
})
}

// AuthorizationSet compiles the authorization scriptlet into memory for use with AuthorizationRun.
// If empty src is provided the current program is deleted.
func AuthorizationSet(src string) error {
return set(AuthorizationCompile, prefixAuthorization, src)
return set(AuthorizationCompile, nameAuthorization, src)
}

// AuthorizationProgram returns the precompiled authorization scriptlet program.
func AuthorizationProgram() (*starlark.Program, *starlark.Thread, error) {
return program("Authorization", prefixAuthorization)
return program("Authorization", nameAuthorization)
}
Loading