Skip to content

Commit

Permalink
Add option to wrap Gatekeeper manifests directly
Browse files Browse the repository at this point in the history
When `informGatekeeperPolicies` is set to `false`, wrap Gatekeeper
manifests directly in a Policy rather than in a ConfigurationPolicy.

ref: https://issues.redhat.com/browse/ACM-4438
Signed-off-by: Dale Haiducek <[email protected]>
  • Loading branch information
dhaiducek authored and openshift-merge-robot committed Apr 12, 2023
1 parent 1761673 commit 3e82d9f
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 36 deletions.
4 changes: 4 additions & 0 deletions docs/policygenerator-reference.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ policyDefaults:
# Optional. Determines whether to treat the policy as compliant when it is waiting for its dependencies to reach their
# desired states. Defaults to false.
ignorePending: false
# Deprecated: Set informGatekeeperPolicies to false to use Gatekeeper manifests directly without wrapping in a
# ConfigurationPolicy.
# Optional. When the policy references a Gatekeeper policy manifest, this determines if an additional configuration
# policy should be generated in order to receive policy violations in Open Cluster Management when the Gatekeeper
# policy has been violated. This defaults to true.
Expand Down Expand Up @@ -281,6 +283,8 @@ policies:
pruneObjectBehavior: ""
# Optional. (See policyDefaults.ignorePending for description.)
ignorePending: false
# Deprecated: Set informGatekeeperPolicies to false to use Gatekeeper manifests
# directly without wrapping in a ConfigurationPolicy.
# Optional. (See policyDefaults.informGatekeeperPolicies for description.)
informGatekeeperPolicies: true
# Optional. (See policyDefaults.informKyvernoPolicies for description.)
Expand Down
4 changes: 4 additions & 0 deletions internal/plugin_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,8 @@ policies:

expectedNsSelector := types.NamespaceSelector{Exclude: nil, Include: nil}

assertEqual(t, p.PolicyDefaults.InformGatekeeperPolicies, true)
assertEqual(t, p.PolicyDefaults.InformKyvernoPolicies, true)
assertReflectEqual(t, p.PolicyDefaults.NamespaceSelector, expectedNsSelector)
assertEqual(t, p.PolicyDefaults.Placement.PlacementRulePath, "")
assertEqual(t, len(p.PolicyDefaults.Placement.ClusterSelectors), 0)
Expand Down Expand Up @@ -257,6 +259,8 @@ policies:
assertEqual(t, policy.RemediationAction, "inform")
assertEqual(t, policy.Severity, "low")
assertReflectEqual(t, policy.Standards, []string{"NIST SP 800-53"})
assertEqual(t, policy.InformGatekeeperPolicies, true)
assertEqual(t, policy.InformKyvernoPolicies, true)
}

func TestConfigNoNamespace(t *testing.T) {
Expand Down
118 changes: 118 additions & 0 deletions internal/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1163,6 +1163,124 @@ spec:
assertEqual(t, output, expected)
}

func TestCreatePolicyWithGkConstraintTemplate(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
gatekeeperPath := path.Join(tmpDir, "gatekeeper.yaml")
yamlContent := `
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: myconstrainingtemplate
`

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

p := Plugin{}

p.PolicyDefaults.Namespace = "gatekeeper-policies"
p.PolicyDefaults.InformGatekeeperPolicies = false
policyConf := types.PolicyConfig{
Name: "policy-gatekeeper",
Manifests: []types.Manifest{
{Path: path.Join(tmpDir, "gatekeeper.yaml")},
},
}
p.Policies = append(p.Policies, policyConf)
p.applyDefaults(map[string]interface{}{})

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

output := p.outputBuffer.String()
expected := `
---
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/standards: NIST SP 800-53
name: policy-gatekeeper
namespace: gatekeeper-policies
spec:
disabled: false
policy-templates:
- objectDefinition:
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: myconstrainingtemplate
`
expected = strings.TrimPrefix(expected, "\n")
assertEqual(t, output, expected)
}

