Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adding scopedenforcementactions #403

Merged
merged 34 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
4aabc45
adding scopedenforcementactions
JaydipGabani Mar 21, 2024
81b2d08
adding tests
JaydipGabani Mar 21, 2024
9deaab2
fixing tests
JaydipGabani Mar 21, 2024
909437e
refactoring template client and query
JaydipGabani Apr 8, 2024
e837a1e
Merge branch 'master' into multi-ea
JaydipGabani Apr 10, 2024
a6f1641
refatoring queryopts to reviewopts
JaydipGabani Apr 10, 2024
46a172b
updating ccomments
JaydipGabani Apr 11, 2024
7a2ff49
removing EP variables
JaydipGabani Apr 12, 2024
e054366
generic webhook EP
JaydipGabani Apr 18, 2024
9bbfbc2
Merge branch 'master' into multi-ea
JaydipGabani Jul 1, 2024
076751a
Merge branch 'master' into multi-ea
JaydipGabani Jul 8, 2024
8f28a01
simplifying constraintToBinding
JaydipGabani Jul 9, 2024
1c6b9d8
adding comments
JaydipGabani Jul 9, 2024
ae81539
Merge branch 'master' into multi-ea
JaydipGabani Jul 15, 2024
5af2f0c
Merge branch 'master' into multi-ea
JaydipGabani Jul 16, 2024
a654a76
checking lowercase eps, fixing nil variable access
JaydipGabani Jul 17, 2024
3cb703c
fixing file perm
JaydipGabani Jul 17, 2024
03dd160
Merge branch 'master' into multi-ea
JaydipGabani Jul 17, 2024
5080e95
Merge branch 'master' into multi-ea
JaydipGabani Jul 22, 2024
d996311
adding tests for case sensitivity and missing enforcementaction
JaydipGabani Jul 23, 2024
759157f
Merge branch 'multi-ea' of github.com:JaydipGabani/frameworks into mu…
JaydipGabani Jul 23, 2024
c9ba827
updating gk-webhook EP
JaydipGabani Jul 23, 2024
7cbf00d
fixing test
JaydipGabani Jul 23, 2024
86bae52
adding enforcement action and scoped enforcement actions in result an…
JaydipGabani Jul 24, 2024
2428b21
fixing faulty test
JaydipGabani Jul 25, 2024
6456bd6
refactoring code for simplycity
JaydipGabani Jul 26, 2024
7fefe2d
removing comments, removing * from review opts and client opts
JaydipGabani Jul 26, 2024
9fdd218
mandating client to be initialized with enforcment point
JaydipGabani Jul 26, 2024
bbefecf
case sensitive check for actions
JaydipGabani Jul 26, 2024
ac65fcf
updating code comments
JaydipGabani Jul 26, 2024
d87f02f
removing comments
JaydipGabani Jul 26, 2024
675353b
removing enforcement action check while adding constriant to template…
JaydipGabani Jul 26, 2024
ce5a973
removing all enforcement points from matches
JaydipGabani Jul 26, 2024
67de36b
preserving enforcement point names
JaydipGabani Jul 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

}
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.

12 changes: 6 additions & 6 deletions constraint/pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -622,7 +622,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 +661,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 +676,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 +711,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 +735,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
Loading