Skip to content

Commit

Permalink
adding scopedenforcementactions
Browse files Browse the repository at this point in the history
Signed-off-by: Jaydip Gabani <[email protected]>
  • Loading branch information
JaydipGabani committed Mar 21, 2024
1 parent 9413112 commit 2f9f4bb
Show file tree
Hide file tree
Showing 26 changed files with 458 additions and 307 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ module github.com/open-policy-agent/gatekeeper/v3

go 1.21

replace github.com/open-policy-agent/frameworks/constraint => /mount/d/go/src/github.com/open-policy-agent/frameworks/constraint

require (
cloud.google.com/go/trace v1.10.6
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.44.0
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,6 @@ github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo=
github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0=
github.com/open-policy-agent/cert-controller v0.10.1 h1:RXSYoyn8FdCenWecRP//UV5nbVfmstNpj4kHQFkvPK4=
github.com/open-policy-agent/cert-controller v0.10.1/go.mod h1:4uRbBLY5DsPOog+a9pqk3JLxuuhrWsbUedQW65HcLTI=
github.com/open-policy-agent/frameworks/constraint v0.0.0-20240320172527-359cf1b785c9 h1:pCYBnD9sTC1c6S/NdghkKnWYNOP+Q4ZxEZwxI9tTqvQ=
github.com/open-policy-agent/frameworks/constraint v0.0.0-20240320172527-359cf1b785c9/go.mod h1:+DLsOmKpO561Scs6UKt4+Oxutbi0csbJWJe756NI5yU=
github.com/open-policy-agent/opa v0.62.1 h1:UcxBQ0fe6NEjkYc775j4PWoUFFhx4f6yXKIKSTAuTVk=
github.com/open-policy-agent/opa v0.62.1/go.mod h1:YqiSIIuvKwyomtnnXkJvy0E3KtVKbavjPJ/hNMuOmeM=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
Expand Down
6 changes: 3 additions & 3 deletions pkg/audit/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,7 @@ func (am *Manager) auditFromCache(ctx context.Context) ([]Result, []error) {
Object: obj,
Namespace: ns,
}
resp, err := am.opa.Review(ctx, au, drivers.Stats(*logStatsAudit))
resp, err := am.opa.Review(ctx, au, util.AuditEnforcementPoint, drivers.Stats(*logStatsAudit))
if err != nil {
am.log.Error(err, fmt.Sprintf("Unable to review object from audit cache %v %s/%s", obj.GroupVersionKind().String(), obj.GetNamespace(), obj.GetName()))
continue
Expand Down Expand Up @@ -697,7 +697,7 @@ func (am *Manager) reviewObjects(ctx context.Context, kind string, folderCount i
Source: mutationtypes.SourceTypeOriginal,
}

resp, err := am.opa.Review(ctx, augmentedObj, drivers.Stats(*logStatsAudit))
resp, err := am.opa.Review(ctx, augmentedObj, util.AuditEnforcementPoint, drivers.Stats(*logStatsAudit))
if err != nil {
am.log.Error(err, "Unable to review object from file", "fileName", fileName, "objNs", objNs)
continue
Expand All @@ -721,7 +721,7 @@ func (am *Manager) reviewObjects(ctx context.Context, kind string, folderCount i
Namespace: ns,
Source: mutationtypes.SourceTypeGenerated,
}
resultantResp, err := am.opa.Review(ctx, au, drivers.Stats(*logStatsAudit))
resultantResp, err := am.opa.Review(ctx, au, util.AuditEnforcementPoint, drivers.Stats(*logStatsAudit))
if err != nil {
am.log.Error(err, "Unable to review expanded object", "objName", (*resultant.Obj).GetName(), "objNs", ns)
continue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ func TestReconcile(t *testing.T) {
Name: "FooNamespace",
Object: runtime.RawExtension{Object: ns},
}
resp, err := cfClient.Review(ctx, req)
resp, err := cfClient.Review(ctx, req, util.AuditEnforcementPoint)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -655,7 +655,7 @@ func TestReconcile(t *testing.T) {
Name: "FooNamespace",
Object: runtime.RawExtension{Object: ns},
}
resp, err := cfClient.Review(ctx, req)
resp, err := cfClient.Review(ctx, req, util.AuditEnforcementPoint)
if err != nil {
t.Fatal(err)
}
Expand All @@ -676,7 +676,7 @@ func TestReconcile(t *testing.T) {
err = retry.OnError(testutils.ConstantRetry, func(err error) bool {
return true
}, func() error {
resp, err := cfClient.Review(ctx, req)
resp, err := cfClient.Review(ctx, req, util.AuditEnforcementPoint)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/gator/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ type Client interface {
RemoveData(ctx context.Context, data interface{}) (*types.Responses, error)

// Review runs all Constraints against obj.
Review(ctx context.Context, obj interface{}, opts ...drivers.QueryOpt) (*types.Responses, error)
Review(ctx context.Context, obj interface{}, sourceEP string, opts ...drivers.QueryOpt) (*types.Responses, error)
}
4 changes: 2 additions & 2 deletions pkg/gator/test/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ func Test(objs []*unstructured.Unstructured, tOpts Opts) (*GatorResponses, error
Source: mutationtypes.SourceTypeOriginal,
}

review, err := client.Review(ctx, au)
review, err := client.Review(ctx, au, "gator.gatekeeper.sh")
if err != nil {
return nil, fmt.Errorf("reviewing %v %s/%s: %w",
obj.GroupVersionKind(), obj.GetNamespace(), obj.GetName(), err)
Expand All @@ -130,7 +130,7 @@ func Test(objs []*unstructured.Unstructured, tOpts Opts) (*GatorResponses, error
Namespace: ns,
Source: mutationtypes.SourceTypeGenerated,
}
resultantReview, err := client.Review(ctx, au)
resultantReview, err := client.Review(ctx, au, "gator.gatekeeper.sh")
if err != nil {
return nil, fmt.Errorf("reviewing expanded resource %v %s/%s: %w",
resultant.Obj.GroupVersionKind(), resultant.Obj.GetNamespace(), resultant.Obj.GetName(), err)
Expand Down
4 changes: 2 additions & 2 deletions pkg/gator/verify/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ func (r *Runner) runReview(ctx context.Context, newClient func() (gator.Client,
Object: *toReview,
Source: mutationtypes.SourceTypeOriginal,
}
return c.Review(ctx, au)
return c.Review(ctx, au, util.GatorEnforcementPoint)
}

func (r *Runner) validateAndReviewAdmissionReviewRequest(ctx context.Context, c gator.Client, toReview *unstructured.Unstructured) (*types.Responses, error) {
Expand Down Expand Up @@ -369,7 +369,7 @@ func (r *Runner) validateAndReviewAdmissionReviewRequest(ctx context.Context, c
Source: mutationtypes.SourceTypeOriginal,
}

return c.Review(ctx, arr)
return c.Review(ctx, arr, util.GatorEnforcementPoint)
}

func (r *Runner) addInventory(ctx context.Context, c gator.Client, suiteDir, inventoryPath string) error {
Expand Down
7 changes: 4 additions & 3 deletions pkg/target/target_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/open-policy-agent/frameworks/constraint/pkg/client/drivers/rego"
"github.com/open-policy-agent/frameworks/constraint/pkg/core/templates"
api "github.com/open-policy-agent/gatekeeper/v3/apis"
"github.com/open-policy-agent/gatekeeper/v3/pkg/util"
admissionv1 "k8s.io/api/admission/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -468,7 +469,7 @@ func TestConstraintEnforcement(t *testing.T) {
}

fullReq := &AugmentedReview{Namespace: tc.ns, AdmissionRequest: req}
res, err := c.Review(ctx, fullReq, drivers.Tracing(true))
res, err := c.Review(ctx, fullReq, util.AuditEnforcementPoint, drivers.Tracing(true))
if err != nil {
t.Errorf("Error reviewing request: %s", err)
}
Expand Down Expand Up @@ -497,7 +498,7 @@ func TestConstraintEnforcement(t *testing.T) {
}

fullReq2 := &AugmentedReview{Namespace: tc.ns, AdmissionRequest: req2}
res2, err := c.Review(ctx, fullReq2, drivers.Tracing(true))
res2, err := c.Review(ctx, fullReq2, util.AuditEnforcementPoint, drivers.Tracing(true))
if err != nil {
t.Errorf("Error reviewing OldObject request: %s", err)
}
Expand All @@ -510,7 +511,7 @@ func TestConstraintEnforcement(t *testing.T) {
}

fullReq3 := &AugmentedUnstructured{Namespace: tc.ns, Object: *tc.obj}
res3, err := c.Review(ctx, fullReq3, drivers.Tracing(true))
res3, err := c.Review(ctx, fullReq3, util.AuditEnforcementPoint, drivers.Tracing(true))
if err != nil {
t.Errorf("Error reviewing AugmentedUnstructured request: %s", err)
}
Expand Down
122 changes: 112 additions & 10 deletions pkg/util/enforcement_action.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package util

import (
"encoding/json"
"errors"
"fmt"

Expand All @@ -10,18 +11,41 @@ import (
// EnforcementAction is the response we take to violations.
type EnforcementAction string

type ScopedEnforcementAction struct {
Action string `json:"action"`
EnforcementPoints []EnforcementPoint `json:"enforcementPoints"`
}

type EnforcementPoint struct {
Name string `json:"name"`
}

// The set of possible responses to policy violations.
const (
Deny EnforcementAction = "deny"
Dryrun EnforcementAction = "dryrun"
Warn EnforcementAction = "warn"
Scoped EnforcementAction = "scoped"
Unrecognized EnforcementAction = "unrecognized"
)

var supportedEnforcementActions = []EnforcementAction{Deny, Dryrun, Warn}
const (
// WebhookEnforcementPoint is the enforcement point for admission.
WebhookEnforcementPoint = "admission.k8s.io"

// AuditEnforcementPoint is the enforcement point for audit.
AuditEnforcementPoint = "audit.gatekeeper.sh"

// GatorEnforcementPoint is the enforcement point for gator cli.
GatorEnforcementPoint = "gator.gatekeeper.sh"
)

var supportedEnforcementActions = []EnforcementAction{Deny, Dryrun, Warn, Scoped}

var supportedScopedActions = []EnforcementAction{Deny, Dryrun, Warn}

// KnownEnforcementActions are all defined EnforcementActions.
var KnownEnforcementActions = []EnforcementAction{Deny, Dryrun, Warn, Unrecognized}
var KnownEnforcementActions = []EnforcementAction{Deny, Dryrun, Warn, Scoped, Unrecognized}

// ErrEnforcementAction indicates the passed EnforcementAction is not valid.
var ErrEnforcementAction = errors.New("unrecognized enforcementAction")
Expand All @@ -30,14 +54,69 @@ var ErrEnforcementAction = errors.New("unrecognized enforcementAction")
// spec.enforcementAction field as it was not a string.
var ErrInvalidSpecEnforcementAction = errors.New("spec.enforcementAction must be a string")

func ValidateEnforcementAction(input EnforcementAction) error {
for _, n := range supportedEnforcementActions {
if input == n {
return nil
var ErrInvalidSpecScopedEnforcementAction = errors.New("spec.scopedEnforcementAction must be in the format of []{action: string, enforcementPoints: []{name: string}}")

func ValidateEnforcementAction(input EnforcementAction, item map[string]interface{}) error {
switch input {
case Scoped:
return ValidateScopedEnforcementAction(item)
case Dryrun, Deny, Warn:
return nil
default:
return fmt.Errorf("%w: %q is not within the supported list %v",
ErrEnforcementAction, input, supportedEnforcementActions)
}
}

func ValidateScopedEnforcementAction(item map[string]interface{}) error {
obj, err := GetScopedEnforcementAction(item)
if err != nil {
return fmt.Errorf("error fetching scopedEnforcementActions: %w", err)
}

// validating scopedEnforcementActions
for _, scopedEnforcementAction := range *obj {
if scopedEnforcementAction.Action == "" {
return ErrInvalidSpecEnforcementAction
}
switch EnforcementAction(scopedEnforcementAction.Action) {
case Dryrun, Deny, Warn:
default:
return fmt.Errorf("%w: %q is not within the supported list %v", ErrEnforcementAction, scopedEnforcementAction.Action, supportedScopedActions)
}
if len(scopedEnforcementAction.EnforcementPoints) == 0 {
return ErrInvalidSpecEnforcementAction
}
for _, enforcementPoint := range scopedEnforcementAction.EnforcementPoints {
if enforcementPoint.Name == "" {
return ErrInvalidSpecEnforcementAction
}
}
}
return fmt.Errorf("%w: %q is not within the supported list %v",
ErrEnforcementAction, input, supportedEnforcementActions)
return nil
}

func GetScopedEnforcementAction(item map[string]interface{}) (*[]ScopedEnforcementAction, error) {
scopedEnforcementActions, found, err := unstructured.NestedFieldNoCopy(item, "spec", "scopedEnforcementActions")
if err != nil {
return nil, fmt.Errorf("error fetching scopedEnforcementActions: %w", err)
}
if !found {
return nil, fmt.Errorf("scopedEnforcementActions is required")
}
return convertToScopedEnforcementActions(scopedEnforcementActions)
}

func convertToScopedEnforcementActions(object interface{}) (*[]ScopedEnforcementAction, error) {
j, err := json.Marshal(object)
if err != nil {
return nil, fmt.Errorf("could not convert unknown object to JSON: %w", err)
}
obj := []ScopedEnforcementAction{}
if err := json.Unmarshal(j, &obj); err != nil {
return nil, fmt.Errorf("Could not convert JSON to scopedEnforcementActions: %w", err)
}
return &obj, nil
}

func GetEnforcementAction(item map[string]interface{}) (EnforcementAction, error) {
Expand All @@ -50,10 +129,33 @@ func GetEnforcementAction(item map[string]interface{}) (EnforcementAction, error
if enforcementAction == "" {
enforcementAction = Deny
}
// validating enforcement action - if it is not deny or dryrun, we are classifying as unrecognized
if err := ValidateEnforcementAction(enforcementAction); err != nil {
// validating enforcement action - if it is not deny or dryrun or scoped, we are classifying as unrecognized
if err := ValidateEnforcementAction(enforcementAction, item); err != nil {
enforcementAction = Unrecognized
}

return enforcementAction, nil
}

func (h *ScopedEnforcementAction) ScopedActionForEP(enforcementPoint string, u *unstructured.Unstructured) ([]string, error) {
scopedEnforcementActions, err := GetScopedEnforcementAction(u.Object)
if err != nil {
return nil, err
}
enforcementActions := []string{}
for _, scopedEnforcementAction := range *scopedEnforcementActions {
if enforcementPointEnabled(scopedEnforcementAction, enforcementPoint) {
enforcementActions = append(enforcementActions, scopedEnforcementAction.Action)
}
}
return enforcementActions, nil
}

func enforcementPointEnabled(scopedEnforcementAction ScopedEnforcementAction, enforcementPoint string) bool {
for _, ep := range scopedEnforcementAction.EnforcementPoints {
if ep.Name == enforcementPoint || ep.Name == "*" {
return true
}
}
return false
}
60 changes: 50 additions & 10 deletions pkg/util/enforcement_action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,61 @@ import (

func TestValidateEnforcementAction(t *testing.T) {
testCases := []struct {
name string
action EnforcementAction
wantErr error
name string
action EnforcementAction
wantErr error
constraint map[string]interface{}
}{
{
name: "empty string",
action: "",
wantErr: ErrEnforcementAction,
name: "empty string",
action: "",
wantErr: ErrEnforcementAction,
constraint: nil,
},
{
action: "notsupported",
wantErr: ErrEnforcementAction,
constraint: nil,
},
{
action: "notsupported",
wantErr: ErrEnforcementAction,
action: Dryrun,
constraint: nil,
},
{
action: Dryrun,
action: Scoped,
constraint: map[string]interface{}{
"spec": map[string]interface{}{
"scopedEnforcementActions": []ScopedEnforcementAction{
{
Action: "deny",
EnforcementPoints: []EnforcementPoint{
{
Name: "audit",
},
},
},
},
},
},
},
{
name: "invalid scopedEnforcementActions",
action: Scoped,
constraint: map[string]interface{}{
"spec": map[string]interface{}{
"scopedEnforcementActions": []map[string]interface{}{
{
"action": "deny",
"enforcementPoints": []map[string]string{
{
"test": "audit",
},
},
},
},
},
},
wantErr: ErrInvalidSpecEnforcementAction,
},
}

Expand All @@ -30,7 +70,7 @@ func TestValidateEnforcementAction(t *testing.T) {
tc.name = string(tc.action)
}
t.Run(tc.name, func(t *testing.T) {
err := ValidateEnforcementAction(tc.action)
err := ValidateEnforcementAction(tc.action, tc.constraint)
if !errors.Is(err, tc.wantErr) {
t.Errorf("got ValidateEnforcementAction(%q) == %v, want %v",
tc.action, err, tc.wantErr)
Expand Down
Loading

0 comments on commit 2f9f4bb

Please sign in to comment.