Skip to content

Commit

Permalink
Introduce Exclusions on PolicyCRD instead of rego (#217)
Browse files Browse the repository at this point in the history
  • Loading branch information
waleedhammam authored Dec 21, 2023
1 parent 1c286d2 commit a51ffe4
Show file tree
Hide file tree
Showing 15 changed files with 340 additions and 117 deletions.
19 changes: 19 additions & 0 deletions api/v2beta3/policy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,20 @@ type PolicyStandard struct {
Controls []string `json:"controls,omitempty"`
}

// PolicyExclusions are the structure which resources should not be evaluated against the policy
type PolicyExclusions struct {
// +optional
// Namespaces is a list of Kubernetes namespaces that a resource needs to be a part of to excluded from this policy
Namespaces []string `json:"namespaces"`
// +optional
// Resources is a list of Kubernetes resources that are excluded by this policy (namespace/name)
Resources []string `json:"resources"`
// +optional
// Labels is a list of Kubernetes labels that are needed to excluded the policy against a resource
// this filter is statisfied if only one label existed, using * for value make it so it will match if the key exists regardless of its value
Labels []map[string]string `json:"labels"`
}

// PolicySpec defines the desired state of Policy
// It describes all that is needed to evaluate a resource against a rego code
// +kubebuilder:object:generate:true
Expand Down Expand Up @@ -113,6 +127,11 @@ type PolicySpec struct {
//+kubebuilder:default:=false
// Mutate is a flag that indicates whether to enable mutation of resources violating this policy or not
Mutate bool `json:"mutate"`

// +optional
// Exclude describes the policy exclusions on (Namespaces, Labels, Resources)
// Select one or more by defining the exclusion list
Exclude PolicyExclusions `json:"exclude,omitempty"`
}

//+kubebuilder:object:root=true
Expand Down
39 changes: 39 additions & 0 deletions api/v2beta3/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions config/crd/bases/pac.weave.works_policies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,34 @@ spec:
via the admission controller or just audited for a violation (default:
true)'
type: boolean
exclude:
description: Exclude describes the policy exclusions on (Namespaces,
Labels, Resources) Select one or more by defining the exclusion
list
properties:
labels:
description: Labels is a list of Kubernetes labels that are needed
to excluded the policy against a resource this filter is statisfied
if only one label existed, using * for value make it so it will
match if the key exists regardless of its value
items:
additionalProperties:
type: string
type: object
type: array
namespaces:
description: Namespaces is a list of Kubernetes namespaces that
a resource needs to be a part of to excluded from this policy
items:
type: string
type: array
resources:
description: Resources is a list of Kubernetes resources that
are excluded by this policy (namespace/name)
items:
type: string
type: array
type: object
how_to_solve:
description: HowToSolve is a description of the steps required to
solve the issues reported by the policy
Expand Down
28 changes: 28 additions & 0 deletions helm/crds/pac.weave.works_policies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,34 @@ spec:
via the admission controller or just audited for a violation (default:
true)'
type: boolean
exclude:
description: Exclude describes the policy exclusions on (Namespaces,
Labels, Resources) Select one or more by defining the exclusion
list
properties:
labels:
description: Labels is a list of Kubernetes labels that are needed
to excluded the policy against a resource this filter is statisfied
if only one label existed, using * for value make it so it will
match if the key exists regardless of its value
items:
additionalProperties:
type: string
type: object
type: array
namespaces:
description: Namespaces is a list of Kubernetes namespaces that
a resource needs to be a part of to excluded from this policy
items:
type: string
type: array
resources:
description: Resources is a list of Kubernetes resources that
are excluded by this policy (namespace/name)
items:
type: string
type: array
type: object
how_to_solve:
description: HowToSolve is a description of the steps required to
solve the issues reported by the policy
Expand Down
5 changes: 5 additions & 0 deletions internal/policies/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ func (p *PoliciesWatcher) GetAll(ctx context.Context) ([]domain.Policy, error) {
ResourceVersion: policiesCRD.Items[i].ResourceVersion,
},
Mutate: policyCRD.Mutate,
Exclude: domain.PolicyExclusions{
Namespaces: policyCRD.Exclude.Namespaces,
Resources: policyCRD.Exclude.Resources,
Labels: policyCRD.Exclude.Labels,
},
}

