Skip to content

Commit

Permalink
Add new field gatekeeperEnforcementAction to override enforcementAction
Browse files Browse the repository at this point in the history
Add a new field of `gatekeeperEnforcementAction`
Default value of the field is unset and when unset, don't touch the constraint (existing behavior)
If set, then directly set `spec.enforcementAction` in the Gatekeeper constraint
Should be settable at the policyDefaults, policy, and manifest levels (i.e. like the `ConfigurationPolicyOptions` struct).
Ref: https://issues.redhat.com/browse/ACM-11076
Signed-off-by: yiraeChristineKim <[email protected]>
  • Loading branch information
yiraeChristineKim committed Jul 10, 2024
1 parent 91cbdec commit 53d65bb
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 1 deletion.
9 changes: 8 additions & 1 deletion docs/policygenerator-reference.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,10 @@ policyDefaults:
# Optional. Labels that the policy will include under its metadata.labels. It will be applied for all
# policies unless specified in the policy.
policyLabels: {}

# Optional. Overrides the spec.enforcementAction field of a Gatekeeper constraint.
# This only applies to Gatekeeper constraints and is ignored by other manifests.
# If not set, the spec.enforcementAction field is not changed.
gatekeeperEnforcementAction: "deny"
# Optional. Defaults for policy set generation. Any default value listed here can be overridden under an entry in the
# policySets array.
policySetDefaults:
Expand Down Expand Up @@ -260,6 +263,8 @@ policies:
# Optional. (See policyDefaults.severity for description.)
# Cannot be specified when policyDefaults.consolidateManifests is set to true.
severity: "low"
# Optional. (See policyDefaults.gatekeeperEnforcementAction for description.)
gatekeeperEnforcementAction: "warn"
# (Note: a path to a directory containing a Kustomize manifest is a supported alternative.) Optional. A
# Kustomize patch to apply to the manifest(s) at the path. If there are multiple manifests, the patch requires
# the apiVersion, kind, metadata.name, and metadata.namespace (if applicable) fields to be set so Kustomize can
Expand Down Expand Up @@ -351,6 +356,8 @@ policies:
policyAnnotations: {}
# Optional. (See policyDefaults.policyLabels for description.)
policyLabels: {}
# Optional. (See policyDefaults.gatekeeperEnforcementAction for description.)
gatekeeperEnforcementAction: "dryrun"

# Optional. The list of policy sets to create. To include a policy in a policy set, use policies[*].policySets or
# policyDefaults.policySets or policySets.policies.
Expand Down
8 changes: 8 additions & 0 deletions internal/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,10 @@ func (p *Plugin) applyDefaults(unmarshaledConfig map[string]interface{}) {
policy.MetadataComplianceType = p.PolicyDefaults.MetadataComplianceType
}

if policy.GatekeeperEnforcementAction == "" {
policy.GatekeeperEnforcementAction = p.PolicyDefaults.GatekeeperEnforcementAction
}

// Only use the policyDefault evaluationInterval value when it's not explicitly set on the policy.
if policy.EvaluationInterval.Compliant == "" {
set := isEvaluationIntervalSet(unmarshaledConfig, i, "compliant")
Expand Down Expand Up @@ -745,6 +749,10 @@ func (p *Plugin) applyDefaults(unmarshaledConfig map[string]interface{}) {
manifest.RecordDiff = policy.RecordDiff
}

if manifest.GatekeeperEnforcementAction == "" {
manifest.GatekeeperEnforcementAction = policy.GatekeeperEnforcementAction
}

if isManifestFieldSet(unmarshaledConfig, i, j, "extraDependencies") {
applyDefaultDependencyFields(manifest.ExtraDependencies, p.PolicyDefaults.Namespace)
} else {
Expand Down
143 changes: 143 additions & 0 deletions internal/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ metadata:
policyDefaults:
namespace: my-policies
consolidateManifests: false
gatekeeperEnforcementAction: deny
%s: %s
policies:
- name: policy-app
Expand All @@ -281,6 +282,7 @@ policies:
t.Fatal("Unexpected error", err)
}

assertEqual(t, p.PolicyDefaults.GatekeeperEnforcementAction, "deny")
assertEqual(t, p.Policies[0].ConsolidateManifests, false)

output, err := p.Generate()
Expand Down Expand Up @@ -1760,6 +1762,147 @@ spec:
assertEqual(t, output, expected)
}

