Skip to content

Commit 411dc2a

Browse files
authored
Merge pull request #1484 from bensmrs/main
Add function checks to scriptlet validation
2 parents ccf645c + 5b90271 commit 411dc2a

File tree

1 file changed

+80
-10
lines changed
  • internal/server/scriptlet/load

1 file changed

+80
-10
lines changed

internal/server/scriptlet/load/load.go

Lines changed: 80 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package load
33
import (
44
"fmt"
55
"slices"
6+
"sort"
67
"sync"
78

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

18-
// prefixAuthorization is the prefix used in Starlark for the Authorization scriptlet.
19-
const prefixAuthorization = "authorization"
19+
// nameAuthorization is the name used in Starlark for the Authorization scriptlet.
20+
const nameAuthorization = "authorization"
2021

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

37+
// validate validates a scriptlet by compiling it and checking the presence of required functions.
38+
func validate(compiler func(string, string) (*starlark.Program, error), programName string, src string, requiredFunctions map[string][]string) error {
39+
prog, err := compiler(programName, src)
40+
if err != nil {
41+
return err
42+
}
43+
44+
thread := &starlark.Thread{Name: programName}
45+
globals, err := prog.Init(thread, nil)
46+
if err != nil {
47+
return err
48+
}
49+
50+
globals.Freeze()
51+
52+
var notFound []string
53+
for funName, requiredArgs := range requiredFunctions {
54+
// The function is missing if its name is not found in the globals.
55+
funv := globals[funName]
56+
if funv == nil {
57+
notFound = append(notFound, funName)
58+
continue
59+
}
60+
61+
// The function is missing if its name is not bound to a function.
62+
fun, ok := funv.(*starlark.Function)
63+
if !ok {
64+
notFound = append(notFound, funName)
65+
}
66+
67+
// Get the function arguments.
68+
argc := fun.NumParams()
69+
var args []string
70+
for i := range argc {
71+
arg, _ := fun.Param(i)
72+
args = append(args, arg)
73+
}
74+
75+
// Return an error early if the function does not have the right arguments.
76+
match := len(args) == len(requiredArgs)
77+
if match {
78+
sort.Strings(args)
79+
sort.Strings(requiredArgs)
80+
for i := range args {
81+
if args[i] != requiredArgs[i] {
82+
match = false
83+
break
84+
}
85+
}
86+
}
87+
88+
if !match {
89+
return fmt.Errorf("The function %q defines arguments %q (expected: %q)", funName, args, requiredArgs)
90+
}
91+
}
92+
93+
switch len(notFound) {
94+
case 0:
95+
return nil
96+
case 1:
97+
return fmt.Errorf("The function %q is required but has not been found in the scriptlet", notFound[0])
98+
default:
99+
return fmt.Errorf("The functions %q are required but have not been found in the scriptlet", notFound)
100+
}
101+
}
102+
36103
var programsMu sync.Mutex
37104
var programs = make(map[string]*starlark.Program)
38105

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

90157
// InstancePlacementValidate validates the instance placement scriptlet.
91158
func InstancePlacementValidate(src string) error {
92-
_, err := InstancePlacementCompile(nameInstancePlacement, src)
93-
return err
159+
return validate(InstancePlacementCompile, nameInstancePlacement, src, map[string][]string{
160+
"instance_placement": {"request", "candidate_members"},
161+
})
94162
}
95163

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

132200
// QEMUValidate validates the QEMU scriptlet.
133201
func QEMUValidate(src string) error {
134-
_, err := QEMUCompile(prefixQEMU, src)
135-
return err
202+
return validate(QEMUCompile, prefixQEMU, src, map[string][]string{
203+
"qemu_hook": {"hook_name"},
204+
})
136205
}
137206

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

158227
// AuthorizationValidate validates the authorization scriptlet.
159228
func AuthorizationValidate(src string) error {
160-
_, err := AuthorizationCompile(prefixAuthorization, src)
161-
return err
229+
return validate(AuthorizationCompile, nameAuthorization, src, map[string][]string{
230+
"authorize": {"details", "object", "entitlement"},
231+
})
162232
}
163233

164234
// AuthorizationSet compiles the authorization scriptlet into memory for use with AuthorizationRun.
165235
// If empty src is provided the current program is deleted.
166236
func AuthorizationSet(src string) error {
167-
return set(AuthorizationCompile, prefixAuthorization, src)
237+
return set(AuthorizationCompile, nameAuthorization, src)
168238
}
169239

170240
// AuthorizationProgram returns the precompiled authorization scriptlet program.
171241
func AuthorizationProgram() (*starlark.Program, *starlark.Thread, error) {
172-
return program("Authorization", prefixAuthorization)
242+
return program("Authorization", nameAuthorization)
173243
}

0 commit comments

Comments
 (0)