func TestCreatePolicyWithGkConstraint(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
`

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

p := Plugin{}

p.PolicyDefaults.Namespace = "gatekeeper-policies"
p.PolicyDefaults.InformGatekeeperPolicies = false
policyConf := types.PolicyConfig{
Name: "policy-gatekeeper",
Manifests: []types.Manifest{
{Path: path.Join(tmpDir, "gatekeeper.yaml")},
},
}
p.Policies = append(p.Policies, policyConf)
p.applyDefaults(map[string]interface{}{})

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

output := p.outputBuffer.String()
expected := `
---
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/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:
name: thisthingimconstraining
`
expected = strings.TrimPrefix(expected, "\n")
assertEqual(t, output, expected)
}

func TestCreatePolicyWithDifferentRemediationAction(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
Expand Down
37 changes: 24 additions & 13 deletions internal/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ func getPolicyTemplates(policyConf *types.PolicyConfig) ([]map[string]interface{
extraDeps := policyConf.Manifests[i].ExtraDependencies

for _, manifest := range manifestGroup {
isPolicyTypeManifest, err := isPolicyTypeManifest(manifest)
isPolicyTypeManifest, isOcmPolicy, err := isPolicyTypeManifest(manifest)
if err != nil {
return nil, fmt.Errorf(
"%w in manifest path: %s",
Expand All @@ -186,7 +186,10 @@ func getPolicyTemplates(policyConf *types.PolicyConfig) ([]map[string]interface{
if isPolicyTypeManifest {
policyTemplate := map[string]interface{}{"objectDefinition": manifest}

setTemplateOptions(manifest, ignorePending, extraDeps)
// Only set dependency options if it's an OCM policy
if isOcmPolicy {
setTemplateOptions(manifest, ignorePending, extraDeps)
}

policyTemplates = append(policyTemplates, policyTemplate)

Expand Down Expand Up @@ -280,41 +283,49 @@ func setTemplateOptions(tmpl map[string]interface{}, ignorePending bool, extraDe
}
}

// isPolicyTypeManifest determines if the manifest is a non-root policy manifest
// by checking apiVersion and kind fields.
// Return error when apiVersion and kind fields aren't string, or if the manifest
// is a non-root policy manifest, but it is invalid because it is missing a name.
func isPolicyTypeManifest(manifest map[string]interface{}) (bool, error) {
// isPolicyTypeManifest determines whether the manifest is a kind handled by the generator and
// whether the manifest is a non-root OCM policy manifest by checking apiVersion and kind fields.
// Return error when:
// - apiVersion and kind fields can't be determined
// - the manifest is a root policy manifest
// - the manifest is invalid because it is missing a name
func isPolicyTypeManifest(manifest map[string]interface{}) (bool, bool, error) {
apiVersion, found, err := unstructured.NestedString(manifest, "apiVersion")
if !found || err != nil {
return false, errors.New("invalid or not found apiVersion")
return false, false, errors.New("invalid or not found apiVersion")
}

kind, found, err := unstructured.NestedString(manifest, "kind")
if !found || err != nil {
return false, errors.New("invalid or not found kind")
return false, false, errors.New("invalid or not found kind")
}

// Don't allow generation for root Policies
isOcmAPI := strings.HasPrefix(apiVersion, "policy.open-cluster-management.io")
if isOcmAPI && kind == "Policy" {
return false, errors.New("providing a root Policy kind is not supported by the generator; " +
return false, false, errors.New("providing a root Policy kind is not supported by the generator; " +
"the manifest should be applied to the hub cluster directly")
}

// Identify OCM Policies
isPolicy := isOcmAPI && kind != "Policy" && strings.HasSuffix(kind, "Policy")
isOcmPolicy := isOcmAPI && kind != "Policy" && strings.HasSuffix(kind, "Policy")

// Identify Gatekeeper kinds
isGkConstraintTemplate := strings.HasPrefix(apiVersion, "templates.gatekeeper.sh") && kind == "ConstraintTemplate"
isGkConstraint := strings.HasPrefix(apiVersion, "constraints.gatekeeper.sh")
isGkObj := isGkConstraintTemplate || isGkConstraint

isPolicy := isOcmPolicy || isGkObj

if isPolicy {
// metadata.name is required on policy manifests
_, found, err = unstructured.NestedString(manifest, "metadata", "name")
if !found || err != nil {
return true, errors.New("invalid or not found metadata.name")
return isPolicy, isOcmPolicy, errors.New("invalid or not found metadata.name")
}
}

return isPolicy, nil
return isPolicy, isOcmPolicy, nil
}

// setNamespaceSelector sets the namespace selector, if set, on the input policy template.
Expand Down
Loading

0 comments on commit 3e82d9f

Please sign in to comment.