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 all 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
117 changes: 114 additions & 3 deletions constraint/pkg/apis/constraints/apis.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,37 @@ import (
"fmt"

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

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

type EnforcementAction string

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

const (
// Group is the API Group of Constraints.
Group = "constraints.gatekeeper.sh"

// EnforcementActionDeny indicates that if a review fails validation for a
// AllEnforcementPoints is a wildcard to indicate all enforcement points.
AllEnforcementPoints = "*"
)

const (
// Deny indicates that if a review fails validation for a
// Constraint, that it should be rejected. Errors encountered running
// validation are treated as failing validation.
//
// This is the default EnforcementAction.
EnforcementActionDeny = "deny"
Deny EnforcementAction = "deny"
Warn EnforcementAction = "warn"
Scoped EnforcementAction = "scoped"
)

var (
Expand All @@ -26,6 +45,11 @@ var (

// ErrSchema is a specific error that a Constraint failed schema validation.
ErrSchema = errors.New("schema validation failed")

// ErrMissingRequiredField is a specific error that a field is missing from a Constraint.
ErrMissingRequiredField = errors.New("missing required field")

ErrInvalidSpecEnforcementAction = errors.New("scopedEnforcementActions value must be a [{action: string, enforcementPoints: [{name: string}]}]")
)

// GetEnforcementAction returns a Constraint's enforcementAction, which indicates
Expand All @@ -40,8 +64,95 @@ func GetEnforcementAction(constraint *unstructured.Unstructured) (string, error)
}

if !found {
return EnforcementActionDeny, nil
return string(Deny), nil
}

return action, nil
}

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

// GetEnforcementActionsForEP returns a map of enforcement actions for enforcement points passed in.
func GetEnforcementActionsForEP(constraint *unstructured.Unstructured, eps []string) (map[string][]string, error) {
scopedActions, found, err := getNestedFieldAsArray(constraint.Object, "spec", "scopedEnforcementActions")
JaydipGabani marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, fmt.Errorf("%w: %w", ErrInvalidSpecEnforcementAction, err)
}
if !found {
return nil, fmt.Errorf("%w: spec.scopedEnforcementActions must be defined", ErrMissingRequiredField)
}

scopedEnforcementActions, err := convertToSliceScopedEnforcementAction(scopedActions)
if err != nil {
return nil, fmt.Errorf("%w: %w", ErrInvalidConstraint, err)
}

enforcementPointsToActionsMap := make(map[string]map[string]bool)
for _, ep := range eps {
enforcementPointsToActionsMap[ep] = make(map[string]bool)
}
for _, scopedEA := range scopedEnforcementActions {
for _, enforcementPoint := range scopedEA.EnforcementPoints {
epName := enforcementPoint.Name
if epName == AllEnforcementPoints {
for _, ep := range eps {
enforcementPointsToActionsMap[ep][scopedEA.Action] = true
}
break
}
if _, ok := enforcementPointsToActionsMap[epName]; ok {
enforcementPointsToActionsMap[epName][scopedEA.Action] = true
}
}
}
enforcementActionsForEPs := make(map[string][]string)
for ep, actions := range enforcementPointsToActionsMap {
if len(actions) == 0 {
continue
}
enforcementActionsForEPs[ep] = make([]string, 0, len(actions))
for action := range actions {
enforcementActionsForEPs[ep] = append(enforcementActionsForEPs[ep], action)
}
}

return enforcementActionsForEPs, 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 convert a value to a []ScopedEnforcementAction.
func convertToSliceScopedEnforcementAction(value interface{}) ([]ScopedEnforcementAction, error) {
var result []ScopedEnforcementAction
arr, ok := value.([]interface{})
if !ok {
return nil, ErrInvalidSpecEnforcementAction
}
for _, v := range arr {
m, ok := v.(map[string]interface{})
if !ok {
return nil, ErrInvalidSpecEnforcementAction
}
scopedEA := &ScopedEnforcementAction{}
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(m, scopedEA); err != nil {
return nil, err
}
result = append(result, *scopedEA)
}
return result, nil
}
213 changes: 213 additions & 0 deletions constraint/pkg/apis/constraints/apis_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
package constraints

