Skip to content

Commit 4b0157a

Browse files
Add-option-to-not-consolidate-under-single-ConfigurationPolicy-#15836 (#13)
* Add-option-to-not-consolidate-under-single-ConfigurationPolicy-#15836 Signed-off-by: Chunxi Luo [email protected] Signed-off-by: ChunxiAlexLuo <[email protected]> * polish Signed-off-by: Chunxi Luo [email protected] Signed-off-by: ChunxiAlexLuo <[email protected]> * update comments and naming Signed-off-by: Chunxi Luo [email protected] Co-Authored-By: Matt Prahl <[email protected]> Co-authored-by: Matt Prahl <[email protected]>
1 parent c0e1af3 commit 4b0157a

File tree

6 files changed

+175
-48
lines changed

6 files changed

+175
-48
lines changed

docs/policygenerator-reference.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ policyDefaults:
2121
# annotation. This defaults to ["CM-2 Baseline Configuration"].
2222
controls:
2323
- "CM-2 Baseline Configuration"
24+
# Optional. This determines if a single configuration policy should be
25+
# generated for all the manifests being wrapped in the policy.
26+
# If set to false, a configuration policy per manifest will be generated.
27+
# This defaults to true.
28+
consolidateManifests: true
2429
# Optional. When the policy references a Kyverno policy manifest, this determines if an additonal
2530
# configuration policy should be generated in order to receive policy violations in Open Cluster
2631
# Management when the Kyverno policy has been violated. This defaults to true.
@@ -91,6 +96,8 @@ policies:
9196
disabled: false
9297
# Optional. (See policyDefaults.informKyvernoPolicies for description.)
9398
informKyvernoPolicies: true
99+
# Optional. (See policyDefaults.consolidateManifests for description.)
100+
consolidateManifests: true
94101
# Optional. Determines the list of namespaces to check on the cluster for the given manifest. If
95102
# a namespace is specified in the manifest, the selector is not necessary. This defaults to no
96103
# selectors.

