Skip to content

Commit 8b797b9

Browse files
mprahldhaiducek
andcommitted
Add support for automatically informing on Kyverno policy violations
By default, when a manifest includes a Kyverno policy, an additional configuration policy will get generated to inform on Kyverno policy violations in Open Cluster Management. Co-authored-by: Dale Haiducek <[email protected]> Signed-off-by: mprahl <[email protected]>
1 parent cd17b71 commit 8b797b9

File tree

11 files changed

+497
-35
lines changed

11 files changed

+497
-35
lines changed

docs/policygenerator-reference.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ policyDefaults:
2121
# annotation. This defaults to ["CM-2 Baseline Configuration"].
2222
controls:
2323
- "CM-2 Baseline Configuration"
24+
# Optional. When the policy references a Kyverno policy manifest, this determines if an additonal
25+
# configuration policy should be generated in order to receive policy violations in Open Cluster
26+
# Management when the Kyverno policy has been violated. This defaults to true.
27+
informKyvernoPolicies: true
2428
# Required. The namespace of all the policies.
2529
namespace: ""
2630
# Optional. The placement configuration for the policies. This defaults to a placement
@@ -85,6 +89,8 @@ policies:
8589
- "CM-2 Baseline Configuration"
8690
# Optional. (See policyDefaults.disabled for description.)
8791
disabled: false
92+
# Optional. (See policyDefaults.informKyvernoPolicies for description.)
93+
informKyvernoPolicies: true
8894
# Optional. Determines the list of namespaces to check on the cluster for the given manifest. If
8995
# a namespace is specified in the manifest, the selector is not necessary. This defaults to no
9096
# selectors.

examples/input3/kyverno.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
apiVersion: kyverno.io/v1
2+
kind: ClusterPolicy
3+
metadata:
4+
name: require-labels
5+
spec:
6+
validationFailureAction: audit
7+
rules:
8+
- name: check-for-labels
9+
match:
10+
resources:
11+
kinds:
12+
- Pod
13+
validate:
14+
message: "The label `friends-character` is required."
15+
pattern:
16+
metadata:
17+
labels:
18+
friends-character: "?*"

examples/policyGenerator.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,7 @@ policies:
4343
namespace: default
4444
labels:
4545
monica: geller
46+
- name: policy-require-labels
47+
disabled: true
48+
manifests:
49+
- path: input3/

internal/expanders/expanders.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright Contributors to the Open Cluster Management project
2+
package expanders
3+
4+
import (
5+
"github.com/open-cluster-management/policy-generator-plugin/internal/types"
6+
)
7+
8+
func GetExpanders() map[string]Expander {
9+
return map[string]Expander{
10+
"kyverno": KyvernoPolicyExpander{},
11+
}
12+
}
13+
14+
type Expander interface {
15+
CanHandle(manifest map[string]interface{}) bool
16+
Enabled(policyConf *types.PolicyConfig) bool
17+
Expand(manifest map[string]interface{}, severity string) []map[string]map[string]interface{}
18+
}
19+
20+
// Common constants for the expanders.
21+
const (
22+
configPolicyAPIVersion = "policy.open-cluster-management.io/v1"
23+
configPolicyKind = "ConfigurationPolicy"
24+
)

