Skip to content

Commit 35defb7

Browse files
author
Yu Cao
authored
Add policyset generation support (#33)
Implement logic to generate policysets. There are three ways to generate 1. use policyDefaults.policysets field. If specified, any policies without override will be included in the policysets 2. use policies[*].policysets field to include current policy in the policysets. If specified, it overrides the policyDefaults.policysets 3. use policysets field. This allows you specify any policies no matter policies are generated by generator or pre-exsit on hub. This field also allows you specify the description of the policyset. Signed-off-by: Yu Cao <[email protected]>
1 parent 2a2fc52 commit 35defb7

File tree

5 files changed

+445
-27
lines changed

5 files changed

+445
-27
lines changed

docs/policygenerator-reference.yaml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,16 @@ policyDefaults:
6060
# annotation. This defaults to ["NIST SP 800-53"].
6161
standards:
6262
- "NIST SP 800-53"
63+
# Optional. Array of policy sets that the policy will join. Policy set details can be defined
64+
# in the policySets section. When a policy is part of a policy set, a placement binding will not be
65+
# generated for the policy since one is generated for the set. Set policies[*].generatePlacementWhenInSet
66+
# or policyDefaults.generatePlacementWhenInSet to override.
67+
policySets: []
68+
# Optional. When a policy is part of a policy set, by default the generator will not generate the placement
69+
# for this policy since a placement is generated for the policy set. If a placement should still be generated,
70+
# set it to "true" so that the policy will be deployed with both policy placement and policy set placement.
71+
# This defaults to "false".
72+
generatePlacementWhenInSet: false
6373

6474
# Required. The list of policies to create along with overrides to either the default values or, if
6575
# set, the values given in policyDefaults.
@@ -125,3 +135,36 @@ policies:
125135
# Optional. (See policyDefaults.standards for description.)
126136
standards:
127137
- "NIST SP 800-53"
138+
# Optional. (See policyDefaults.policySets for description.)
139+
policySets: []
140+
# Optional. (See policyDefaults.generatePlacementWhenInSet for description.)
141+
generatePlacementWhenInSet: false
142+
143+
# Optional. The list of policy sets to create. To include a policy in a policy set, use
144+
# policies[*].policySets or policyDefaults.policySets or policySets.policies.
145+
policySets:
146+
# Required. The name of the policy set to create.
147+
- name: ""
148+
# Optional. The description of the policy set to create.
149+
description: ""
150+
# Optional. The list of policies to be included in the policy set. If policies[*].policySets or
151+
# policyDefaults.policySets is also specified, the list is merged.
152+
policies: []
153+
# Optional. The placement configuration for the policy set. This defaults to a placement
154+
# configuration that matches all clusters.
155+
placement:
156+
# To specify a placement rule, specify key:value pair cluster selectors. (See placementRulePath
157+
# to specify an existing file instead.)
158+
clusterSelectors: {}
159+
# To specify a placement, specify key:value pair cluster label selectors. (See placementPath to
160+
# specify an existing file instead.)
161+
labelSelector: {}
162+
# Optional. Specifying a name will consolidate placement rules that contain the same cluster
163+
# selectors.
164+
name: ""
165+
# To reuse an existing placement manifest, specify the path here relative to the
166+
# kustomization.yaml file. (See clusterSelectors to generate a new Placement instead.)
167+
placementPath: ""
168+
# To reuse an existing placement rule manifest, specify the path here relative to the
169+
# kustomization.yaml file. (See clusterSelectors to generate a new PlacementRule instead.)
170+
placementRulePath: ""

examples/policyGenerator.yaml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ policyDefaults:
2020
remediationAction: inform
2121
severity: medium
2222
# standards: []
23+
policySets:
24+
- policyset-config
2325
policies:
2426
- name: policy-app-config-aliens
2527
disabled: false
@@ -48,6 +50,17 @@ policies:
4850
disabled: true
4951
manifests:
5052
- path: input-kyverno/
53+
policySets:
54+
- policyset-kyverno
5155
- name: policy-require-ns-labels
5256
manifests:
53-
- path: input-gatekeeper/
57+
- path: input-gatekeeper/
58+
policySets:
59+
- policyset-gatekeeper
60+
policySets:
61+
- name: policyset-kyverno
62+
description: this is a kyverno policy set.
63+
policies:
64+
- pre-exists-kyverno-policy
65+
- name: policyset-gatekeeper
66+
description: this is a gatekeeper policy set.

internal/plugin.go

Lines changed: 121 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const (
1919
configPolicyKind = "ConfigurationPolicy"
2020
policyAPIVersion = "policy.open-cluster-management.io/v1"
2121
policyKind = "Policy"
22+
policySetKind = "PolicySet"
2223
placementBindingAPIVersion = "policy.open-cluster-management.io/v1"
2324
placementBindingKind = "PlacementBinding"
2425
placementRuleAPIVersion = "apps.open-cluster-management.io/v1"
@@ -37,8 +38,9 @@ type Plugin struct {
3738
PlacementBindingDefaults struct {
3839
Name string `json:"name,omitempty" yaml:"name,omitempty"`
3940
} `json:"placementBindingDefaults,omitempty" yaml:"placementBindingDefaults,omitempty"`
40-
PolicyDefaults types.PolicyDefaults `json:"policyDefaults,omitempty" yaml:"policyDefaults,omitempty"`
41-
Policies []types.PolicyConfig `json:"policies" yaml:"policies"`
41+
PolicyDefaults types.PolicyDefaults `json:"policyDefaults,omitempty" yaml:"policyDefaults,omitempty"`
42+
Policies []types.PolicyConfig `json:"policies" yaml:"policies"`
43+
PolicySets []types.PolicySetConfig `json:"policySets" yaml:"policySets"`
4244
// A set of all placement names that have been processed or generated
4345
allPlcs map[string]bool
4446
// The base of the directory tree to restrict all manifest files to be within
@@ -105,15 +107,26 @@ func (p *Plugin) Generate() ([]byte, error) {
105107
}
106108
}
107109

110+
for i := range p.PolicySets {
111+
err := p.createPolicySet(&p.PolicySets[i])
112+
if err != nil {
113+
return nil, err
114+
}
115+
}
116+
108117
// Keep track of which placement maps to which policy. This will be used to determine
109118
// how many placement bindings are required since one binding per placement is required.
110119
plcNameToPolicyIdxs := map[string][]int{}
111120
for i := range p.Policies {
112-
plcName, err := p.createPlacement(&p.Policies[i])
113-
if err != nil {
114-
return nil, err
121+
// only generate placement when GeneratePlacementWhenInSet equals to true or policy is not
122+
// part of any policy sets
123+
if p.Policies[i].GeneratePlacementWhenInSet || len(p.Policies[i].PolicySets) == 0 {
124+
plcName, err := p.createPlacement(&p.Policies[i])
125+
if err != nil {
126+
return nil, err
127+
}
128+
plcNameToPolicyIdxs[plcName] = append(plcNameToPolicyIdxs[plcName], i)
115129
}
116-
plcNameToPolicyIdxs[plcName] = append(plcNameToPolicyIdxs[plcName], i)
117130
}
118131

119132
// Sort the keys of plcNameToPolicyIdxs so that the policy bindings are generated in a
@@ -201,11 +214,11 @@ func getPolicyBool(
201214
return
202215
}
203216

204-
// applyDefaults applies any missing defaults under Policy.PlacementBindingDefaults and
205-
// Policy.PolicyDefaults. It then applies the defaults and user provided defaults on each
206-
// policy entry if they are not overridden by the user. The input unmarshaledConfig is used
207-
// in situations where it is necessary to know if an explicit false is provided rather than
208-
// rely on the default Go value on the Plugin struct.
217+
// applyDefaults applies any missing defaults under Policy.PlacementBindingDefaults,
218+
// Policy.PolicyDefaults and PolicySets. It then applies the defaults and user provided
219+
// defaults on each policy and policyset entry if they are not overridden by the user. The
220+
// input unmarshaledConfig is used in situations where it is necessary to know if an explicit
221+
// false is provided rather than rely on the default Go value on the Plugin struct.
209222
func (p *Plugin) applyDefaults(unmarshaledConfig map[string]interface{}) {
210223
if len(p.Policies) == 0 {
211224
return
@@ -258,6 +271,25 @@ func (p *Plugin) applyDefaults(unmarshaledConfig map[string]interface{}) {
258271
if p.PolicyDefaults.Standards == nil {
259272
p.PolicyDefaults.Standards = defaults.Standards
260273
}
274+
// Generate temporary sets to later merge the policy sets declared in p.Policies[*] and p.PolicySets
275+
plcsetToPlc := make(map[string]map[string]bool)
276+
plcToPlcset := make(map[string]map[string]bool)
277+
278+
for _, plcset := range p.PolicySets {
279+
if plcsetToPlc[plcset.Name] == nil {
280+
plcsetToPlc[plcset.Name] = make(map[string]bool)
281+
}
282+
283+
for _, plc := range plcset.Policies {
284+
plcsetToPlc[plcset.Name][plc] = true
285+
286+
if plcToPlcset[plc] == nil {
287+
plcToPlcset[plc] = make(map[string]bool)
288+
}
289+
290+
plcToPlcset[plc][plcset.Name] = true
291+
}
292+
}
261293

262294
for i := range p.Policies {
263295
policy := &p.Policies[i]
@@ -273,6 +305,18 @@ func (p *Plugin) applyDefaults(unmarshaledConfig map[string]interface{}) {
273305
policy.Controls = p.PolicyDefaults.Controls
274306
}
275307

308+
if policy.PolicySets == nil {
309+
policy.PolicySets = p.PolicyDefaults.PolicySets
310+
}
311+
312+
// GeneratePlacementWhenInSet default to false unless explicitly set in the config.
313+
gpValue, setGp := getPolicyBool(unmarshaledConfig, i, "generatePlacementWhenInSet")
314+
if setGp {
315+
policy.GeneratePlacementWhenInSet = gpValue
316+
} else {
317+
policy.GeneratePlacementWhenInSet = p.PolicyDefaults.GeneratePlacementWhenInSet
318+
}
319+
276320
// Policy expanders default to the policy default unless explicitly set.
277321
// Gatekeeper policy expander policy override
278322
igvValue, setIgv := getPolicyBool(unmarshaledConfig, i, "informGatekeeperPolicies")
@@ -342,6 +386,42 @@ func (p *Plugin) applyDefaults(unmarshaledConfig map[string]interface{}) {
342386
policy.Manifests[i].ComplianceType = policy.ComplianceType
343387
}
344388
}
389+
390+
for _, plcsetInPlc := range policy.PolicySets {
391+
if _, ok := plcsetToPlc[plcsetInPlc]; !ok {
392+
newPlcset := types.PolicySetConfig{
393+
Name: plcsetInPlc,
394+
}
395+
p.PolicySets = append(p.PolicySets, newPlcset)
396+
plcsetToPlc[plcsetInPlc] = make(map[string]bool)
397+
}
398+
if plcToPlcset[policy.Name] == nil {
399+
plcToPlcset[policy.Name] = make(map[string]bool)
400+
}
401+
402+
plcToPlcset[policy.Name][plcsetInPlc] = true
403+
404+
plcsetToPlc[plcsetInPlc][policy.Name] = true
405+
}
406+
407+
policy.PolicySets = make([]string, 0, len(plcToPlcset[policy.Name]))
408+
409+
for plcset := range plcToPlcset[policy.Name] {
410+
policy.PolicySets = append(policy.PolicySets, plcset)
411+
}
412+
}
413+
414+
// Sync up the declared policy sets in p.Policies[*]
415+
for i := range p.PolicySets {
416+
plcset := &p.PolicySets[i]
417+
plcset.Policies = make([]string, 0, len(plcsetToPlc[plcset.Name]))
418+
419+
for plc := range plcsetToPlc[plcset.Name] {
420+
plcset.Policies = append(plcset.Policies, plc)
421+
}
422+
423+
// Sort alphabetically to make it deterministic
424+
sort.Strings(plcset.Policies)
345425
}
346426
}
347427

@@ -532,6 +612,36 @@ func (p *Plugin) createPolicy(policyConf *types.PolicyConfig) error {
532612
return nil
533613
}
534614

615+
// createPolicySet will generate the policyset based on the Policy Generator configuration.
616+
// The generated policyset is written to the plugin's output buffer. An error is returned if the
617+
// manifests specified in the configuration are invalid or can't be read.
618+
func (p *Plugin) createPolicySet(policySetConf *types.PolicySetConfig) error {
619+
policyset := map[string]interface{}{
620+
"apiVersion": policyAPIVersion,
621+
"kind": policySetKind,
622+
"metadata": map[string]interface{}{
623+
"name": policySetConf.Name,
624+
"namespace": p.PolicyDefaults.Namespace, // policyset should be generated in the same namespace of policy
625+
},
626+
"spec": map[string]interface{}{
627+
"description": policySetConf.Description,
628+
"policies": policySetConf.Policies,
629+
},
630+
}
631+
632+
policysetYAML, err := yaml.Marshal(policyset)
633+
if err != nil {
634+
return fmt.Errorf(
635+
"an unexpected error occurred when converting the policyset to YAML: %w", err,
636+
)
637+
}
638+
639+
p.outputBuffer.Write([]byte("---\n"))
640+
p.outputBuffer.Write(policysetYAML)
641+
642+
return nil
643+
}
644+
535645
// getPlcFromPath finds the placement manifest in the input manifest file. It will return the name
536646
// of the placement, the unmarshaled placement manifest, and an error. An error is returned if the
537647
// placement manifest cannot be found or is invalid.

0 commit comments

Comments
 (0)