Skip to content

Commit

Permalink
adding scopedenforcementactions
Browse files Browse the repository at this point in the history
Signed-off-by: Jaydip Gabani <gabanijaydip@gmail.com>
  • Loading branch information
JaydipGabani committed Mar 18, 2024
1 parent f2188ae commit b09049c
Show file tree
Hide file tree
Showing 27 changed files with 662 additions and 133 deletions.
1 change: 1 addition & 0 deletions constraint/deploy/tools.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build tools
// +build tools

// This existence of this package allows vendoring of the manifests in this directory by go 1.13+.
Expand Down
125 changes: 124 additions & 1 deletion constraint/pkg/apis/constraints/apis.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,17 @@ const (
// validation are treated as failing validation.
//
// This is the default EnforcementAction.
EnforcementActionDeny = "deny"
EnforcementActionDeny = "deny"
EnforcementActionScoped = "scoped"

// 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 (
Expand Down Expand Up @@ -45,3 +55,116 @@ func GetEnforcementAction(constraint *unstructured.Unstructured) (string, error)

return action, nil
}

func IsEnforcementActionScoped(action string) bool {
return action == EnforcementActionScoped
}

// GetEnforcementActionsForEP returns a map of enforcement actions for each enforcement point.
func GetEnforcementActionsForEP(constraint *unstructured.Unstructured, ep string) ([]string, error) {
// Access the scopedEnforcementAction field
scopedActions, found, err := getNestedFieldAsArray(constraint.Object, "spec", "scopedEnforcementActions")
if err != nil {
return nil, fmt.Errorf("%w: invalid spec.enforcementActionPerEP", ErrInvalidConstraint)
}

// Return early if scopedEnforcementAction is not found
if !found {
return nil, nil
}

// Convert scopedActions to a slice of map[string]interface{}
scopedEnforcementActions, err := convertToMapSlice(scopedActions)
if err != nil {
return nil, fmt.Errorf("%w: spec.scopedEnforcementAction must be an array", ErrInvalidConstraint)
}

actions := make(map[string]bool)
for _, scopedEnforcementAction := range scopedEnforcementActions {
// Access the enforcementPoints field
enforcementPoints, found, err := getNestedFieldAsArray(scopedEnforcementAction, "enforcementPoints")
if err != nil || !found {
continue
}

// Iterate over enforcementPoints
for _, enforcementPoint := range enforcementPoints {
// Convert enforcementPoint to map[string]string
enforcementPointMap, err := convertToMapInterface(enforcementPoint)
if err != nil {
continue
}

if pt, ok := enforcementPointMap["name"].(string); ok {
// Check if enforcementPoint matches the current ep or "*"
if pt == ep || pt == "*" {
// Access the action field
action, found, err := getNestedFieldAsString(scopedEnforcementAction, "action")
if err != nil || !found {
continue
}

// Add action to the actionMap
actions[action] = true
}
}
}
}
actionsForEPs := []string{}
for action := range actions {
actionsForEPs = append(actionsForEPs, action)
}

return actionsForEPs, nil
}

// Helper function to access nested fields as an array.
func getNestedFieldAsArray(obj map[string]interface{}, fields ...string) ([]interface{}, bool, error) {
value, found, err := unstructured.NestedFieldNoCopy(obj, fields...)
if err != nil {
return nil, false, err
}
if !found {
return nil, false, nil
}
if arr, ok := value.([]interface{}); ok {
return arr, true, nil
}
return nil, false, nil
}

// Helper function to access nested fields as a string.
func getNestedFieldAsString(obj map[string]interface{}, fields ...string) (string, bool, error) {
for _, field := range fields {
value, found, err := unstructured.NestedString(obj, field)
if err != nil {
return "", false, err
}
if found {
return value, true, nil
}
}
return "", false, nil
}

// Helper function to convert a value to a map[string]interface{}.
func convertToMapInterface(value interface{}) (map[string]interface{}, error) {
if m, ok := value.(map[string]interface{}); ok {
return m, nil
}
return nil, fmt.Errorf("value must be a map[string]interface{}")
}

// Helper function to convert a value to a []map[string]interface{}.
func convertToMapSlice(value interface{}) ([]map[string]interface{}, error) {
if arr, ok := value.([]interface{}); ok {
result := make([]map[string]interface{}, 0, len(arr))
for _, v := range arr {
if m, ok := v.(map[string]interface{}); ok {
result = append(result, m)
}
}
return result, nil
}
return nil, fmt.Errorf("value must be a []interface{}")
}
101 changes: 101 additions & 0 deletions constraint/pkg/apis/constraints/apis_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package constraints