import (
"reflect"
"testing"

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

const (
// WebhookEnforcementPoint is the enforcement point for admission.
WebhookEnforcementPoint = "validation.gatekeeper.sh"

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

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

func TestGetEnforcementActionsForEP(t *testing.T) {
tests := []struct {
name string
constraint *unstructured.Unstructured
eps []string
expected map[string]map[string]bool
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": AuditEnforcementPoint,
},
map[string]interface{}{
"name": WebhookEnforcementPoint,
},
},
"action": "warn",
},
map[string]interface{}{
"enforcementPoints": []interface{}{
map[string]interface{}{
"name": "*",
},
},
"action": "deny",
},
},
},
},
},
expected: map[string]map[string]bool{
AuditEnforcementPoint: {
"warn": true,
"deny": true,
},
WebhookEnforcementPoint: {
"warn": true,
"deny": true,
},
GatorEnforcementPoint: {
"deny": true,
},
},
eps: []string{AuditEnforcementPoint, WebhookEnforcementPoint, GatorEnforcementPoint},
},
{
name: "Actions for selective enforcement point with case sensitive input",
constraint: &unstructured.Unstructured{
Object: map[string]interface{}{
"spec": map[string]interface{}{
"scopedEnforcementActions": []interface{}{
map[string]interface{}{
"enforcementPoints": []interface{}{
map[string]interface{}{
"name": AuditEnforcementPoint,
},
map[string]interface{}{
"name": "Validation.Gatekeeper.Sh",
},
},
"action": "warn",
},
map[string]interface{}{
"enforcementPoints": []interface{}{
map[string]interface{}{
"name": "*",
},
},
"action": "deny",
},
},
},
},
},
expected: map[string]map[string]bool{
ritazh marked this conversation as resolved.
Show resolved Hide resolved
WebhookEnforcementPoint: {
"deny": true,
},
GatorEnforcementPoint: {
"deny": true,
},
},
eps: []string{WebhookEnforcementPoint, GatorEnforcementPoint},
},
{
name: "wildcard enforcement point in scoped enforcement action, get actions for two enforcement points",
constraint: &unstructured.Unstructured{
Object: map[string]interface{}{
"spec": map[string]interface{}{
"scopedEnforcementActions": []interface{}{
map[string]interface{}{
"enforcementPoints": []interface{}{
map[string]interface{}{
"name": AuditEnforcementPoint,
},
map[string]interface{}{
"name": WebhookEnforcementPoint,
},
},
"action": "warn",
},
map[string]interface{}{
"enforcementPoints": []interface{}{
map[string]interface{}{
"name": AllEnforcementPoints,
},
},
"action": "deny",
},
},
},
},
},
expected: map[string]map[string]bool{
AuditEnforcementPoint: {
"warn": true,
"deny": true,
},
WebhookEnforcementPoint: {
"warn": true,
"deny": true,
},
},
eps: []string{WebhookEnforcementPoint, AuditEnforcementPoint},
},
}

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

got := make(map[string]map[string]bool)
for ep, actions := range actions {
got[ep] = make(map[string]bool)
for _, action := range actions {
got[ep][action] = true
}
}

if !reflect.DeepEqual(got, tt.expected) {
t.Errorf("Expected %v, got %v", tt.expected, actions)
}
})
}
}

func TestIsEnforcementActionScoped(t *testing.T) {
tests := []struct {
name string
action string
want bool
}{
{
name: "scoped enforcement action",
action: "scoped",
want: true,
},
{
name: "Scoped enforcement action",
action: "Scoped",
want: false,
},
{
name: "Non-scoped enforcement action",
action: "Deny",
want: false,
},
{
name: "Empty enforcement action",
action: "",
want: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := IsEnforcementActionScoped(tt.action)
if got != tt.want {
t.Errorf("Expected %v, got %v", tt.want, got)
}
})
}
}
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.

Loading
Loading