func TestOverrideConstraintEnforcementAction(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
gatekeeperPath := path.Join(tmpDir, "gatekeeper.yaml")
yamlContent := `
apiVersion: constraints.gatekeeper.sh/v1
kind: MyConstrainingTemplate
metadata:
name: thisthingimconstraining
`

common := `
---
apiVersion: policy.open-cluster-management.io/v1
kind: Policy
metadata:
annotations:
policy.open-cluster-management.io/categories: CM Configuration Management
policy.open-cluster-management.io/controls: CM-2 Baseline Configuration
policy.open-cluster-management.io/description: ""
policy.open-cluster-management.io/standards: NIST SP 800-53
name: policy-gatekeeper
namespace: gatekeeper-policies
spec:
disabled: false
policy-templates:
- objectDefinition:
apiVersion: constraints.gatekeeper.sh/v1
kind: MyConstrainingTemplate
metadata:
annotations:
policy.open-cluster-management.io/severity: low
name: thisthingimconstraining
spec:
enforcementAction: `

err := os.WriteFile(gatekeeperPath, []byte(yamlContent), 0o666)
if err != nil {
t.Fatalf("Failed to write %s", gatekeeperPath)
}

tests := []struct {
policyConf types.PolicyConfig
policyDefaultEA string
expectedEA string
}{
{
policyConf: types.PolicyConfig{
Name: "policy-gatekeeper",
Manifests: []types.Manifest{
{
Path: gatekeeperPath,
GatekeeperOptions: types.GatekeeperOptions{GatekeeperEnforcementAction: "deny"},
},
},
},
policyDefaultEA: "",
expectedEA: "deny",
},
{
policyConf: types.PolicyConfig{
Name: "policy-gatekeeper",
Manifests: []types.Manifest{
{
Path: gatekeeperPath,
GatekeeperOptions: types.GatekeeperOptions{GatekeeperEnforcementAction: "warn"},
},
},
},
policyDefaultEA: "",
expectedEA: "warn",
},
{
policyConf: types.PolicyConfig{
Name: "policy-gatekeeper",
Manifests: []types.Manifest{
{
Path: gatekeeperPath,
},
},
},
policyDefaultEA: "deny",
expectedEA: "deny",
},
{
policyConf: types.PolicyConfig{
Name: "policy-gatekeeper",
Manifests: []types.Manifest{
{
Path: gatekeeperPath,
GatekeeperOptions: types.GatekeeperOptions{GatekeeperEnforcementAction: "dryrun"},
},
},
},
policyDefaultEA: "deny",
expectedEA: "dryrun",
},
{
policyConf: types.PolicyConfig{
Name: "policy-gatekeeper",
GatekeeperOptions: types.GatekeeperOptions{GatekeeperEnforcementAction: "dryrun"},
Manifests: []types.Manifest{
{
Path: gatekeeperPath,
},
},
},
policyDefaultEA: "deny",
expectedEA: "dryrun",
},
}

for _, tc := range tests {
p := Plugin{}

p.PolicyDefaults.Namespace = "gatekeeper-policies"
p.PolicyDefaults.InformGatekeeperPolicies = false

p.PolicyDefaults.GatekeeperEnforcementAction = tc.policyDefaultEA

p.Policies = append(p.Policies, tc.policyConf)

p.applyDefaults(map[string]interface{}{
"policyDefaults": map[string]interface{}{
"informGatekeeperPolicies": false,
},
})

err = p.createPolicy(&p.Policies[0])
if err != nil {
t.Fatal(err.Error())
}

output := p.outputBuffer.String()

expected := strings.TrimPrefix(common+tc.expectedEA+"\n", "\n")

assertEqual(t, output, expected)
}
}