import (
"testing"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

func TestGetEnforcementActionsForEP(t *testing.T) {
tests := []struct {
name string
constraint *unstructured.Unstructured
ep string
expected []string
err error
}{
{
name: "wildcard enforcement point",
constraint: &unstructured.Unstructured{
Object: map[string]interface{}{
"spec": map[string]interface{}{
"scopedEnforcementActions": []interface{}{
map[string]interface{}{
"enforcementPoints": []interface{}{
map[string]interface{}{
"name": "ep1",
},
map[string]interface{}{
"name": "ep2",
},
},
"action": "warn",
},
map[string]interface{}{
"enforcementPoints": []interface{}{
map[string]interface{}{
"name": "*",
},
},
"action": "deny",
},
},
},
},
},
ep: "ep2",
expected: []string{"deny", "warn"},
},
{
name: "enforcement point not found",
constraint: &unstructured.Unstructured{
Object: map[string]interface{}{
"spec": map[string]interface{}{
"scopedEnforcementActions": []interface{}{
map[string]interface{}{
"enforcementPoints": []interface{}{
map[string]interface{}{
"name": "ep1",
},
},
"action": "warn",
},
map[string]interface{}{
"enforcementPoints": []interface{}{
map[string]interface{}{
"name": "ep2",
},
},
"action": "deny",
},
},
},
},
},
ep: "ep3",
expected: []string{},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actions, err := GetEnforcementActionsForEP(tt.constraint, tt.ep)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}

l := 0
for _, action := range actions {
for _, expected := range tt.expected {
if action == expected {
l++
break
}
}
}
if l != len(tt.expected) {
t.Errorf("Expected %v, got %v", tt.expected, actions)
}
})
}
}
1 change: 1 addition & 0 deletions constraint/pkg/apis/templates/zz_generated.deepcopy.go

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

15 changes: 9 additions & 6 deletions constraint/pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ type Client struct {

// templates is a map from a Template's name to its entry.
templates map[string]*templateClient

// enforcementPoints is the list of enforcement points that the client is associated with.
enforcementPoints []string
}

// driverForTemplate returns the driver to be used for a template according
Expand Down Expand Up @@ -622,7 +625,7 @@ func (c *Client) RemoveData(ctx context.Context, data interface{}) (*types.Respo
// Review makes sure the provided object satisfies all stored constraints.
// On error, the responses return value will still be populated so that
// partial results can be analyzed.
func (c *Client) Review(ctx context.Context, obj interface{}, opts ...drivers.QueryOpt) (*types.Responses, error) {
func (c *Client) Review(ctx context.Context, obj interface{}, sourceEP string, opts ...drivers.QueryOpt) (*types.Responses, error) {
responses := types.NewResponses()
errMap := make(clienterrors.ErrorMap)

Expand Down Expand Up @@ -661,7 +664,7 @@ func (c *Client) Review(ctx context.Context, obj interface{}, opts ...drivers.Qu
var targetConstraints []*unstructured.Unstructured

for _, template := range templateList {
matchingConstraints := template.Matches(target, review)
matchingConstraints := template.Matches(target, review, sourceEP)
for _, matchResult := range matchingConstraints {
if matchResult.error == nil {
targetConstraints = append(targetConstraints, matchResult.constraint)
Expand All @@ -676,14 +679,14 @@ func (c *Client) Review(ctx context.Context, obj interface{}, opts ...drivers.Qu
for target, review := range reviews {
constraints := constraintsByTarget[target]

resp, stats, err := c.review(ctx, target, constraints, review, opts...)
resp, stats, err := c.review(ctx, target, constraints, review, sourceEP, opts...)
if err != nil {
errMap.Add(target, err)
continue
}

for _, autorejection := range autorejections[target] {
resp.AddResult(autorejection.ToResult())
resp.AddResult(autorejection.ToResult()...)
}

// Ensure deterministic result ordering.
Expand Down Expand Up @@ -711,7 +714,7 @@ func (c *Client) Review(ctx context.Context, obj interface{}, opts ...drivers.Qu
return responses, &errMap
}

func (c *Client) review(ctx context.Context, target string, constraints []*unstructured.Unstructured, review interface{}, opts ...drivers.QueryOpt) (*types.Response, []*instrumentation.StatsEntry, error) {
func (c *Client) review(ctx context.Context, target string, constraints []*unstructured.Unstructured, review interface{}, sourceEP string, opts ...drivers.QueryOpt) (*types.Response, []*instrumentation.StatsEntry, error) {
var results []*types.Result
var stats []*instrumentation.StatsEntry
var tracesBuilder strings.Builder
Expand All @@ -735,7 +738,7 @@ func (c *Client) review(ctx context.Context, target string, constraints []*unstr
if len(driverToConstraints[driverName]) == 0 {
continue
}
qr, err := driver.Query(ctx, target, driverToConstraints[driverName], review, opts...)
qr, err := driver.Query(ctx, target, driverToConstraints[driverName], review, sourceEP, opts...)
if err != nil {
errs.Add(driverName, err)
continue
Expand Down
Loading

0 comments on commit b09049c

Please sign in to comment.