internal/expanders/kyverno.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright Contributors to the Open Cluster Management project
2+
package expanders
3+
4+
import (
5+
"fmt"
6+
7+
"github.com/open-cluster-management/policy-generator-plugin/internal/types"
8+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
9+
)
10+
11+
type KyvernoPolicyExpander struct{}
12+
13+
const (
14+
kyvernoAPIVersion = "kyverno.io/v1"
15+
kyvernoClusterKind = "ClusterPolicy"
16+
kyvernoPolicyReportAPIVersion = "wgpolicyk8s.io/v1alpha2"
17+
kyvernoNamespacedKind = "Policy"
18+
)
19+
20+
// CanHandle determines if the manifest is a Kyverno policy that can be expanded.
21+
func (k KyvernoPolicyExpander) CanHandle(manifest map[string]interface{}) bool {
22+
if a, _, _ := unstructured.NestedString(manifest, "apiVersion"); a != kyvernoAPIVersion {
23+
return false
24+
}
25+
26+
kind, _, _ := unstructured.NestedString(manifest, "kind")
27+
if kind != kyvernoClusterKind && kind != kyvernoNamespacedKind {
28+
return false
29+
}
30+
31+
if n, _, _ := unstructured.NestedString(manifest, "metadata", "name"); n == "" {
32+
return false
33+
}
34+
35+
return true
36+
}
37+
38+
// Enabled determines if the policy configuration allows a Kyverno policy to be expanded.
39+
func (k KyvernoPolicyExpander) Enabled(policyConf *types.PolicyConfig) bool {
40+
return policyConf.InformKyvernoPolicies
41+
}
42+
43+
// AdditionalPolicyTemplates will generate additional policy templates for the Kyverno policy
44+
// for auditing purposes through Open Cluster Management. This should be run after the CanHandle
45+
// method.
46+
func (k KyvernoPolicyExpander) Expand(
47+
manifest map[string]interface{}, severity string,
48+
) []map[string]map[string]interface{} {
49+
templates := []map[string]map[string]interface{}{}
50+
// This was previously validated in the CanHandle method.
51+
policyName, _, _ := unstructured.NestedString(manifest, "metadata", "name")
52+
53+
configPolicyName := fmt.Sprintf("inform-kyverno-%s", policyName)
54+
configurationPolicy := map[string]map[string]interface{}{
55+
"objectDefinition": {
56+
"apiVersion": configPolicyAPIVersion,
57+
"kind": configPolicyKind,
58+
"metadata": map[string]interface{}{"name": configPolicyName},
59+
"spec": map[string]interface{}{
60+
"namespaceSelector": map[string]interface{}{
61+
"exclude": []string{"kube-*"},
62+
"include": []string{"*"},
63+
},
64+
"remediationAction": "inform",
65+
"severity": severity,
66+
"object-templates": []map[string]interface{}{
67+
{
68+
"complianceType": "mustnothave",
69+
"objectDefinition": map[string]interface{}{
70+
"apiVersion": kyvernoPolicyReportAPIVersion,
71+
"kind": kyvernoClusterKind + "Report",
72+
"results": []map[string]interface{}{
73+
{
74+
"policy": policyName,
75+
"result": "fail",
76+
},
77+
},
78+
},
79+
},
80+
{
81+
"complianceType": "mustnothave",
82+
"objectDefinition": map[string]interface{}{
83+
"apiVersion": kyvernoPolicyReportAPIVersion,
84+
"kind": kyvernoNamespacedKind + "Report",
85+
"results": []map[string]interface{}{
86+
{
87+
"policy": policyName,
88+
"result": "fail",
89+
},
90+
},
91+
},
92+
},
93+
},
94+
},
95+
},
96+
}
97+
98+
templates = append(templates, configurationPolicy)
99+
100+
return templates
101+
}

