Skip to content

Commit 371978f

Browse files
committed
feat: add support for return values
1 parent 94dfc8b commit 371978f

File tree

6 files changed

+122
-41
lines changed

6 files changed

+122
-41
lines changed

eval/cmdeval.go

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -54,53 +54,66 @@ func (cv commandEvaluate) EvalCommandPolicy(commands []string, evalExpr string,
5454
if err != nil {
5555
return CmdEvalResult{Match: false}
5656
}
57-
val, err := cv.evalPolicy(commands, cmdExec, evalExpr, policy, pep.EvalParamNum, []string{pep.PolicyQueryParam}...)
57+
val, err := cv.evalPolicy(commands, cmdExec, evalExpr, policy, pep.EvalParamNum, []string{pep.PolicyQueryParam}, pep.ReturnKeys)
5858
if err != nil {
5959
return CmdEvalResult{Match: false, Error: err}
6060
}
6161
return CmdEvalResult{Match: val.EvalExpResult == 0, Error: err, PolicyResult: val.PolicyResult}
6262
}
6363

64-
func (cv commandEvaluate) evalPolicy(commands []string, cmdExec cmd, evalExpr string, policy string, compareComm int, propertyEval ...string) (FinalResult, error) {
65-
resMap := make(map[int][]string)
66-
cmdTotalRes := make([]string, 0)
67-
var commNum = 0
68-
for index := range commands {
69-
res := cmdExec.execCommand(index, cmdTotalRes, make([]IndexValue, 0), evalExpr)
70-
sb := strings.Builder{}
71-
for _, s := range res {
72-
sb.WriteString(s)
73-
}
74-
resMap[commNum] = res
75-
cmdTotalRes = append(cmdTotalRes, sb.String())
76-
commNum++
77-
}
78-
policyEvalResults := make([]*validator.ValidateResult, 0)
64+
func (cv commandEvaluate) evalPolicy(commands []string, cmdExec cmd, evalExpr string, policy string, compareComm int, propertyEval []string, ReturnFields []string) (*FinalResult, error) {
65+
resMap, cmdTotalRes := cv.ExecCommands(commands, cmdExec, evalExpr)
66+
policyEvalResults := make([]utils.PolicyResult, 0)
7967
var policyRes int
8068
if val, ok := resMap[compareComm]; ok {
8169
for _, cmdRes := range val {
8270
res, err := validator.NewPolicyEval().EvaluatePolicy(propertyEval, policy, cmdRes)
8371
if err != nil {
84-
res = []*validator.ValidateResult{{Value: false}}
72+
return nil, err
8573
}
86-
policyEvalResults = append(policyEvalResults, res...)
74+
policyResult := utils.MatchPolicy(res[0].ExpressionValue[0].Value, ReturnFields)
75+
policyEvalResults = append(policyEvalResults, policyResult)
8776
}
8877
for _, per := range policyEvalResults {
89-
if !per.Value {
90-
policyRes = 1
91-
break
78+
if returnVal, ok := per.ReturnValues["allow"]; ok {
79+
val, err := strconv.ParseBool(returnVal)
80+
if err != nil {
81+
continue
82+
}
83+
if !val {
84+
policyRes = 1
85+
break
86+
}
9287
}
9388
}
9489
}
9590
match := policyRes == 0
9691
policyExpr := utils.GetPolicyExpr(evalExpr)
9792
if len(policyExpr) == len(evalExpr) {
98-
return FinalResult{EvalExpResult: policyRes, PolicyResult: policyEvalResults}, nil
93+
return &FinalResult{EvalExpResult: policyRes, PolicyResult: policyEvalResults}, nil
9994
}
10095
neweEvalExpr := strings.Replace(evalExpr, policyExpr, fmt.Sprintf("'true' == '%s'", strconv.FormatBool(match)), -1)
10196
evalExpResult, err := cmdExec.evalExpression(cmdTotalRes, len(cmdTotalRes), make([]string, 0), 0, neweEvalExpr)
102-
return FinalResult{EvalExpResult: evalExpResult, PolicyResult: policyEvalResults}, err
97+
return &FinalResult{EvalExpResult: evalExpResult, PolicyResult: policyEvalResults}, err
98+
99+
}
103100

101+
//ExecCommands execute shell commands and encapsulate it results
102+
func (cv commandEvaluate) ExecCommands(commands []string, cmdExec cmd, evalExpr string) (map[int][]string, []string) {
103+
resMap := make(map[int][]string)
104+
cmdTotalRes := make([]string, 0)
105+
var commNum = 0
106+
for index := range commands {
107+
res := cmdExec.execCommand(index, cmdTotalRes, make([]IndexValue, 0), evalExpr)
108+
sb := strings.Builder{}
109+
for _, s := range res {
110+
sb.WriteString(s)
111+
}
112+
resMap[commNum] = res
113+
cmdTotalRes = append(cmdTotalRes, sb.String())
114+
commNum++
115+
}
116+
return resMap, cmdTotalRes
104117
}
105118

106119
func (cv commandEvaluate) evalCommand(commands []string, cmdExec cmd, evalExpr string) (int, error) {
@@ -121,12 +134,12 @@ func (cv commandEvaluate) evalCommand(commands []string, cmdExec cmd, evalExpr s
121134
type CmdEvalResult struct {
122135
Match bool
123136
CmdEvalExpr string
124-
PolicyResult []*validator.ValidateResult
137+
PolicyResult []utils.PolicyResult
125138
Error error
126139
}
127140

128141
//FinalResult eval result object
129142
type FinalResult struct {
130143
EvalExpResult int
131-
PolicyResult []*validator.ValidateResult
144+
PolicyResult []utils.PolicyResult
132145
}

eval/cmdeval_test.go

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,21 @@ func TestEvalCommand(t *testing.T) {
3232
}
3333
}
3434

35-
const policy = `package example
36-
default deny = false
37-
deny {
35+
const NotAllowPolicy = `package itsio
36+
policy_eval :={"name":namespace_name,"allow":allow_policy} {
37+
namespace_name:= input.metadata.namespace
38+
input.kind == "Pod"
3839
some i
39-
input.kind == "Pod"
40-
image := input.spec.containers[i].image
41-
not startswith(image, "kalpine")
42-
}`
40+
allow_policy := input.spec.containers[i].imagePullPolicy == "Always"
41+
}
42+
`
43+
44+
const AllowPolicy = `package itsio
45+
policy_eval :={"name":namespace_name,"allow":allow_policy} {
46+
namespace_name:= input.metadata.namespace
47+
allow_policy := namespace_name == "default"
48+
}
49+
`
4350

4451
func TestEvalPolicy(t *testing.T) {
4552
res := NewEvalCmd()
@@ -50,12 +57,12 @@ func TestEvalPolicy(t *testing.T) {
5057
policy string
5158
want bool
5259
}{
53-
{name: "two command and deny policy match", evalExpr: "'${0}' != '';&& [${1} MATCH no_permission.policy QUERY example.deny]", cmd: []string{"kubectl get pods --no-headers -o custom-columns=\":metadata.name\"",
60+
{name: "two command and deny policy match", evalExpr: "'${0}' != '';&& [${1} MATCH no_permission.policy QUERY itsio.policy_eval RETURN allow,name]", cmd: []string{"kubectl get pods --no-headers -o custom-columns=\":metadata.name\"",
5461
"kubectl get pod ${0} -o json"},
55-
policy: policy, want: true},
56-
{name: "two command and deny policy expr not match", evalExpr: "'${0}' == '';&& [${1} MATCH no_permission.policy QUERY example.deny]", cmd: []string{"kubectl get pods --no-headers -o custom-columns=\":metadata.name\"",
62+
policy: AllowPolicy, want: true},
63+
{name: "two command and deny policy expr not match", evalExpr: "'${0}' == '';&& [${1} MATCH no_permission.policy QUERY itsio.policy_eval RETURN allow,name]", cmd: []string{"kubectl get pods --no-headers -o custom-columns=\":metadata.name\"",
5764
"kubectl get pod ${0} -o json"},
58-
policy: policy, want: false},
65+
policy: NotAllowPolicy, want: false},
5966
}
6067
for _, tt := range tests {
6168
t.Run(tt.name, func(t *testing.T) {

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ module github.com/chen-keinan/go-command-eval
22

33
go 1.16
44

5-
replace github.com/chen-keinan/go-opa-validate => github.com/chen-keinan/go-opa-validate v0.0.5
5+
replace github.com/chen-keinan/go-opa-validate => github.com/chen-keinan/go-opa-validate v0.0.6
66

77
require (
88
github.com/Knetic/govaluate v3.0.0+incompatible

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf
6969
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
7070
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
7171
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
72-
github.com/chen-keinan/go-opa-validate v0.0.5 h1:vweuJCpR+PfqhwYnt0mGMfx5NqH3bUUSN6L4/V4YyQw=
73-
github.com/chen-keinan/go-opa-validate v0.0.5/go.mod h1:De/1llRdXYZY2XHDiSgJNUH1449Ha1G3OpL/ouyrBm4=
72+
github.com/chen-keinan/go-opa-validate v0.0.6 h1:EJ7y+KoOGd/WLLGKwil0vtDKvIt5yPTSnz1I9fbaZxo=
73+
github.com/chen-keinan/go-opa-validate v0.0.6/go.mod h1:ntu4OB8A6Ni3oyAdiWfwqUBTU8J2gS9K0HC2dk7n0gU=
7474
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
7575
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
7676
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=

utils/stringutil.go

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,9 @@ func ReadPolicyExpr(policyExpr string) (*PolicyEvalParams, error) {
179179
var err error
180180
queryArgs := strings.Split(args[1], "QUERY")
181181
if len(queryArgs) == 2 {
182-
pep.PolicyQueryParam = strings.TrimSpace(queryArgs[1])
182+
queryArgsReturn := strings.Split(queryArgs[1], "RETURN")
183+
pep.PolicyQueryParam = strings.TrimSpace(queryArgsReturn[0])
184+
pep.ReturnKeys = strings.Split(queryArgsReturn[1], ",")
183185
}
184186
pep.PolicyName = strings.TrimSpace(queryArgs[0])
185187
param := strings.TrimSpace(args[0])
@@ -210,4 +212,39 @@ type PolicyEvalParams struct {
210212
PolicyName string
211213
PolicyQueryParam string
212214
EvalParamNum int
215+
ReturnKeys []string
216+
}
217+
218+
//MatchPolicy match policies results against expected return fields
219+
func MatchPolicy(evalResult interface{}, returnKeys []string) PolicyResult {
220+
switch t := evalResult.(type) {
221+
case bool:
222+
boolValue := strconv.FormatBool(t)
223+
return PolicyResult{ReturnValues: map[string]string{"allow": boolValue}}
224+
case map[string]interface{}:
225+
pr := PolicyResult{ReturnValues: make(map[string]string)}
226+
for _, rv := range returnKeys {
227+
key := strings.TrimSpace(rv)
228+
if key == "allow" {
229+
b, ok := t[key].(bool)
230+
if ok {
231+
boolValue := strconv.FormatBool(b)
232+
pr.ReturnValues[key] = boolValue
233+
}
234+
continue
235+
}
236+
s, ok := t[key].(string)
237+
if ok {
238+
pr.ReturnValues[key] = s
239+
}
240+
}
241+
return pr
242+
default:
243+
return PolicyResult{ReturnValues: map[string]string{"allow": "false"}}
244+
}
245+
}
246+
247+
//PolicyResult hold policy eval result
248+
type PolicyResult struct {
249+
ReturnValues map[string]string
213250
}

utils/stringutil_test.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,16 +126,40 @@ func Test_ValidParam(t *testing.T) {
126126
}
127127

128128
func Test_ReadPolicyExpr(t *testing.T) {
129-
evalExpr := "'${0}' != '';&& [${0} MATCH no_permission.policy QUERY example.deny]"
129+
evalExpr := "'${0}' != '';&& [${0} MATCH no_permission.policy QUERY example.policy_eval RETURN allow]"
130+
policy, err := ReadPolicyExpr(evalExpr)
131+
assert.NoError(t, err)
132+
assert.Equal(t, policy.PolicyName, "no_permission.policy")
133+
assert.Equal(t, policy.PolicyQueryParam, "example.policy_eval")
134+
assert.Equal(t, policy.EvalParamNum, 0)
135+
}
136+
137+
func Test_ReadPolicyExprwithReturn(t *testing.T) {
138+
evalExpr := "'${0}' != '';&& [${0} MATCH no_permission.policy QUERY example.deny RETURN allow_policy,namespace]"
130139
policy, err := ReadPolicyExpr(evalExpr)
131140
assert.NoError(t, err)
132141
assert.Equal(t, policy.PolicyName, "no_permission.policy")
133142
assert.Equal(t, policy.PolicyQueryParam, "example.deny")
134143
assert.Equal(t, policy.EvalParamNum, 0)
144+
assert.Equal(t, len(policy.ReturnKeys), 2)
135145
}
136146

137147
func Test_GetPolicyExpr(t *testing.T) {
138148
evalExpr := "'${0}' == 'root:root'; && [${0} MATCH no_deny.policy]"
139149
policyExpr := GetPolicyExpr(evalExpr)
140150
assert.Equal(t, policyExpr, "[${0} MATCH no_deny.policy]")
141151
}
152+
153+
func Test_MatchPolicySingleReturn(t *testing.T) {
154+
policyValues := true
155+
returnData := []string{"allow"}
156+
mpr := MatchPolicy(policyValues, returnData)
157+
assert.Equal(t, mpr.ReturnValues["allow"], "true")
158+
}
159+
160+
func Test_MatchPolicySingleMulti(t *testing.T) {
161+
policyValues := map[string]interface{}{"allow": true}
162+
returnData := []string{"allow"}
163+
mpr := MatchPolicy(policyValues, returnData)
164+
assert.Equal(t, mpr.ReturnValues["allow"], "true")
165+
}

0 commit comments

Comments
 (0)