internal/plugin.go

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -211,20 +211,27 @@ func (p *Plugin) applyDefaults(unmarshaledConfig map[string]interface{}) {
211211

212212
// Policy expanders default to true unless explicitly set in the config.
213213
// Gatekeeper policy expander policyDefault
214-
igvValue, set := getDefaultBool(unmarshaledConfig, "informGatekeeperPolicies")
215-
if set {
214+
igvValue, setIgv := getDefaultBool(unmarshaledConfig, "informGatekeeperPolicies")
215+
if setIgv {
216216
p.PolicyDefaults.InformGatekeeperPolicies = igvValue
217217
} else {
218218
p.PolicyDefaults.InformGatekeeperPolicies = true
219219
}
220220
// Kyverno policy expander policyDefault
221-
ikvValue, set := getDefaultBool(unmarshaledConfig, "informKyvernoPolicies")
222-
if set {
221+
ikvValue, setIkv := getDefaultBool(unmarshaledConfig, "informKyvernoPolicies")
222+
if setIkv {
223223
p.PolicyDefaults.InformKyvernoPolicies = ikvValue
224224
} else {
225225
p.PolicyDefaults.InformKyvernoPolicies = true
226226
}
227227

228+
consolidatedValue, setConsolidated := getDefaultBool(unmarshaledConfig, "consolidateManifests")
229+
if setConsolidated {
230+
p.PolicyDefaults.ConsolidateManifests = consolidatedValue
231+
} else {
232+
p.PolicyDefaults.ConsolidateManifests = true
233+
}
234+
228235
if p.PolicyDefaults.RemediationAction == "" {
229236
p.PolicyDefaults.RemediationAction = defaults.RemediationAction
230237
}
@@ -253,20 +260,27 @@ func (p *Plugin) applyDefaults(unmarshaledConfig map[string]interface{}) {
253260

254261
// Policy expanders default to the policy default unless explicitly set.
255262
// Gatekeeper policy expander policy override
256-
igvValue, set := getPolicyBool(unmarshaledConfig, i, "informGatekeeperPolicies")
257-
if set {
263+
igvValue, setIgv := getPolicyBool(unmarshaledConfig, i, "informGatekeeperPolicies")
264+
if setIgv {
258265
policy.InformGatekeeperPolicies = igvValue
259266
} else {
260267
policy.InformGatekeeperPolicies = p.PolicyDefaults.InformGatekeeperPolicies
261268
}
262269
// Kyverno policy expander policy override
263-
ikvValue, set := getPolicyBool(unmarshaledConfig, i, "informKyvernoPolicies")
264-
if set {
270+
ikvValue, setIkv := getPolicyBool(unmarshaledConfig, i, "informKyvernoPolicies")
271+
if setIkv {
265272
policy.InformKyvernoPolicies = ikvValue
266273
} else {
267274
policy.InformKyvernoPolicies = p.PolicyDefaults.InformKyvernoPolicies
268275
}
269276

277+
consolidatedValue, setConsolidated := getPolicyBool(unmarshaledConfig, i, "consolidateManifests")
278+
if setConsolidated {
279+
policy.ConsolidateManifests = consolidatedValue
280+
} else {
281+
policy.ConsolidateManifests = p.PolicyDefaults.ConsolidateManifests
282+
}
283+
270284
// If both cluster selectors and placement rule path aren't set, then use the
271285
// defaults with a priority on placement rule path.
272286
if len(policy.Placement.ClusterSelectors) == 0 && policy.Placement.PlacementRulePath == "" {

internal/plugin_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ func TestGenerate(t *testing.T) {
4343
}
4444
p.Policies = append(p.Policies, policyConf, policyConf2)
4545
p.applyDefaults(map[string]interface{}{})
46+
// Default all policy ConsolidateManifests flags are set to true
47+
// unless explicitly set
48+
assertEqual(t, p.Policies[0].ConsolidateManifests, true)
49+
assertEqual(t, p.Policies[1].ConsolidateManifests, true)
4650
if err := p.assertValidConfig(); err != nil {
4751
t.Fatal(err.Error())
4852
}

internal/types/types.go

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,35 +19,37 @@ type PlacementConfig struct {
1919

2020
// PolicyConfig represents a policy entry in the PolicyGenerator configuration.
2121
type PolicyConfig struct {
22-
Categories []string `json:"categories,omitempty" yaml:"categories,omitempty"`
23-
ComplianceType string `json:"complianceType,omitempty" yaml:"complianceType,omitempty"`
24-
Controls []string `json:"controls,omitempty" yaml:"controls,omitempty"`
25-
Disabled bool `json:"disabled,omitempty" yaml:"disabled,omitempty"`
26-
InformGatekeeperPolicies bool `json:"informGatekeeperPolicies,omitempty" yaml:"informGatekeeperPolicies,omitempty"`
27-
InformKyvernoPolicies bool `json:"informKyvernoPolicies,omitempty" yaml:"informKyvernoPolicies,omitempty"`
22+
Categories []string `json:"categories,omitempty" yaml:"categories,omitempty"`
23+
ComplianceType string `json:"complianceType,omitempty" yaml:"complianceType,omitempty"`
24+
Controls []string `json:"controls,omitempty" yaml:"controls,omitempty"`
2825
// This a slice of structs to allow additional configuration related to a manifest such as
2926
// accepting patches.
3027
Manifests []Manifest `json:"manifests,omitempty" yaml:"manifests,omitempty"`
3128
Name string `json:"name,omitempty" yaml:"name,omitempty"`
3229
NamespaceSelector NamespaceSelector `json:"namespaceSelector,omitempty" yaml:"namespaceSelector,omitempty"`
3330
// This is named Placement so that eventually PlacementRules and Placements will be supported
34-
Placement PlacementConfig `json:"placement,omitempty" yaml:"placement,omitempty"`
35-
RemediationAction string `json:"remediationAction,omitempty" yaml:"remediationAction,omitempty"`
36-
Severity string `json:"severity,omitempty" yaml:"severity,omitempty"`
37-
Standards []string `json:"standards,omitempty" yaml:"standards,omitempty"`
31+
Placement PlacementConfig `json:"placement,omitempty" yaml:"placement,omitempty"`
32+
RemediationAction string `json:"remediationAction,omitempty" yaml:"remediationAction,omitempty"`
33+
Severity string `json:"severity,omitempty" yaml:"severity,omitempty"`
34+
Standards []string `json:"standards,omitempty" yaml:"standards,omitempty"`
35+
ConsolidateManifests bool `json:"consolidateManifests,omitempty" yaml:"consolidateManifests,omitempty"`
36+
Disabled bool `json:"disabled,omitempty" yaml:"disabled,omitempty"`
37+
InformGatekeeperPolicies bool `json:"informGatekeeperPolicies,omitempty" yaml:"informGatekeeperPolicies,omitempty"`
38+
InformKyvernoPolicies bool `json:"informKyvernoPolicies,omitempty" yaml:"informKyvernoPolicies,omitempty"`
3839
}
3940

4041
type PolicyDefaults struct {
41-
Categories []string `json:"categories,omitempty" yaml:"categories,omitempty"`
42-
ComplianceType string `json:"complianceType,omitempty" yaml:"complianceType,omitempty"`
43-
Controls []string `json:"controls,omitempty" yaml:"controls,omitempty"`
44-
InformGatekeeperPolicies bool `json:"informGatekeeperPolicies,omitempty" yaml:"informGatekeeperPolicies,omitempty"`
45-
InformKyvernoPolicies bool `json:"informKyvernoPolicies,omitempty" yaml:"informKyvernoPolicies,omitempty"`
46-
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
47-
NamespaceSelector NamespaceSelector `json:"namespaceSelector,omitempty" yaml:"namespaceSelector,omitempty"`
42+
Categories []string `json:"categories,omitempty" yaml:"categories,omitempty"`
43+
ComplianceType string `json:"complianceType,omitempty" yaml:"complianceType,omitempty"`
44+
Controls []string `json:"controls,omitempty" yaml:"controls,omitempty"`
45+
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
46+
NamespaceSelector NamespaceSelector `json:"namespaceSelector,omitempty" yaml:"namespaceSelector,omitempty"`
4847
// This is named Placement so that eventually PlacementRules and Placements will be supported
49-
Placement PlacementConfig `json:"placement,omitempty" yaml:"placement,omitempty"`
50-
RemediationAction string `json:"remediationAction,omitempty" yaml:"remediationAction,omitempty"`
51-
Severity string `json:"severity,omitempty" yaml:"severity,omitempty"`
52-
Standards []string `json:"standards,omitempty" yaml:"standards,omitempty"`
48+
Placement PlacementConfig `json:"placement,omitempty" yaml:"placement,omitempty"`
49+
RemediationAction string `json:"remediationAction,omitempty" yaml:"remediationAction,omitempty"`
50+
Severity string `json:"severity,omitempty" yaml:"severity,omitempty"`
51+
Standards []string `json:"standards,omitempty" yaml:"standards,omitempty"`
52+
ConsolidateManifests bool `json:"consolidateManifests,omitempty" yaml:"consolidateManifests,omitempty"`
53+
InformGatekeeperPolicies bool `json:"informGatekeeperPolicies,omitempty" yaml:"informGatekeeperPolicies,omitempty"`
54+
InformKyvernoPolicies bool `json:"informKyvernoPolicies,omitempty" yaml:"informKyvernoPolicies,omitempty"`
5355
}

internal/utils.go

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,11 @@ func getManifests(policyConf *types.PolicyConfig) ([]map[string]interface{}, err
8787
}
8888

8989
// getPolicyTemplates generates the policy templates for the ConfigurationPolicy manifests
90-
// that includes the manifests specified in policyConf. An error is returned
91-
// if one or more manifests cannot be read or are invalid.
90+
// policyConf.ConsolidateManifests = true (default value) will generate a policy templates slice
91+
// that just has one template which includes all the manifests specified in policyConf.
92+
// policyConf.ConsolidateManifests = false will generate a policy templates slice
93+
// that each template includes a single manifest specified in policyConf.
94+
// An error is returned if one or more manifests cannot be read or are invalid.
9295
func getPolicyTemplates(policyConf *types.PolicyConfig) ([]map[string]map[string]interface{}, error) {
9396
manifests, err := getManifests(policyConf)
9497
if err != nil {
@@ -101,14 +104,54 @@ func getPolicyTemplates(policyConf *types.PolicyConfig) ([]map[string]map[string
101104
)
102105
}
103106

104-
objectTemplates := make([]map[string]interface{}, 0, len(manifests))
107+
objectTemplatesLength := len(manifests)
108+
policyTemplatesLength := 1
109+
if !policyConf.ConsolidateManifests {
110+
policyTemplatesLength = len(manifests)
111+
objectTemplatesLength = 0
112+
}
113+
objectTemplates := make([]map[string]interface{}, 0, objectTemplatesLength)
114+
policyTemplates := make([]map[string]map[string]interface{}, 0, policyTemplatesLength)
105115
for _, manifest := range manifests {
106116
objTemplate := map[string]interface{}{
107117
"complianceType": policyConf.ComplianceType,
108118
"objectDefinition": manifest,
109119
}
110-
objectTemplates = append(objectTemplates, objTemplate)
120+
if policyConf.ConsolidateManifests {
121+
// put all objTemplate with manifest into single consolidated objectTemplates object
122+
objectTemplates = append(objectTemplates, objTemplate)
123+
} else {
124+
// casting each objTemplate with manifest to objectTemplates type
125+
// build policyTemplate for each objectTemplates
126+
policyTemplate := buildPolicyTemplate(policyConf, &[]map[string]interface{}{objTemplate})
127+
setNamespaceSelector(policyConf, policyTemplate)
128+
policyTemplates = append(policyTemplates, *policyTemplate)
129+
}
111130
}
131+
132+
// just build one policyTemplate by using the above consolidated objectTemplates
133+
if policyConf.ConsolidateManifests {
134+
policyTemplate := buildPolicyTemplate(policyConf, &objectTemplates)
135+
setNamespaceSelector(policyConf, policyTemplate)
136+
policyTemplates = append(policyTemplates, *policyTemplate)
137+
}
138+
139+
// check the enabled expanders and add additional policy templates
140+
expandedPolicyTemplates := handleExpanders(manifests, policyConf)
141+
policyTemplates = append(policyTemplates, expandedPolicyTemplates...)
142+
143+
return policyTemplates, nil
144+
}
145+
146+
// setNamespaceSelector sets the namespace selector, if set, on the input policy template.
147+
func setNamespaceSelector(policyConf *types.PolicyConfig, policyTemplate *map[string]map[string]interface{}) {
148+
if policyConf.NamespaceSelector.Exclude != nil || policyConf.NamespaceSelector.Include != nil {
149+
(*policyTemplate)["objectDefinition"]["spec"].(map[string]interface{})["namespaceSelector"] = policyConf.NamespaceSelector
150+
}
151+
}
152+
153+
// buildPolicyTemplate generates single policy template by using objectTemplates with manifests.
154+
func buildPolicyTemplate(policyConf *types.PolicyConfig, objectTemplates *[]map[string]interface{}) *map[string]map[string]interface{} {
112155
policyTemplate := map[string]map[string]interface{}{
113156
"objectDefinition": {
114157
"apiVersion": policyAPIVersion,
@@ -117,22 +160,14 @@ func getPolicyTemplates(policyConf *types.PolicyConfig) ([]map[string]map[string
117160
"name": policyConf.Name,
118161
},
119162
"spec": map[string]interface{}{
120-
"object-templates": objectTemplates,
163+
"object-templates": *objectTemplates,
121164
"remediationAction": policyConf.RemediationAction,
122165
"severity": policyConf.Severity,
123166
},
124167
},
125168
}
126169

127-
if policyConf.NamespaceSelector.Exclude != nil || policyConf.NamespaceSelector.Include != nil {
128-
policyTemplate["objectDefinition"]["spec"].(map[string]interface{})["namespaceSelector"] = policyConf.NamespaceSelector
129-
}
130-
131-
policyTemplates := []map[string]map[string]interface{}{policyTemplate}
132-
expandedPolicyTemplates := handleExpanders(manifests, policyConf)
133-
policyTemplates = append(policyTemplates, expandedPolicyTemplates...)
134-
135-
return policyTemplates, nil
170+
return &policyTemplate
136171
}
137172

138173
// handleExpanders will go through all the enabled expanders and generate additional

internal/utils_test.go

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,17 @@ data:
6464
{Manifests: manifestFiles},
6565
{Manifests: []types.Manifest{{Path: tmpDir}}},
6666
}
67+
// test ConsolidateManifests = true (default case)
68+
// policyTemplates will have only one policyTemplate
69+
// and two objTemplate under this policyTemplate
6770
for _, test := range tests {
6871
policyConf := types.PolicyConfig{
69-
ComplianceType: "musthave",
70-
Manifests: test.Manifests,
71-
Name: "policy-app-config",
72-
RemediationAction: "inform",
73-
Severity: "low",
72+
ComplianceType: "musthave",
73+
ConsolidateManifests: true,
74+
Manifests: test.Manifests,
75+
Name: "policy-app-config",
76+
RemediationAction: "inform",
77+
Severity: "low",
7478
}
7579

7680
policyTemplates, err := getPolicyTemplates(&policyConf)
@@ -106,6 +110,67 @@ data:
106110
}
107111
assertEqual(t, kind2, "ConfigMap")
108112
}
113+
114+
// test ConsolidateManifests = false case
115+
// policyTemplates will skip the consolidation and have two policyTemplate
116+
// and each policyTemplate has only one objTemplate
117+
for _, test := range tests {
118+
policyConf := types.PolicyConfig{
119+
ComplianceType: "musthave",
120+
ConsolidateManifests: false,
121+
Manifests: test.Manifests,
122+
Name: "policy-app-config",
123+
RemediationAction: "inform",
124+
Severity: "low",
125+
}
126+
127+
policyTemplates, err := getPolicyTemplates(&policyConf)
128+
if err != nil {
129+
t.Fatalf("Failed to get the policy templates: %v", err)
130+
}
131+
assertEqual(t, len(policyTemplates), 2)
132+
policyTemplate1 := policyTemplates[0]
133+
objdef1 := policyTemplate1["objectDefinition"]
134+
assertEqual(t, objdef1["metadata"].(map[string]string)["name"], "policy-app-config")
135+
spec1, ok := objdef1["spec"].(map[string]interface{})
136+
if !ok {
137+
t.Fatal("The spec field is an invalid format")
138+
}
139+
assertEqual(t, spec1["remediationAction"], "inform")
140+
assertEqual(t, spec1["severity"], "low")
141+
objTemplates1, ok := spec1["object-templates"].([]map[string]interface{})
142+
if !ok {
143+
t.Fatal("The object-templates field is an invalid format")
144+
}
145+
assertEqual(t, len(objTemplates1), 1)
146+
assertEqual(t, objTemplates1[0]["complianceType"], "musthave")
147+
kind1, ok := objTemplates1[0]["objectDefinition"].(map[string]interface{})["kind"]
148+
if !ok {
149+
t.Fatal("The objectDefinition field is an invalid format")
150+
}
151+
assertEqual(t, kind1, "ConfigMap")
152+
153+
policyTemplate2 := policyTemplates[1]
154+
objdef2 := policyTemplate2["objectDefinition"]
155+
assertEqual(t, objdef2["metadata"].(map[string]string)["name"], "policy-app-config")
156+
spec2, ok := objdef2["spec"].(map[string]interface{})
157+
if !ok {
158+
t.Fatal("The spec field is an invalid format")
159+
}
160+
assertEqual(t, spec2["remediationAction"], "inform")
161+
assertEqual(t, spec2["severity"], "low")
162+
objTemplates2, ok := spec2["object-templates"].([]map[string]interface{})
163+
if !ok {
164+
t.Fatal("The object-templates field is an invalid format")
165+
}
166+
assertEqual(t, len(objTemplates2), 1)
167+
assertEqual(t, objTemplates2[0]["complianceType"], "musthave")
168+
kind2, ok := objTemplates2[0]["objectDefinition"].(map[string]interface{})["kind"]
169+
if !ok {
170+
t.Fatal("The objectDefinition field is an invalid format")
171+
}
172+
assertEqual(t, kind2, "ConfigMap")
173+
}
109174
}
110175

111176
func TestGetPolicyTemplatePatches(t *testing.T) {

0 commit comments

Comments
 (0)