Skip to content

Commit 889b406

Browse files
committed
sprintf improvements and test Kubescape regolib
1 parent a4eb82d commit 889b406

File tree

5 files changed

+486
-5
lines changed

5 files changed

+486
-5
lines changed

core/src/main/java/com/styra/opa/wasm/OpaWasm.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.util.ArrayList;
1313
import java.util.HashMap;
1414
import java.util.List;
15+
import java.util.Map;
1516

1617
// Low level bindings to OPA
1718
@WasmModuleInterface("demo-policy.wasm")
@@ -71,26 +72,37 @@ public OpaBuiltin.Builtin[] initializeBuiltins(
7172
throw new RuntimeException(e);
7273
}
7374

74-
var result = new OpaBuiltin.Builtin[mappings.size()];
75+
Map<Integer, OpaBuiltin.Builtin> result = new HashMap<>();
7576
// Default initialization to have proper error messages
7677
for (var m : mappings.entrySet()) {
77-
result[m.getValue()] = () -> m.getKey();
78+
result.put(m.getValue(), () -> m.getKey());
7879
}
7980
for (var builtin : builtins) {
8081
if (mappings.containsKey(builtin.name())) {
81-
result[mappings.get(builtin.name())] = builtin;
82+
result.put(mappings.get(builtin.name()), builtin);
8283
}
8384
}
8485
if (defaultBuiltins) {
8586
// provided builtins override anything with clashing name
8687
var all = Provided.all();
8788
for (var builtin : all) {
8889
if (mappings.containsKey(builtin.name())) {
89-
result[mappings.get(builtin.name())] = builtin;
90+
result.put(mappings.get(builtin.name()), builtin);
9091
}
9192
}
9293
}
93-
return result;
94+
95+
int size = result.keySet().stream().mapToInt(i -> i + 1).max().orElse(0);
96+
var finalResult = new OpaBuiltin.Builtin[size];
97+
for (int i = 0; i < size; i++) {
98+
if (result.containsKey(i)) {
99+
finalResult[i] = result.get(i);
100+
} else {
101+
finalResult[i] = null;
102+
}
103+
}
104+
105+
return finalResult;
94106
}
95107