func TestCreatePolicyWithDifferentRemediationAction(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
Expand Down
7 changes: 7 additions & 0 deletions internal/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,13 @@ type ConfigurationPolicyOptions struct {
RecreateOption string `json:"recreateOption,omitempty" yaml:"recreateOption,omitempty"`
}

type GatekeeperOptions struct {
GatekeeperEnforcementAction string `json:"gatekeeperEnforcementAction,omitempty" yaml:"gatekeeperEnforcementAction,omitempty"`
}

type Manifest struct {
ConfigurationPolicyOptions `json:",inline" yaml:",inline"`
GatekeeperOptions `json:",inline" yaml:",inline"`
Patches []map[string]interface{} `json:"patches,omitempty" yaml:"patches,omitempty"`
Path string `json:"path,omitempty" yaml:"path,omitempty"`
ExtraDependencies []PolicyDependency `json:"extraDependencies,omitempty" yaml:"extraDependencies,omitempty"`
Expand Down Expand Up @@ -105,6 +110,7 @@ type EvaluationInterval struct {
type PolicyConfig struct {
PolicyOptions `json:",inline" yaml:",inline"`
ConfigurationPolicyOptions `json:",inline" yaml:",inline"`
GatekeeperOptions `json:",inline" yaml:",inline"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
// This a slice of structs to allow additional configuration related to a manifest such as
// accepting patches.
Expand All @@ -114,6 +120,7 @@ type PolicyConfig struct {
type PolicyDefaults struct {
PolicyOptions `json:",inline" yaml:",inline"`
ConfigurationPolicyOptions `json:",inline" yaml:",inline"`
GatekeeperOptions `json:",inline" yaml:",inline"`
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
OrderPolicies bool `json:"orderPolicies,omitempty" yaml:"orderPolicies,omitempty"`
}
Expand Down
24 changes: 24 additions & 0 deletions internal/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,12 @@ func getPolicyTemplates(policyConf *types.PolicyConfig) ([]map[string]interface{
extraDeps := policyConf.Manifests[i].ExtraDependencies

for _, manifest := range manifestGroup {
err := setGatekeeperEnforcementAction(manifest,
policyConf.Manifests[i].GatekeeperEnforcementAction)
if err != nil {
return nil, err
}

isPolicyTypeManifest, isOcmPolicy, err := isPolicyTypeManifest(
manifest, policyConf.InformGatekeeperPolicies)
if err != nil {
Expand Down Expand Up @@ -217,6 +223,7 @@ func getPolicyTemplates(policyConf *types.PolicyConfig) ([]map[string]interface{
annotations[severityAnnotation] = policyConf.Severity

policyTemplateUnstructured.SetAnnotations(annotations)

}

policyTemplates = append(policyTemplates, policyTemplate)
Expand Down Expand Up @@ -310,6 +317,23 @@ func getPolicyTemplates(policyConf *types.PolicyConfig) ([]map[string]interface{
return policyTemplates, nil
}

// setGatekeeperEnforcementAction function override gatekeeper.constraint.enforcementAction
func setGatekeeperEnforcementAction(manifest map[string]interface{}, enforcementAction string) error {
apiVersion, found, err := unstructured.NestedString(manifest, "apiVersion")
if err != nil {
return fmt.Errorf("getting apiVersion has an error %w", err)
}

if found && strings.HasPrefix(apiVersion, "constraints.gatekeeper.sh") && enforcementAction != "" {
err := unstructured.SetNestedField(manifest, enforcementAction, "spec", "enforcementAction")
if err != nil {
return err
}
}

return nil
}

func setTemplateOptions(tmpl map[string]interface{}, ignorePending bool, extraDeps []types.PolicyDependency) {
if ignorePending {
tmpl["ignorePending"] = ignorePending
Expand Down

0 comments on commit 53d65bb

Please sign in to comment.