Skip to content

Commit c0fc740

Browse files
Add policy tests
1 parent c345f31 commit c0fc740

File tree

9 files changed

+365
-147
lines changed

9 files changed

+365
-147
lines changed

pkg/validation/kubeconform_test.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package validation
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
const (
12+
validKubeconformDeployment = `
13+
apiVersion: apps/v1
14+
kind: Deployment
15+
metadata:
16+
name: nginx-deployment
17+
namespace: test1
18+
labels:
19+
app: nginx
20+
spec:
21+
replicas: 3
22+
selector:
23+
matchLabels:
24+
app: nginx
25+
template:
26+
metadata:
27+
labels:
28+
app: nginx
29+
spec:
30+
containers:
31+
- name: nginx
32+
image: nginx:1.14.2
33+
ports:
34+
- containerPort: 80`
35+
36+
invalidKubeconformDeployment = `
37+
apiVersion: apps/v1
38+
kind: Deployment
39+
metadata:
40+
name: nginx-deployment
41+
namespace: test1
42+
labels:
43+
app: nginx
44+
spec:
45+
replicas: 3
46+
invalidField: invalid
47+
selector:
48+
matchLabels:
49+
app: nginx
50+
template:
51+
metadata:
52+
labels:
53+
app: nginx
54+
spec:
55+
containers:
56+
- name: nginx
57+
image: nginx:1.14.2
58+
ports:
59+
- containerPort: 80`
60+
)
61+
62+
func TestKubeconformChecker(t *testing.T) {
63+
ctx := context.Background()
64+
65+
type testCase struct {
66+
resource Resource
67+
expected CheckResult
68+
}
69+
70+
testCases := []testCase{
71+
{
72+
resource: MakeResource(
73+
"test/path",
74+
[]byte(validKubeconformDeployment),
75+
0,
76+
),
77+
expected: CheckResult{
78+
CheckType: CheckTypeKubeconform,
79+
CheckName: "kubeconform",
80+
Status: StatusValid,
81+
},
82+
},
83+
{
84+
resource: MakeResource(
85+
"test/path",
86+
[]byte(invalidKubeconformDeployment),
87+
0,
88+
),
89+
expected: CheckResult{
90+
CheckType: CheckTypeKubeconform,
91+
CheckName: "kubeconform",
92+
Status: StatusInvalid,
93+
Message: "For field spec: Additional property invalidField is not allowed",
94+
},
95+
},
96+
{
97+
resource: MakeResource(
98+
"test/path",
99+
[]byte("xxx\nzzzz"),
100+
0,
101+
),
102+
expected: CheckResult{
103+
CheckType: CheckTypeKubeconform,
104+
CheckName: "kubeconform",
105+
Status: StatusError,
106+
Message: "error unmarshalling resource: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type map[string]interface {}",
107+
},
108+
},
109+
}
110+
111+
checker, err := NewKubeconformChecker()
112+
require.NoError(t, err)
113+
114+
for _, testCase := range testCases {
115+
result := checker.Check(ctx, testCase.resource)
116+
assert.Equal(t, testCase.expected, result)
117+
}
118+
}

pkg/validation/policy.go

Lines changed: 74 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,19 @@ package validation
33
import (
44
"context"
55
"fmt"
6+
"io/ioutil"
7+
"path/filepath"
8+
"reflect"
69

710
"github.com/ghodss/yaml"
811
"github.com/open-policy-agent/opa/rego"
912
)
1013

