diff --git a/cmd/gator/test/gatortest_test.go b/cmd/gator/test/gatortest_test.go index 4fa3207bce6..b98d3b7753c 100644 --- a/cmd/gator/test/gatortest_test.go +++ b/cmd/gator/test/gatortest_test.go @@ -59,7 +59,8 @@ func Test_formatOutput(t *testing.T) { constraint: object: kind: kind - enforcementaction: [] + enforcementaction: "" + scopedenforcementactions: [] violatingObject: bar: xyz trace: xyz diff --git a/cmd/gator/test/test.go b/cmd/gator/test/test.go index eea7a7f9611..58f911776e9 100644 --- a/cmd/gator/test/test.go +++ b/cmd/gator/test/test.go @@ -206,7 +206,10 @@ func formatOutput(flagOutput string, results []*test.GatorResult, stats []*instr func enforceableFailure(results []*test.GatorResult) bool { for _, result := range results { - for _, action := range result.EnforcementAction { + if result.EnforcementAction == string(util.Deny) { + return true + } + for _, action := range result.ScopedEnforcementActions { if action == string(util.Deny) { return true } diff --git a/pkg/audit/manager.go b/pkg/audit/manager.go index 21ae010a7c1..0128cda82cf 100644 --- a/pkg/audit/manager.go +++ b/pkg/audit/manager.go @@ -96,13 +96,14 @@ type Manager struct { // StatusViolation represents each violation under status. type StatusViolation struct { - Group string `json:"group"` - Version string `json:"version"` - Kind string `json:"kind"` - Name string `json:"name"` - Namespace string `json:"namespace,omitempty"` - Message string `json:"message"` - EnforcementAction string `json:"enforcementAction"` + Group string `json:"group"` + Version string `json:"version"` + Kind string `json:"kind"` + Name string `json:"name"` + Namespace string `json:"namespace,omitempty"` + Message string `json:"message"` + EnforcementAction string `json:"enforcementAction"` + EnforcementActions []string `json:"enforcementActions,omitempty"` } // ConstraintMsg represents publish message for each constraint. @@ -117,6 +118,7 @@ type PubsubMsg struct { Namespace string `json:"namespace,omitempty"` Message string `json:"message,omitempty"` EnforcementAction string `json:"enforcementAction,omitempty"` + EnforcementActions []string `json:"enforcementActions,omitempty"` ConstraintAnnotations map[string]string `json:"constraintAnnotations,omitempty"` ResourceGroup string `json:"resourceGroup,omitempty"` ResourceAPIVersion string `json:"resourceAPIVersion,omitempty"` @@ -873,11 +875,8 @@ func (am *Manager) addAuditResponsesToUpdateLists( } totalViolationsPerConstraint[key]++ - - for _, action := range r.EnforcementAction { - ea := util.EnforcementAction(action) - totalViolationsPerEnforcementAction[ea]++ - } + ea := util.EnforcementAction(r.EnforcementAction) + totalViolationsPerEnforcementAction[ea]++ gvk := r.obj.GroupVersionKind() namespace := r.obj.GetNamespace() @@ -886,15 +885,15 @@ func (am *Manager) addAuditResponsesToUpdateLists( if len(msg) > msgSize { msg = truncateString(msg, msgSize) } - action := strings.Join(r.EnforcementAction, "/") violation := &StatusViolation{ - Group: gvk.Group, - Version: gvk.Version, - Kind: gvk.Kind, - Namespace: namespace, - Name: name, - Message: msg, - EnforcementAction: action, + Group: gvk.Group, + Version: gvk.Version, + Kind: gvk.Kind, + Namespace: namespace, + Name: name, + Message: msg, + EnforcementAction: r.EnforcementAction, + EnforcementActions: r.ScopedEnforcementActions, } // since keyQueue is a LimitQueue, it guarantees len <= limit after a push. // the limit on size ensures Push() has O(1) time complexity. @@ -902,9 +901,9 @@ func (am *Manager) addAuditResponsesToUpdateLists( details := r.Metadata["details"] labels := r.obj.GetLabels() - logViolation(am.log, constraint, action, gvk, namespace, name, msg, details, labels) + logViolation(am.log, constraint, ea, r.ScopedEnforcementActions, gvk, namespace, name, msg, details, labels) if *pubsubController.PubsubEnabled { - err := am.pubsubSystem.Publish(context.Background(), *auditConnection, *auditChannel, violationMsg(constraint, action, gvk, namespace, name, msg, details, labels, timestamp)) + err := am.pubsubSystem.Publish(context.Background(), *auditConnection, *auditChannel, violationMsg(constraint, ea, r.ScopedEnforcementActions, gvk, namespace, name, msg, details, labels, timestamp)) if err != nil { am.log.Error(err, "pubsub audit Publishing") } @@ -912,7 +911,7 @@ func (am *Manager) addAuditResponsesToUpdateLists( if *emitAuditEvents { uid := r.obj.GetUID() rv := r.obj.GetResourceVersion() - emitEvent(constraint, timestamp, action, gvk, namespace, name, rv, msg, am.gkNamespace, uid, am.eventRecorder) + emitEvent(constraint, timestamp, ea, strings.Join(r.ScopedEnforcementActions, ","), gvk, namespace, name, rv, msg, am.gkNamespace, uid, am.eventRecorder) } } } @@ -1158,7 +1157,7 @@ func logConstraint(l logr.Logger, gvknn *util.KindVersionName, enforcementAction ) } -func violationMsg(constraint *unstructured.Unstructured, enforcementAction string, resourceGroupVersionKind schema.GroupVersionKind, rnamespace, rname, message string, details interface{}, rlabels map[string]string, timestamp string) interface{} { +func violationMsg(constraint *unstructured.Unstructured, enforcementAction util.EnforcementAction, scopedEnforcementActions []string, resourceGroupVersionKind schema.GroupVersionKind, rnamespace, rname, message string, details interface{}, rlabels map[string]string, timestamp string) interface{} { userConstraintAnnotations := constraint.GetAnnotations() delete(userConstraintAnnotations, "kubectl.kubernetes.io/last-applied-configuration") @@ -1172,7 +1171,8 @@ func violationMsg(constraint *unstructured.Unstructured, enforcementAction strin Kind: constraint.GetKind(), Name: constraint.GetName(), Namespace: constraint.GetNamespace(), - EnforcementAction: enforcementAction, + EnforcementAction: string(enforcementAction), + EnforcementActions: scopedEnforcementActions, ConstraintAnnotations: userConstraintAnnotations, ResourceGroup: resourceGroupVersionKind.Group, ResourceAPIVersion: resourceGroupVersionKind.Version, @@ -1185,7 +1185,7 @@ func violationMsg(constraint *unstructured.Unstructured, enforcementAction strin func logViolation(l logr.Logger, constraint *unstructured.Unstructured, - enforcementAction string, resourceGroupVersionKind schema.GroupVersionKind, rnamespace, rname, message string, details interface{}, rlabels map[string]string, + enforcementAction util.EnforcementAction, scopedEnforcementActions []string, resourceGroupVersionKind schema.GroupVersionKind, rnamespace, rname, message string, details interface{}, rlabels map[string]string, ) { userConstraintAnnotations := constraint.GetAnnotations() delete(userConstraintAnnotations, "kubectl.kubernetes.io/last-applied-configuration") @@ -1200,6 +1200,7 @@ func logViolation(l logr.Logger, logging.ConstraintName, constraint.GetName(), logging.ConstraintNamespace, constraint.GetNamespace(), logging.ConstraintAction, enforcementAction, + logging.ConstraintEnforcementActions, scopedEnforcementActions, logging.ConstraintAnnotations, userConstraintAnnotations, logging.ResourceGroup, resourceGroupVersionKind.Group, logging.ResourceAPIVersion, resourceGroupVersionKind.Version, @@ -1211,24 +1212,25 @@ func logViolation(l logr.Logger, } func emitEvent(constraint *unstructured.Unstructured, - timestamp string, enforcementAction string, resourceGroupVersionKind schema.GroupVersionKind, rnamespace, rname, rrv, message, gkNamespace string, ruid types.UID, + timestamp string, enforcementAction util.EnforcementAction, scopedEnforcementActions string, resourceGroupVersionKind schema.GroupVersionKind, rnamespace, rname, rrv, message, gkNamespace string, ruid types.UID, eventRecorder record.EventRecorder, ) { annotations := map[string]string{ - "process": "audit", - "auditTimestamp": timestamp, - logging.EventType: "violation_audited", - logging.ConstraintGroup: constraint.GroupVersionKind().Group, - logging.ConstraintAPIVersion: constraint.GroupVersionKind().Version, - logging.ConstraintKind: constraint.GetKind(), - logging.ConstraintName: constraint.GetName(), - logging.ConstraintNamespace: constraint.GetNamespace(), - logging.ConstraintAction: enforcementAction, - logging.ResourceGroup: resourceGroupVersionKind.Group, - logging.ResourceAPIVersion: resourceGroupVersionKind.Version, - logging.ResourceKind: resourceGroupVersionKind.Kind, - logging.ResourceNamespace: rnamespace, - logging.ResourceName: rname, + "process": "audit", + "auditTimestamp": timestamp, + logging.EventType: "violation_audited", + logging.ConstraintGroup: constraint.GroupVersionKind().Group, + logging.ConstraintAPIVersion: constraint.GroupVersionKind().Version, + logging.ConstraintKind: constraint.GetKind(), + logging.ConstraintName: constraint.GetName(), + logging.ConstraintNamespace: constraint.GetNamespace(), + logging.ConstraintAction: string(enforcementAction), + logging.ConstraintEnforcementActions: scopedEnforcementActions, + logging.ResourceGroup: resourceGroupVersionKind.Group, + logging.ResourceAPIVersion: resourceGroupVersionKind.Version, + logging.ResourceKind: resourceGroupVersionKind.Kind, + logging.ResourceNamespace: rnamespace, + logging.ResourceName: rname, } reason := "AuditViolation" diff --git a/pkg/audit/result_test.go b/pkg/audit/result_test.go index f3d6e2ee6eb..a71ea7d9ae6 100644 --- a/pkg/audit/result_test.go +++ b/pkg/audit/result_test.go @@ -13,7 +13,7 @@ func TestResult_ToResult(t *testing.T) { aResult := types.Result{ Target: "targetA", Msg: "violationA", - EnforcementAction: []string{"deny"}, + EnforcementAction: "deny", } responses := types.Responses{ diff --git a/pkg/expansion/aggregate.go b/pkg/expansion/aggregate.go index 84f39195058..2967a08ef17 100644 --- a/pkg/expansion/aggregate.go +++ b/pkg/expansion/aggregate.go @@ -51,7 +51,7 @@ func OverrideEnforcementAction(action string, resps *types.Responses) { for _, resp := range resps.ByTarget { for _, res := range resp.Results { - res.EnforcementAction = []string{action} + res.EnforcementAction = action } } } diff --git a/pkg/expansion/aggregate_test.go b/pkg/expansion/aggregate_test.go index 82a3946d3f0..549a420ce65 100644 --- a/pkg/expansion/aggregate_test.go +++ b/pkg/expansion/aggregate_test.go @@ -187,7 +187,7 @@ func TestOverrideEnforcementAction(t *testing.T) { { Target: "targetA", Msg: "violationA", - EnforcementAction: []string{"deny"}, + EnforcementAction: "deny", }, }, }, @@ -201,7 +201,7 @@ func TestOverrideEnforcementAction(t *testing.T) { { Target: "targetA", Msg: "violationA", - EnforcementAction: []string{"deny"}, + EnforcementAction: "deny", }, }, }, @@ -219,7 +219,7 @@ func TestOverrideEnforcementAction(t *testing.T) { { Target: "targetA", Msg: "violationA", - EnforcementAction: []string{"warn"}, + EnforcementAction: "warn", }, }, }, @@ -229,12 +229,12 @@ func TestOverrideEnforcementAction(t *testing.T) { { Target: "targetB", Msg: "violationB", - EnforcementAction: []string{"warn"}, + EnforcementAction: "warn", }, { Target: "targetB", Msg: "violationC", - EnforcementAction: []string{"warn"}, + EnforcementAction: "warn", }, }, }, @@ -248,7 +248,7 @@ func TestOverrideEnforcementAction(t *testing.T) { { Target: "targetA", Msg: "violationA", - EnforcementAction: []string{"deny"}, + EnforcementAction: "deny", }, }, }, @@ -258,12 +258,12 @@ func TestOverrideEnforcementAction(t *testing.T) { { Target: "targetB", Msg: "violationB", - EnforcementAction: []string{"deny"}, + EnforcementAction: "deny", }, { Target: "targetB", Msg: "violationC", - EnforcementAction: []string{"deny"}, + EnforcementAction: "deny", }, }, }, diff --git a/pkg/gator/test/test_test.go b/pkg/gator/test/test_test.go index e6f0055e200..a5724cafc14 100644 --- a/pkg/gator/test/test_test.go +++ b/pkg/gator/test/test_test.go @@ -196,26 +196,29 @@ func TestTest(t *testing.T) { want: []*GatorResult{ { Result: types.Result{ - Target: target.Name, - Msg: "never validate", - Constraint: constraintGatorValidate, - EnforcementAction: []string{"deny", "warn"}, + Target: target.Name, + Msg: "never validate", + Constraint: constraintGatorValidate, + EnforcementAction: "scoped", + ScopedEnforcementActions: []string{"deny", "warn"}, }, }, { Result: types.Result{ - Target: target.Name, - Msg: "never validate", - Constraint: constraintGatorValidate, - EnforcementAction: []string{"deny", "warn"}, + Target: target.Name, + Msg: "never validate", + Constraint: constraintGatorValidate, + EnforcementAction: "scoped", + ScopedEnforcementActions: []string{"deny", "warn"}, }, }, { Result: types.Result{ - Target: target.Name, - Msg: "never validate", - Constraint: constraintGatorValidate, - EnforcementAction: []string{"deny", "warn"}, + Target: target.Name, + Msg: "never validate", + Constraint: constraintGatorValidate, + EnforcementAction: "scoped", + ScopedEnforcementActions: []string{"deny", "warn"}, }, }, }, diff --git a/pkg/gator/test/types.go b/pkg/gator/test/types.go index f3877b24b1c..33db9b4451b 100644 --- a/pkg/gator/test/types.go +++ b/pkg/gator/test/types.go @@ -2,7 +2,6 @@ package test import ( "sort" - "strings" "github.com/open-policy-agent/frameworks/constraint/pkg/instrumentation" "github.com/open-policy-agent/frameworks/constraint/pkg/types" @@ -67,8 +66,8 @@ func (r *GatorResponses) Results() []*GatorResult { // responses to individual constraints. This is a stopgap to make tests easier // to write until then. sort.Slice(res, func(i, j int) bool { - if strings.Join(res[i].EnforcementAction, "/") != strings.Join(res[j].EnforcementAction, "/") { - return strings.Join(res[i].EnforcementAction, "/") < strings.Join(res[j].EnforcementAction, "/") + if res[i].EnforcementAction != res[j].EnforcementAction { + return res[i].EnforcementAction < res[j].EnforcementAction } return res[i].Msg < res[j].Msg }) diff --git a/pkg/logging/logging.go b/pkg/logging/logging.go index d9f4ec81b13..3821aca2d73 100644 --- a/pkg/logging/logging.go +++ b/pkg/logging/logging.go @@ -9,32 +9,33 @@ import ( // Log keys. const ( - Process = "process" - Details = "details" - EventType = "event_type" - TemplateName = "template_name" - ConstraintNamespace = "constraint_namespace" - ConstraintName = "constraint_name" - ConstraintGroup = "constraint_group" - ConstraintKind = "constraint_kind" - ConstraintAPIVersion = "constraint_api_version" - ConstraintStatus = "constraint_status" - ConstraintAction = "constraint_action" - ConstraintAnnotations = "constraint_annotations" - AuditID = "audit_id" - ConstraintViolations = "constraint_violations" - ResourceGroup = "resource_group" - ResourceKind = "resource_kind" - ResourceLabels = "resource_labels" - ResourceAPIVersion = "resource_api_version" - ResourceNamespace = "resource_namespace" - ResourceName = "resource_name" - ResourceSourceType = "resource_source_type" - RequestUsername = "request_username" - MutationApplied = "mutation_applied" - Mutator = "mutator" - DebugLevel = 1 // r.log.Debug(foo) == r.log.V(logging.DebugLevel).Info(foo) - ExecutionStats = "execution_stats" + Process = "process" + Details = "details" + EventType = "event_type" + TemplateName = "template_name" + ConstraintNamespace = "constraint_namespace" + ConstraintName = "constraint_name" + ConstraintGroup = "constraint_group" + ConstraintKind = "constraint_kind" + ConstraintAPIVersion = "constraint_api_version" + ConstraintStatus = "constraint_status" + ConstraintAction = "constraint_action" + ConstraintEnforcementActions = "constraint_enforcement_actions" + ConstraintAnnotations = "constraint_annotations" + AuditID = "audit_id" + ConstraintViolations = "constraint_violations" + ResourceGroup = "resource_group" + ResourceKind = "resource_kind" + ResourceLabels = "resource_labels" + ResourceAPIVersion = "resource_api_version" + ResourceNamespace = "resource_namespace" + ResourceName = "resource_name" + ResourceSourceType = "resource_source_type" + RequestUsername = "request_username" + MutationApplied = "mutation_applied" + Mutator = "mutator" + DebugLevel = 1 // r.log.Debug(foo) == r.log.V(logging.DebugLevel).Info(foo) + ExecutionStats = "execution_stats" ) func LogStatsEntries(client *constraintclient.Client, logger logr.Logger, entries []*instrumentation.StatsEntry, msg string) { diff --git a/pkg/util/enforcement_action_test.go b/pkg/util/enforcement_action_test.go index c5240608f81..e0958500027 100644 --- a/pkg/util/enforcement_action_test.go +++ b/pkg/util/enforcement_action_test.go @@ -31,6 +31,33 @@ func TestValidateEnforcementAction(t *testing.T) { action: Dryrun, constraint: nil, }, + { + name: "invalid spec.scopedEnforcementAction", + action: Scoped, + wantErr: ErrEnforcementAction, + constraint: map[string]interface{}{ + "spec": map[string]interface{}{ + "scopedEnforcementActions": []apiconstraints.ScopedEnforcementAction{ + { + Action: "deny", + EnforcementPoints: []apiconstraints.EnforcementPoint{ + { + Name: "audit", + }, + }, + }, + { + Action: "test", + EnforcementPoints: []apiconstraints.EnforcementPoint{ + { + Name: "audit", + }, + }, + }, + }, + }, + }, + }, { action: Scoped, constraint: map[string]interface{}{ diff --git a/pkg/webhook/policy.go b/pkg/webhook/policy.go index ec4b19e8c79..ec228a47d33 100644 --- a/pkg/webhook/policy.go +++ b/pkg/webhook/policy.go @@ -258,7 +258,11 @@ func (h *validationHandler) getValidationMessages(res []*rtypes.Result, req *adm } } for _, r := range res { - for _, action := range r.EnforcementAction { + actions := r.ScopedEnforcementActions + if len(actions) == 0 { + actions = []string{r.EnforcementAction} + } + for _, action := range actions { if err := util.ValidateEnforcementAction(util.EnforcementAction(action), r.Constraint.Object); err != nil { continue } diff --git a/pkg/webhook/policy_test.go b/pkg/webhook/policy_test.go index 9d4b7d2e3c9..f57ba27ebb1 100644 --- a/pkg/webhook/policy_test.go +++ b/pkg/webhook/policy_test.go @@ -709,22 +709,22 @@ func TestGetValidationMessages(t *testing.T) { resDryRun := &rtypes.Result{ Msg: "test", Constraint: newConstraint("Foo", "ph", "dryrun", t), - EnforcementAction: []string{"dryrun"}, + EnforcementAction: "dryrun", } resDeny := &rtypes.Result{ Msg: "test", Constraint: newConstraint("Foo", "ph", "deny", t), - EnforcementAction: []string{"deny"}, + EnforcementAction: "deny", } resWarn := &rtypes.Result{ Msg: "test", Constraint: newConstraint("Foo", "ph", "warn", t), - EnforcementAction: []string{"warn"}, + EnforcementAction: "warn", } resRandom := &rtypes.Result{ Msg: "test", Constraint: newConstraint("Foo", "ph", "random", t), - EnforcementAction: []string{"random"}, + EnforcementAction: "random", } tc := []struct {