Skip to content

Commit

Permalink
Introduce Enforce flag on Policy CRD (#216)
Browse files Browse the repository at this point in the history
* introduce enforce flag instead of enabled

* update field description

* add new column to show policy status
  • Loading branch information
waleedhammam authored Dec 18, 2023
1 parent adbb15f commit 1c286d2
Show file tree
Hide file tree
Showing 21 changed files with 277 additions and 54 deletions.
6 changes: 4 additions & 2 deletions api/v2beta3/policy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@ type PolicySpec struct {
// Code contains the policy rego code
Code string `json:"code"`
// +optional
// Enabled flag for third parties consumers that indicates if this policy should be considered or not
Enabled bool `json:"enabled,omitempty"`
// +kubebuilder:default:=true
// Enforce flag to define whether a policy is enforced via the admission controller or just audited for a violation (default: true)
Enforce bool `json:"enforce,omitempty"`
// +optional
// Parameters are the inputs needed for the policy validation
Parameters []PolicyParameters `json:"parameters,omitempty"`
Expand Down Expand Up @@ -118,6 +119,7 @@ type PolicySpec struct {
//+kubebuilder:printcolumn:name="Severity",type=string,JSONPath=`.spec.severity`
//+kubebuilder:printcolumn:name="Category",type=string,JSONPath=`.spec.category`
//+kubebuilder:printcolumn:name="Provider",type=string,JSONPath=`.spec.provider`
//+kubebuilder:printcolumn:name="Enforced",type=string,JSONPath=`.spec.enforce`
//+kubebuilder:resource:scope=Cluster
//+kubebuilder:storageversion

Expand Down
11 changes: 8 additions & 3 deletions config/crd/bases/pac.weave.works_policies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,9 @@ spec:
- jsonPath: .spec.provider
name: Provider
type: string
- jsonPath: .spec.enforce
name: Enforced
type: string
name: v2beta3
schema:
openAPIV3Schema:
Expand Down Expand Up @@ -518,9 +521,11 @@ spec:
description:
description: Description is a summary of what that policy validates
type: string
enabled:
description: Enabled flag for third parties consumers that indicates
if this policy should be considered or not
enforce:
default: true
description: 'Enforce flag to define whether a policy is enforced
via the admission controller or just audited for a violation (default:
true)'
type: boolean
how_to_solve:
description: HowToSolve is a description of the steps required to
Expand Down
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ module github.com/weaveworks/policy-agent

go 1.20

replace github.com/weaveworks/policy-agent/api v1.0.5 => ./api/ // TODO: change when release API
replace (
github.com/weaveworks/policy-agent/api v1.0.5 => ./api/ // TODO: change when release API
github.com/weaveworks/policy-agent/pkg/policy-core v1.2.0 => ./pkg/policy-core // TODO: change when release API
)

require (
github.com/elastic/go-elasticsearch/v7 v7.17.7
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -329,8 +329,6 @@ github.com/weaveworks/policy-agent/pkg/logger v1.1.0 h1:LwWacSwGApgqniM0wzBS1XcA
github.com/weaveworks/policy-agent/pkg/logger v1.1.0/go.mod h1:bhlwMW3rcPE294XrmlDYvx4EkRCbAzEqXBT9LI4KIFA=
github.com/weaveworks/policy-agent/pkg/opa-core v1.1.0 h1:++9oPP/cNuo2SUbBjFim1o0YM8g42kcNWgXMBCOdl7w=
github.com/weaveworks/policy-agent/pkg/opa-core v1.1.0/go.mod h1:ZY18awHQq5tvh4lL9PvaRbP34qOIw709wGcKhtf6ZbI=
github.com/weaveworks/policy-agent/pkg/policy-core v1.2.0 h1:n7B1SazaMKcr/xfp9JdnaXm24T+zAGnxb5vA4pC41tQ=
github.com/weaveworks/policy-agent/pkg/policy-core v1.2.0/go.mod h1:+XSiyuRBM+RXWGeh2RLUbKQ3nlBFbmmsPAFzG7onKKg=
github.com/weaveworks/policy-agent/pkg/uuid-go v0.1.0 h1:2zODdAnMFAgYhNJj/Oe59OxG98LWuFRxF7cIovPnSVE=
github.com/weaveworks/policy-agent/pkg/uuid-go v0.1.0/go.mod h1:norQi1tZAienB1ad0kAbiJlTe85j+LVef5P53kW7qAo=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
Expand Down
11 changes: 8 additions & 3 deletions helm/crds/pac.weave.works_policies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,9 @@ spec:
- jsonPath: .spec.provider
name: Provider
type: string
- jsonPath: .spec.enforce
name: Enforced
type: string
name: v2beta3
schema:
openAPIV3Schema:
Expand Down Expand Up @@ -518,9 +521,11 @@ spec:
description:
description: Description is a summary of what that policy validates
type: string
enabled:
description: Enabled flag for third parties consumers that indicates
if this policy should be considered or not
enforce:
default: true
description: 'Enforce flag to define whether a policy is enforced
via the admission controller or just audited for a violation (default:
true)'
type: boolean
how_to_solve:
description: HowToSolve is a description of the steps required to
Expand Down
13 changes: 12 additions & 1 deletion internal/admission/admission.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,18 @@ func (a *AdmissionHandler) Handle(ctx context.Context, req ctrlAdmission.Request
}

if len(result.Violations) > 0 {
return ctrlAdmission.ValidationResponse(false, generateResponse(result.Violations))
// If a resource has multiple policies evaluated
// and any of those policies are violated and has the enforce flag equals true
// then the resource submission is blocked.
allowed := true
for _, violation := range result.Violations {
if violation.Enforced {
allowed = false
break
}
}

return ctrlAdmission.ValidationResponse(allowed, generateResponse(result.Violations))
}

return ctrlAdmission.ValidationResponse(true, "")
Expand Down
39 changes: 37 additions & 2 deletions internal/admission/admission_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,10 @@ func TestAdmissionHandler_Handle(t *testing.T) {
Allowed: false,
Result: &metav1.Status{
Reason: metav1.StatusReason(generateResponse([]domain.PolicyValidation{
{Message: "violation"},
{
Message: "violation",
Enforced: true,
},
})),
Code: http.StatusForbidden,
},
Expand All @@ -148,7 +151,39 @@ func TestAdmissionHandler_Handle(t *testing.T) {
val.EXPECT().Validate(gomock.Any(), gomock.Any(), gomock.Any()).
Times(1).Return(&domain.PolicyValidationSummary{
Violations: []domain.PolicyValidation{
{Message: "violation"},
{
Message: "violation",
Enforced: true,
},
},
}, nil)
},
},
{
name: "test allowed (not enforced)",
body: testdata.ValidadmissionBody,
wantResponse: ctrlAdmission.Response{
AdmissionResponse: v1.AdmissionResponse{
Allowed: true,
Result: &metav1.Status{
Reason: metav1.StatusReason(generateResponse([]domain.PolicyValidation{
{
Message: "violation",
Enforced: false,
},
})),
Code: http.StatusOK,
},
},
},
loadStubs: func(val *validationmock.MockValidator) {
val.EXPECT().Validate(gomock.Any(), gomock.Any(), gomock.Any()).
Times(1).Return(&domain.PolicyValidationSummary{
Violations: []domain.PolicyValidation{
{
Message: "violation",
Enforced: false,
},
},
}, nil)
},
Expand Down
2 changes: 1 addition & 1 deletion internal/policies/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func (p *PoliciesWatcher) GetAll(ctx context.Context) ([]domain.Policy, error) {
Name: policyCRD.Name,
ID: policyCRD.ID,
Code: policyCRD.Code,
Enabled: policyCRD.Enabled,
Enforce: policyCRD.Enforce,
Targets: domain.PolicyTargets{
Kinds: policyCRD.Targets.Kinds,
Labels: policyCRD.Targets.Labels,
Expand Down
6 changes: 3 additions & 3 deletions pkg/policy-core/domain/mutation_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package domain

import (
"io/ioutil"
"os"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -55,7 +55,7 @@ func TestMutation(t *testing.T) {
mutated, err := result.NewResource()
assert.Nil(t, err)

expectedMutatedEntity, err := ioutil.ReadFile(tt.mutatedEntityFile)
expectedMutatedEntity, err := os.ReadFile(tt.mutatedEntityFile)
assert.Nil(t, err)

assert.YAMLEq(t, string(expectedMutatedEntity), string(mutated))
Expand All @@ -64,7 +64,7 @@ func TestMutation(t *testing.T) {
}

func getEntityFromFile(path string) (Entity, error) {
raw, err := ioutil.ReadFile(path)
raw, err := os.ReadFile(path)
if err != nil {
return Entity{}, err
}
Expand Down
3 changes: 1 addition & 2 deletions pkg/policy-core/domain/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type Policy struct {
Name string `json:"name"`
ID string `json:"id"`
Code string `json:"code"`
Enabled bool `json:"enabled"`
Enforce bool `json:"enforce"`
Parameters []PolicyParameters `json:"parameters"`
Targets PolicyTargets `json:"targets"`
Description string `json:"description"`
Expand All @@ -41,7 +41,6 @@ type Policy struct {
Standards []PolicyStandard `json:"standards"`
Reference interface{} `json:"-"`
GitCommit string `json:"git_commit,omitempty"`
Modes []string `json:"modes"`
Mutate bool `json:"mutate"`
}

Expand Down
15 changes: 12 additions & 3 deletions pkg/policy-core/domain/policy_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package domain
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -60,6 +61,7 @@ type PolicyValidation struct {
Trigger string `json:"trigger"`
CreatedAt time.Time `json:"created_at"`
Metadata interface{} `json:"metadata"`
Enforced bool `json:"enforced"`
}

// PolicyValidationSummary contains violation and compliance result of a validate operation
Expand Down Expand Up @@ -138,7 +140,7 @@ func NewK8sEventFromPolicyValidation(result PolicyValidation) (*v1.Event, error)
"description": result.Policy.Description,
"how_to_solve": result.Policy.HowToSolve,
"parameters": string(parameters),
"modes": strings.Join(result.Policy.Modes, ","),
"enforce": fmt.Sprint(result.Policy.Enforce),
}

namespace := result.Entity.Namespace
Expand Down Expand Up @@ -186,6 +188,13 @@ func NewPolicyValidationFRomK8sEvent(event *v1.Event) (PolicyValidation, error)
} else {
status = PolicyValidationStatusCompliant
}

enforced, err := strconv.ParseBool(annotations["enforce"])
if err != nil {
// defaults to true if not available
enforced = true
}

policyValidation := PolicyValidation{
AccountID: annotations["account_id"],
ClusterID: annotations["cluster_id"],
Expand All @@ -195,6 +204,7 @@ func NewPolicyValidationFRomK8sEvent(event *v1.Event) (PolicyValidation, error)
CreatedAt: event.FirstTimestamp.Time,
Message: event.Message,
Status: status,
Enforced: enforced,
Policy: Policy{
ID: annotations["policy_id"],
Name: annotations["policy_name"],
Expand All @@ -204,7 +214,6 @@ func NewPolicyValidationFRomK8sEvent(event *v1.Event) (PolicyValidation, error)
HowToSolve: annotations["how_to_solve"],
Reference: event.Related,
Tags: strings.Split(annotations["tags"], ","),
Modes: strings.Split(annotations["modes"], ","),
},
Entity: Entity{
APIVersion: event.InvolvedObject.APIVersion,
Expand All @@ -215,7 +224,7 @@ func NewPolicyValidationFRomK8sEvent(event *v1.Event) (PolicyValidation, error)
ResourceVersion: event.InvolvedObject.ResourceVersion,
},
}
err := json.Unmarshal([]byte(annotations["standards"]), &policyValidation.Policy.Standards)
err = json.Unmarshal([]byte(annotations["standards"]), &policyValidation.Policy.Standards)
if err != nil {
return policyValidation, fmt.Errorf("failed to get standards from event: %w", err)
}
Expand Down
13 changes: 7 additions & 6 deletions pkg/policy-core/domain/policy_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package domain

import (
"encoding/json"
"fmt"
"strings"
"testing"
"time"
Expand All @@ -19,6 +20,7 @@ func TestPolicyToEvent(t *testing.T) {
Name: "my-policy",
Category: "my-category",
Severity: "low",
Enforce: true,
Reference: v1.ObjectReference{
UID: "my-policy",
APIVersion: "pac.weave.works/v1",
Expand All @@ -42,7 +44,6 @@ func TestPolicyToEvent(t *testing.T) {
ConfigRef: "config-1",
},
},
Modes: []string{"audit", "admission"},
}

entity := Entity{
Expand All @@ -65,6 +66,7 @@ func TestPolicyToEvent(t *testing.T) {
Type: "Admission",
Trigger: "Admission",
CreatedAt: time.Now(),
Enforced: true,
Occurrences: []Occurrence{
{
Message: "test",
Expand All @@ -75,6 +77,7 @@ func TestPolicyToEvent(t *testing.T) {
Policy: policy,
Entity: entity,
Status: PolicyValidationStatusCompliant,
Enforced: true,
Message: "message",
Type: "Audit",
Trigger: "PolicyChange",
Expand Down Expand Up @@ -135,13 +138,13 @@ func TestPolicyToEvent(t *testing.T) {
"severity": result.Policy.Severity,
"category": result.Policy.Category,
"description": result.Policy.Description,
"enforce": fmt.Sprint(result.Policy.Enforce),
"how_to_solve": result.Policy.HowToSolve,
"tags": strings.Join(result.Policy.Tags, ","),
"standards": string(standards),
"entity_manifest": string(manifest),
"occurrences": string(occurrences),
"parameters": string(parameters),
"modes": "audit,admission",
})
assert.Equal(t, event.Labels, map[string]string{
PolicyValidationIDLabel: result.ID,
Expand All @@ -166,6 +169,7 @@ func TestEventToPolicy(t *testing.T) {
"account_id": uuid.NewV4().String(),
"cluster_id": uuid.NewV4().String(),
"policy_id": uuid.NewV4().String(),
"enforce": "true",
"description": uuid.NewV4().String(),
"how_to_solve": uuid.NewV4().String(),
"policy_name": "my-policy",
Expand All @@ -175,7 +179,6 @@ func TestEventToPolicy(t *testing.T) {
"entity_manifest": `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"name":"nginx-deployment","namespace":"default","uid":"af912668-957b-46d4-bc7a-51e6994cba56"},"spec":{"template":{"spec":{"containers":[{"image":"nginx:latest","imagePullPolicy":"Always","name":"nginx","ports":[{"containerPort":80,"protocol":"TCP"}]}]}}}}`,
"standards": `[{"id":"weave.standards.cis-benchmark","controls":["weave.controls.cis-benchmark.5.5.1"]},{"id":"weave.standards.mitre-attack","controls":["weave.controls.mitre-attack.1.2"]},{"id":"weave.standards.gdpr","controls":["weave.controls.gdpr.25","weave.controls.gdpr.32","weave.controls.gdpr.24"]},{"id":"weave.standards.soc2-type-i","controls":["weave.controls.soc2-type-i.1.6.8"]}]`,
"occurrences": `[{"message":"test1"},{"message":"test2"}]`,
"modes": "audit,admission",
},
Labels: map[string]string{
PolicyValidationIDLabel: uuid.NewV4().String(),
Expand Down Expand Up @@ -218,22 +221,20 @@ func TestEventToPolicy(t *testing.T) {
occurrences, err := json.Marshal(policyValidation.Occurrences)
assert.Nil(t, err)

assert.Equal(t, []string{"audit", "admission"}, policyValidation.Policy.Modes)

assert.Equal(t, event.Annotations, map[string]string{
"account_id": policyValidation.AccountID,
"cluster_id": policyValidation.ClusterID,
"policy_id": policyValidation.Policy.ID,
"policy_name": policyValidation.Policy.Name,
"severity": policyValidation.Policy.Severity,
"category": policyValidation.Policy.Category,
"enforce": fmt.Sprint(policyValidation.Enforced),
"description": policyValidation.Policy.Description,
"how_to_solve": policyValidation.Policy.HowToSolve,
"tags": strings.Join(policyValidation.Policy.Tags, ","),
"standards": string(standards),
"occurrences": string(occurrences),
"entity_manifest": string(manifest),
"modes": "audit,admission",
})

assert.Equal(t, event.Labels, map[string]string{
Expand Down
Loading

0 comments on commit 1c286d2

Please sign in to comment.