96108
public static class Builder {

core/src/main/java/com/styra/opa/wasm/builtins/String.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ private static JsonNode sprintfImpl(OpaWasm instance, JsonNode operand0, JsonNod
6060

6161
// Apply formatting using String.format
6262
try {
63+
// %v partial support
64+
if (format.contains("%v")) {
65+
format = format.replaceAll("%v", "%s");
66+
}
67+
6368
var result = java.lang.String.format(format, args.toArray());
6469
return TextNode.valueOf(result);
6570
} catch (IllegalArgumentException e) {

core/src/test/java/com/styra/opa/wasm/OpaTest.java

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,86 @@ public void issue69() throws Exception {
105105
policy.input("{\"method\":\"POST\"}");
106106
Assertions.assertFalse(Utils.getResult(policy.evaluate()).asBoolean());
107107
}
108+
109+
@Test
110+
public void issue78Sprintf() throws Exception {
111+
var policyWasm =
112+
OpaCli.compile("issue78-sprintf", "armo_builtins/deny").resolve("policy.wasm");
113+
var policy = OpaPolicy.builder().withPolicy(policyWasm).build();
114+
var pod =
115+
"apiVersion: v1\n"
116+
+ "kind: Pod\n"
117+
+ "metadata:\n"
118+
+ " name: static-web\n"
119+
+ " labels:\n"
120+
+ " role: myrole\n"
121+
+ "spec:\n"
122+
+ " securityContext:\n"
123+
+ " runAsNonRoot: false\n"
124+
+ " containers:\n"
125+
+ " - name: web\n"
126+
+ " image: nginx\n"
127+
+ " ports:\n"
128+
+ " - name: web\n"
129+
+ " containerPort: 80\n"
130+
+ " protocol: TCP\n"
131+
+ " securityContext:\n"
132+
+ " runAsGroup: 1000\n";
133+
134+
// should be an array of Pods
135+
var rawInput = DefaultMappers.jsonMapper.createArrayNode();
136+
rawInput.add(DefaultMappers.yamlMapper.readTree(pod));
137+
138+
var input = DefaultMappers.jsonMapper.writeValueAsString(rawInput);
139+
140+
var result = policy.evaluate(input);
141+
142+
var resultNode = Utils.getResult(result).get(0);
143+
var alertMessage = resultNode.get("alertMessage").asText();
144+
var alertScore = resultNode.get("alertScore").asInt();
145+
146+
assertEquals("container: web in pod: static-web may run as root", alertMessage);
147+
assertEquals(7, alertScore);
148+
}
149+
150+
@Test
151+
public void issue78Updated() throws Exception {
152+
var policyWasm =
153+
OpaCli.compile("issue78-updated", "armo_builtins/deny").resolve("policy.wasm");
154+
var policy = OpaPolicy.builder().withPolicy(policyWasm).build();
155+
var pod =
156+
"apiVersion: v1\n"
157+
+ "kind: Pod\n"
158+
+ "metadata:\n"
159+
+ " name: static-web\n"
160+
+ " labels:\n"
161+
+ " role: myrole\n"
162+
+ "spec:\n"
163+
+ " securityContext:\n"
164+
+ " runAsNonRoot: false\n"
165+
+ " containers:\n"
166+
+ " - name: web\n"
167+
+ " image: nginx\n"
168+
+ " ports:\n"
169+
+ " - name: web\n"
170+
+ " containerPort: 80\n"
171+
+ " protocol: TCP\n"
172+
+ " securityContext:\n"
173+
+ " runAsGroup: 1000";
174+
175+
// should be an array of Pods
176+
var rawInput = DefaultMappers.jsonMapper.createArrayNode();
177+
rawInput.add(DefaultMappers.yamlMapper.readTree(pod));
178+
179+
var input = DefaultMappers.jsonMapper.writeValueAsString(rawInput);
180+
181+
var result = policy.evaluate(input);
182+
183+
var resultNode = Utils.getResult(result).get(0);
184+
var alertMessage = resultNode.get("alertMessage").asText();
185+
var alertScore = resultNode.get("alertScore").asInt();
186+
187+
assertEquals("container: web in pod: static-web may run as root", alertMessage);
188+
assertEquals(7, alertScore);
189+
}
108190
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package armo_builtins
2+
3+
4+
################################################################################
5+
# Rules
6+
deny[msga] {
7+
pod := input[_]
8+
pod.kind == "Pod"
9+
container := pod.spec.containers[i]
10+
11+
start_of_path := "spec"
12+
run_as_user_fixpath := evaluate_workload_run_as_user(container, pod, start_of_path)
13+
run_as_group_fixpath := evaluate_workload_run_as_group(container, pod, start_of_path)
14+
all_fixpaths := array.concat(run_as_user_fixpath, run_as_group_fixpath)
15+
count(all_fixpaths) > 0
16+
fixPaths := get_fixed_paths(all_fixpaths, i)
17+
18+
msga := {
19+
"alertMessage": sprintf("container: %v in pod: %v may run as root", [container.name, pod.metadata.name]),
20+
"packagename": "armo_builtins",
21+
"alertScore": 7,
22+
"reviewPaths": [],
23+
"failedPaths": [],
24+
"fixPaths": fixPaths,
25+
"alertObject": {
26+
"k8sApiObjects": [pod]
27+
}
28+
}
29+
}
30+
31+
32+
deny[msga] {
33+
wl := input[_]
34+
spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"}
35+
spec_template_spec_patterns[wl.kind]
36+
container := wl.spec.template.spec.containers[i]
37+
38+
start_of_path := "spec.template.spec"
39+
run_as_user_fixpath := evaluate_workload_run_as_user(container, wl.spec.template, start_of_path)
40+
run_as_group_fixpath := evaluate_workload_run_as_group(container, wl.spec.template, start_of_path)
41+
all_fixpaths := array.concat(run_as_user_fixpath, run_as_group_fixpath)
42+
count(all_fixpaths) > 0
43+
fixPaths := get_fixed_paths(all_fixpaths, i)
44+
45+
msga := {
46+
"alertMessage": sprintf("container: %v in %v: %v may run as root", [container.name, wl.kind, wl.metadata.name]),
47+
"packagename": "armo_builtins",
48+
"alertScore": 7,
49+
"reviewPaths": [],
50+
"failedPaths": [],
51+
"fixPaths": fixPaths,
52+
"alertObject": {
53+
"k8sApiObjects": [wl]
54+
}
55+
}
56+
}
57+
58+
# Fails if cronjob has a container configured to run as root
59+
deny[msga] {
60+
wl := input[_]
61+
wl.kind == "CronJob"
62+
container = wl.spec.jobTemplate.spec.template.spec.containers[i]
63+
64+
start_of_path := "spec.jobTemplate.spec.template.spec"
65+
run_as_user_fixpath := evaluate_workload_run_as_user(container, wl.spec.jobTemplate.spec.template, start_of_path)
66+
run_as_group_fixpath := evaluate_workload_run_as_group(container, wl.spec.jobTemplate.spec.template, start_of_path)
67+
all_fixpaths := array.concat(run_as_user_fixpath, run_as_group_fixpath)
68+
count(all_fixpaths) > 0
69+
fixPaths := get_fixed_paths(all_fixpaths, i)
70+
71+
72+
msga := {
73+
"alertMessage": sprintf("container: %v in %v: %v may run as root", [container.name, wl.kind, wl.metadata.name]),
74+
"packagename": "armo_builtins",
75+
"alertScore": 7,
76+
"reviewPaths": [],
77+
"failedPaths": [],
78+
"fixPaths": fixPaths,
79+
"alertObject": {
80+
"k8sApiObjects": [wl]
81+
}
82+
}
83+
}
84+
85+
86+
get_fixed_paths(all_fixpaths, i) = [{"path":replace(all_fixpaths[0].path,"container_ndx",format_int(i,10)), "value":all_fixpaths[0].value}, {"path":replace(all_fixpaths[1].path,"container_ndx",format_int(i,10)), "value":all_fixpaths[1].value}]{
87+
count(all_fixpaths) == 2
88+
} else = [{"path":replace(all_fixpaths[0].path,"container_ndx",format_int(i,10)), "value":all_fixpaths[0].value}]
89+
90+
#################################################################################
91+
# Workload evaluation
92+
93+
# if runAsUser is set to 0 and runAsNonRoot is set to false/ not set - suggest to set runAsUser to 1000
94+
# if runAsUser is not set and runAsNonRoot is set to false/ not set - suggest to set runAsNonRoot to true
95+
# all checks are both on the pod and the container level
96+
evaluate_workload_run_as_user(container, pod, start_of_path) = fixPath {
97+
runAsNonRootValue := get_run_as_non_root_value(container, pod, start_of_path)
98+
runAsNonRootValue.value == false
99+
100+
runAsUserValue := get_run_as_user_value(container, pod, start_of_path)
101+
runAsUserValue.value == 0
102+
103+
alertInfo := choose_first_if_defined(runAsUserValue, runAsNonRootValue)
104+
fixPath := alertInfo.fixPath
105+
} else = []
106+
107+
108+
# if runAsGroup is set to 0/ not set - suggest to set runAsGroup to 1000
109+
# all checks are both on the pod and the container level
110+
evaluate_workload_run_as_group(container, pod, start_of_path) = fixPath {
111+
runAsGroupValue := get_run_as_group_value(container, pod, start_of_path)
112+
runAsGroupValue.value == 0
113+
114+
fixPath := runAsGroupValue.fixPath
115+
} else = []
116+
117+
118+
#################################################################################
119+
# Value resolution functions
120+
121+
122+
get_run_as_non_root_value(container, pod, start_of_path) = runAsNonRoot {
123+
runAsNonRoot := {"value" : container.securityContext.runAsNonRoot, "fixPath": [{"path": sprintf("%v.containers[container_ndx].securityContext.runAsNonRoot", [start_of_path]), "value":"true"}], "defined" : true}
124+
} else = runAsNonRoot {
125+
runAsNonRoot := {"value" : pod.spec.securityContext.runAsNonRoot, "fixPath": [{"path": sprintf("%v.containers[container_ndx].securityContext.runAsNonRoot", [start_of_path]), "value":"true"}], "defined" : true}
126+
} else = {"value" : false, "fixPath": [{"path": sprintf("%v.containers[container_ndx].securityContext.runAsNonRoot", [start_of_path]) , "value":"true"}], "defined" : false}
127+
128+
get_run_as_user_value(container, pod, start_of_path) = runAsUser {
129+
path := sprintf("%v.containers[container_ndx].securityContext.runAsUser", [start_of_path])
130+
runAsUser := {"value" : container.securityContext.runAsUser, "fixPath": [{"path": path, "value": "1000"}], "defined" : true}
131+
} else = runAsUser {
132+
path := sprintf("%v.securityContext.runAsUser", [start_of_path])
133+
runAsUser := {"value" : pod.spec.securityContext.runAsUser, "fixPath": [{"path": path, "value": "1000"}],"defined" : true}
134+
} else = {"value" : 0, "fixPath": [{"path": sprintf("%v.containers[container_ndx].securityContext.runAsNonRoot", [start_of_path]), "value":"true"}],
135+
"defined" : false}
136+
137+
get_run_as_group_value(container, pod, start_of_path) = runAsGroup {
138+
path := sprintf("%v.containers[container_ndx].securityContext.runAsGroup", [start_of_path])
139+
runAsGroup := {"value" : container.securityContext.runAsGroup, "fixPath": [{"path": path, "value": "1000"}],"defined" : true}
140+
} else = runAsGroup {
141+
path := sprintf("%v.securityContext.runAsGroup", [start_of_path])
142+
runAsGroup := {"value" : pod.spec.securityContext.runAsGroup, "fixPath":[{"path": path, "value": "1000"}], "defined" : true}
143+
} else = {"value" : 0, "fixPath": [{"path": sprintf("%v.containers[container_ndx].securityContext.runAsGroup", [start_of_path]), "value":"1000"}],
144+
"defined" : false
145+
}
146+
147+
choose_first_if_defined(l1, l2) = c {
148+
l1.defined
149+
c := l1
150+
} else = l2

0 commit comments

Comments
 (0)