internal/expanders/test_kyverno.go

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// Copyright Contributors to the Open Cluster Management project
2+
package expanders
3+
4+
import (
5+
"fmt"
6+
"reflect"
7+
"testing"
8+
9+
"github.com/open-cluster-management/policy-generator-plugin/internal/types"
10+
)
11+
12+
func assertEqual(t *testing.T, a interface{}, b interface{}) {
13+
t.Helper()
14+
if a != b {
15+
t.Fatalf("%s != %s", a, b)
16+
}
17+
}
18+
19+
func assertReflectEqual(t *testing.T, a interface{}, b interface{}) {
20+
t.Helper()
21+
if !reflect.DeepEqual(a, b) {
22+
t.Fatalf("%s != %s", a, b)
23+
}
24+
}
25+
26+
func TestCanHandle(t *testing.T) {
27+
t.Parallel()
28+
k := KyvernoPolicyExpander{}
29+
30+
tests := []struct{ kind string }{
31+
{kyvernoClusterKind},
32+
{kyvernoNamespacedKind},
33+
}
34+
35+
for _, test := range tests {
36+
test := test
37+
t.Run(
38+
fmt.Sprintf("kind=%s", test.kind),
39+
func(t *testing.T) {
40+
t.Parallel()
41+
manifest := map[string]interface{}{
42+
"apiVersion": kyvernoAPIVersion,
43+
"kind": test.kind,
44+
"metadata": map[string]string{
45+
"name": "my-awesome-policy",
46+
},
47+
}
48+
assertEqual(t, k.CanHandle(manifest), true)
49+
},
50+
)
51+
}
52+
}
53+
54+
func TestCanHandleInvalid(t *testing.T) {
55+
t.Parallel()
56+
k := KyvernoPolicyExpander{}
57+
58+
tests := []struct{ apiVersion, kind, name string }{
59+
{"v1", kyvernoClusterKind, "my-awesome-policy"},
60+
{"v1", kyvernoNamespacedKind, "my-awesome-policy"},
61+
{kyvernoAPIVersion, "ConfigMap", "my-awesome-policy"},
62+
{kyvernoAPIVersion, kyvernoClusterKind, ""},
63+
{kyvernoAPIVersion, kyvernoNamespacedKind, ""},
64+
}
65+
66+
for _, test := range tests {
67+
test := test
68+
t.Run(
69+
fmt.Sprintf("kind=%s", test.kind),
70+
func(t *testing.T) {
71+
t.Parallel()
72+
manifest := map[string]interface{}{
73+
"apiVersion": test.apiVersion,
74+
"kind": test.kind,
75+
"metadata": map[string]string{
76+
"name": test.name,
77+
},
78+
}
79+
assertEqual(t, k.CanHandle(manifest), false)
80+
},
81+
)
82+
}
83+
}
84+
85+
func TestEnabled(t *testing.T) {
86+
t.Parallel()
87+
k := KyvernoPolicyExpander{}
88+
tests := []struct {
89+
Enabled bool
90+
Expected bool
91+
}{{true, true}, {false, false}}
92+
for _, test := range tests {
93+
policyConf := types.PolicyConfig{InformKyvernoPolicies: test.Enabled}
94+
assertEqual(t, k.Enabled(&policyConf), test.Expected)
95+
}
96+
}
97+
98+
func TestExpand(t *testing.T) {
99+
t.Parallel()
100+
k := KyvernoPolicyExpander{}
101+
manifest := map[string]interface{}{
102+
"apiVersion": kyvernoAPIVersion,
103+
"kind": kyvernoClusterKind,
104+
"metadata": map[string]string{
105+
"name": "my-awesome-policy",
106+
},
107+
}
108+
109+
expected := []map[string]map[string]interface{}{
110+
{
111+
"objectDefinition": {
112+
"apiVersion": configPolicyAPIVersion,
113+
"kind": configPolicyKind,
114+
"metadata": map[string]interface{}{"name": "inform-kyverno-my-awesome-policy"},
115+
"spec": map[string]interface{}{
116+
"namespaceSelector": map[string]interface{}{
117+
"exclude": []string{"kube-*"},
118+
"include": []string{"*"},
119+
},
120+
"remediationAction": "inform",
121+
"severity": "medium",
122+
"object-templates": []map[string]interface{}{
123+
{
124+
"complianceType": "mustnothave",
125+
"objectDefinition": map[string]interface{}{
126+
"apiVersion": kyvernoPolicyReportAPIVersion,
127+
"kind": "ClusterPolicyReport",
128+
"results": []map[string]interface{}{
129+
{
130+
"policy": "my-awesome-policy",
131+
"result": "fail",
132+
},
133+
},
134+
},
135+
},
136+
{
137+
"complianceType": "mustnothave",
138+
"objectDefinition": map[string]interface{}{
139+
"apiVersion": kyvernoPolicyReportAPIVersion,
140+
"kind": "PolicyReport",
141+
"results": []map[string]interface{}{
142+
{
143+
"policy": "my-awesome-policy",
144+
"result": "fail",
145+
},
146+
},
147+
},
148+
},
149+
},
150+
},
151+
},
152+
},
153+
}
154+
155+
templates := k.Expand(manifest, "medium")
156+
157+
assertReflectEqual(t, templates, expected)
158+
}

0 commit comments

Comments
 (0)