14+
const (
15+
defaultPackage = "com.segment.kubeapply"
16+
defaultResult = "deny"
17+
)
18+
1119
// Policy wraps a policy module and a prepared query.
1220
type PolicyChecker struct {
1321
Module PolicyModule
@@ -27,7 +35,7 @@ type PolicyModule struct {
2735
func NewPolicyChecker(ctx context.Context, module PolicyModule) (*PolicyChecker, error) {
2836
query, err := rego.New(
2937
rego.Query(
30-
fmt.Sprintf("result = %s.%s", module.Package, module.Result),
38+
fmt.Sprintf("result = data.%s.%s", module.Package, module.Result),
3139
),
3240
rego.Module(module.Package, module.Contents),
3341
).PrepareForEval(ctx)
@@ -42,6 +50,42 @@ func NewPolicyChecker(ctx context.Context, module PolicyModule) (*PolicyChecker,
4250
}, nil
4351
}
4452

53+
func DefaultPoliciesFromGlobs(
54+
ctx context.Context,
55+
globs []string,
56+
) ([]PolicyChecker, error) {
57+
checkers := []PolicyChecker{}
58+
59+
for _, glob := range globs {
60+
matches, err := filepath.Glob(glob)
61+
if err != nil {
62+
return nil, err
63+
}
64+
for _, match := range matches {
65+
contents, err := ioutil.ReadFile(match)
66+
if err != nil {
67+
return nil, err
68+
}
69+
70+
checker, err := NewPolicyChecker(
71+
ctx,
72+
PolicyModule{
73+
Name: filepath.Base(match),
74+
Contents: string(contents),
75+
Package: defaultPackage,
76+
Result: defaultResult,
77+
},
78+
)
79+
if err != nil {
80+
return nil, err
81+
}
82+
checkers = append(checkers, *checker)
83+
}
84+
}
85+
86+
return checkers, nil
87+
}
88+
4589
func (p *PolicyChecker) Check(ctx context.Context, resource Resource) CheckResult {
4690
data := map[string]interface{}{}
4791
result := CheckResult{
@@ -68,19 +112,35 @@ func (p *PolicyChecker) Check(ctx context.Context, resource Resource) CheckResul
68112
return result
69113
}
70114

71-
allowed, ok := results[0].Bindings["result"].(bool)
72-
73-
if !ok {
115+
switch value := results[0].Bindings["result"].(type) {
116+
case bool:
117+
if value {
118+
result.Status = StatusValid
119+
result.Message = "Policy returned allowed = true"
120+
} else {
121+
result.Status = StatusInvalid
122+
result.Message = "Policy returned allowed = false"
123+
}
124+
case []interface{}:
125+
if len(value) == 0 {
126+
result.Status = StatusValid
127+
result.Message = "Policy returned 0 deny reasons"
128+
} else {
129+
result.Status = StatusInvalid
130+
result.Message = fmt.Sprintf(
131+
"Policy returned %d deny reason(s): %+v",
132+
len(value),
133+
value,
134+
)
135+
}
136+
default:
74137
result.Status = StatusError
75-
result.Message = fmt.Sprintf("Did not get exactly one result: %+v", results)
76-
return result
77-
} else if !allowed {
78-
result.Status = StatusInvalid
79-
result.Message = "Policy returned allowed = false"
80-
return result
81-
} else {
82-
result.Status = StatusValid
83-
result.Message = "Policy returned allowed = true"
84-
return result
138+
result.Message = fmt.Sprintf(
139+
"Got unexpected response type: %+v (%+v)",
140+
reflect.TypeOf(value),
141+
value,
142+
)
85143
}
144+
145+
return result
86146
}

pkg/validation/policy_test.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package validation
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
const (
12+
denyPolicyStr = `
13+
package example
14+
15+
deny[msg] {
16+
input.apiVersion == "badVersion"
17+
msg = "Cannot have bad api version"
18+
}`
19+
20+
allowPolicyStr = `
21+
package example
22+
23+
default allow = true
24+
25+
allow = false {
26+
input.apiVersion == "badVersion"
27+
}`
28+
)
29+
30+
func TestPolicyChecker(t *testing.T) {
31+
ctx := context.Background()
32+
33+
type testCase struct {
34+
policyModule PolicyModule
35+
resource Resource
36+
expected CheckResult
37+
}
38+
39+
testCases := []testCase{
40+
{
41+
policyModule: PolicyModule{
42+
Name: "testDenyPolicy",
43+
Contents: denyPolicyStr,
44+
Package: "example",
45+
Result: "deny",
46+
},
47+
resource: MakeResource("test/path", []byte("apiVersion: goodVersion"), 0),
48+
expected: CheckResult{
49+
CheckType: CheckTypeOPA,
50+
CheckName: "testDenyPolicy",
51+
Status: StatusValid,
52+
Message: "Policy returned 0 deny reasons",
53+
},
54+
},
55+
{
56+
policyModule: PolicyModule{
57+
Name: "testDenyPolicy",
58+
Contents: denyPolicyStr,
59+
Package: "example",
60+
Result: "deny",
61+
},
62+
resource: MakeResource("test/path", []byte("apiVersion: badVersion"), 0),
63+
expected: CheckResult{
64+
CheckType: CheckTypeOPA,
65+
CheckName: "testDenyPolicy",
66+
Status: StatusInvalid,
67+
Message: "Policy returned 1 deny reason(s): [Cannot have bad api version]",
68+
},
69+
},
70+
{
71+
policyModule: PolicyModule{
72+
Name: "testAllowPolicy",
73+
Contents: allowPolicyStr,
74+
Package: "example",
75+
Result: "allow",
76+
},
77+
resource: MakeResource("test/path", []byte("apiVersion: goodVersion"), 0),
78+
expected: CheckResult{
79+
CheckType: CheckTypeOPA,
80+
CheckName: "testAllowPolicy",
81+
Status: StatusValid,
82+
Message: "Policy returned allowed = true",
83+
},
84+
},
85+
{
86+
policyModule: PolicyModule{
87+
Name: "testAllowPolicy",
88+
Contents: allowPolicyStr,
89+
Package: "example",
90+
Result: "allow",
91+
},
92+
resource: MakeResource("test/path", []byte("apiVersion: badVersion"), 0),
93+
expected: CheckResult{
94+
CheckType: CheckTypeOPA,
95+
CheckName: "testAllowPolicy",
96+
Status: StatusInvalid,
97+
Message: "Policy returned allowed = false",
98+
},
99+
},
100+
}
101+
102+
for _, testCase := range testCases {
103+
checker, err := NewPolicyChecker(
104+
ctx,
105+
testCase.policyModule,
106+
)
107+
require.NoError(t, err)
108+
109+
result := checker.Check(ctx, testCase.resource)
110+
assert.Equal(t, testCase.expected, result)
111+
}
112+
}

pkg/validation/testdata/error/deployment.yaml

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)