for _, standardCRD := range policyCRD.Standards {
Expand Down
8 changes: 8 additions & 0 deletions pkg/policy-core/domain/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ type PolicyStandard struct {
Controls []string `json:"controls"`
}

// PolicyExclusions are the structure which resources should not be evaluated against the policy
type PolicyExclusions struct {
Namespaces []string `json:"namespaces"`
Resources []string `json:"resources"`
Labels []map[string]string `json:"labels"`
}

// Policy represents a policy
type Policy struct {
Name string `json:"name"`
Expand All @@ -42,6 +49,7 @@ type Policy struct {
Reference interface{} `json:"-"`
GitCommit string `json:"git_commit,omitempty"`
Mutate bool `json:"mutate"`
Exclude PolicyExclusions `json:"exclude"`
}

// ObjectRef returns the kubernetes object reference of the policy
Expand Down
32 changes: 32 additions & 0 deletions pkg/policy-core/validation/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package validation

import (
"context"
"fmt"

"github.com/weaveworks/policy-agent/pkg/policy-core/domain"
)
Expand Down Expand Up @@ -56,6 +57,37 @@ func matchEntity(entity domain.Entity, policy domain.Policy) bool {
return matchKind && matchNamespace && matchLabel
}

// isExcluded evaluates the policy exclusion against the requested entity
func isExcluded(entity domain.Entity, policy domain.Policy) bool {
resourceNamespace := entity.Namespace
for _, namespace := range policy.Exclude.Namespaces {
if resourceNamespace == namespace {
return true
}
}

resourceName := fmt.Sprintf("%s/%s", entity.Namespace, entity.Name)
for _, resource := range policy.Exclude.Resources {
if resourceName == resource {
return true
}
}

for _, obj := range policy.Exclude.Labels {
for key, val := range obj {
entityVal, ok := entity.Labels[key]
if ok {
if val != "*" && val != entityVal {
continue
}
return true
}
}
}

return false
}

func writeToSinks(
ctx context.Context,
resultsSinks []domain.PolicyValidationSink,
Expand Down
3 changes: 3 additions & 0 deletions pkg/policy-core/validation/opa_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ func (v *OpaValidator) Validate(ctx context.Context, entity domain.Entity, trigg
if !matchEntity(entity, policy) {
return
}
if isExcluded(entity, policy) {
return
}

opaPolicy, err := opa.Parse(policy.Code, PolicyQuery)
if err != nil {
Expand Down
80 changes: 80 additions & 0 deletions pkg/policy-core/validation/opa_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,86 @@ func TestOpaValidator_Validate(t *testing.T) {
},
wantErr: false,
},
{
name: "entity namespace exclusion",
init: init{
writeCompliance: false,
loadStubs: func(policiesSource *mock.MockPoliciesSource, sink *mock.MockPolicyValidationSink) {
imageTag := testdata.Policies["imageTagExcluded"]
policiesSource.EXPECT().GetAll(gomock.Any()).
Times(1).Return([]domain.Policy{
imageTag,
}, nil)
policiesSource.EXPECT().GetPolicyConfig(gomock.Any(), gomock.Any()).
Times(1).Return(nil, nil)
},
},
entity: entity,
want: &domain.PolicyValidationSummary{
Violations: []domain.PolicyValidation{},
},
wantErr: false,
},
{
name: "entity labels exclusion",
init: init{
writeCompliance: false,
loadStubs: func(policiesSource *mock.MockPoliciesSource, sink *mock.MockPolicyValidationSink) {
imageTag := testdata.Policies["imageTagExcluded"]
policiesSource.EXPECT().GetAll(gomock.Any()).
Times(1).Return([]domain.Policy{
imageTag,
}, nil)
policiesSource.EXPECT().GetPolicyConfig(gomock.Any(), gomock.Any()).
Times(1).Return(nil, nil)
},
},
entity: entity,
want: &domain.PolicyValidationSummary{
Violations: []domain.PolicyValidation{},
},
wantErr: false,
},
{
name: "entity resource exclusion",
init: init{
writeCompliance: false,
loadStubs: func(policiesSource *mock.MockPoliciesSource, sink *mock.MockPolicyValidationSink) {
missingOwner := testdata.Policies["missingOwner"]
imageTag := testdata.Policies["imageTag"]
imageTag.Exclude.Resources = []string{"unit-testing/nginx-deployment"}
policiesSource.EXPECT().GetAll(gomock.Any()).
Times(1).Return([]domain.Policy{
missingOwner,
imageTag,
}, nil)
policiesSource.EXPECT().GetPolicyConfig(gomock.Any(), gomock.Any()).
Times(1).Return(nil, nil)
sink.EXPECT().Write(gomock.Any(), gomock.Any()).
Times(1).Return(nil)
},
},
entity: entity,
want: &domain.PolicyValidationSummary{
Violations: []domain.PolicyValidation{
{
Policy: testdata.Policies["missingOwner"],
Entity: entity,
Type: validationType,
Status: domain.PolicyValidationStatusViolating,
Trigger: validationType,
Message: "Missing owner label in metadata in deployment nginx-deployment (1 occurrences)",
Occurrences: []domain.Occurrence{
{
Message: "you are missing a label with the key 'owner'",
},
},
Enforced: false,
},
},
},
wantErr: false,
},
}

for _, tt := range tests {
Expand Down
Loading

0 comments on commit a51ffe4

Please sign in to comment.