Skip to content

Commit

Permalink
feat: support expansion in gator verify (#3650)
Browse files Browse the repository at this point in the history
Signed-off-by: David-Jaeyoon-Lee <[email protected]>
Co-authored-by: Sertaç Özercan <[email protected]>
Co-authored-by: Rita Zhang <[email protected]>
  • Loading branch information
3 people authored Nov 14, 2024
1 parent 044bc89 commit 31932a2
Show file tree
Hide file tree
Showing 11 changed files with 298 additions and 11 deletions.
7 changes: 5 additions & 2 deletions pkg/gator/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ var (
// ErrNotASyncSet indicates the user-indicated file does not contain a
// SyncSet.
ErrNotASyncSet = errors.New("not a SyncSet")
// ErrNotASyncSet indicates the user-indicated file does not contain a
// SyncSet.
// ErrNotAGVKManifest indicates the user-indicated file does not contain a
// GVK Manifest.
ErrNotAGVKManifest = errors.New("not a GVKManifest")
// ErrNotAnExpansion indicates the user-indicated file does not contain an
// ExpansionTemplate.
ErrNotAnExpansion = errors.New("not an Expansion Template")
// ErrAddingTemplate indicates a problem instantiating a Suite's ConstraintTemplate.
ErrAddingTemplate = errors.New("adding template")
// ErrAddingConstraint indicates a problem instantiating a Suite's Constraint.
Expand Down
72 changes: 72 additions & 0 deletions pkg/gator/fixtures/fixtures.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,34 @@ spec:
}
`

TemplateRestrictCustomField = `
kind: ConstraintTemplate
apiVersion: templates.gatekeeper.sh/v1beta1
metadata:
name: restrictedcustomfield
spec:
crd:
spec:
names:
kind: RestrictedCustomField
validation:
openAPIV3Schema:
type: object
properties:
expectedCustomField:
type: boolean
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package restrictedcustomfield
violation[{"msg": msg}] {
got := input.review.object.spec.customField
expected := input.parameters.expectedCustomField
got == expected
msg := sprintf("foo object has restricted custom field value of %v", [expected])
}
`

ConstraintAlwaysValidate = `
kind: AlwaysValidate
apiVersion: constraints.gatekeeper.sh/v1beta1
Expand Down Expand Up @@ -262,6 +290,22 @@ metadata:
name: other
`

ConstraintRestrictCustomField = `
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: RestrictedCustomField
metadata:
name: restrict-foo-custom-field
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Foo"]
namespaces:
- "default"
parameters:
expectedCustomField: true
`

Object = `
kind: Object
apiVersion: group.sh/v1
Expand Down Expand Up @@ -328,6 +372,17 @@ apiVersion: group.sh/v1
metadata:
name: object`

ObjectFooTemplate = `
apiVersion: apps/v1
kind: FooTemplate
metadata:
name: foo-template
spec:
template:
spec:
customField: true
`

NamespaceSelected = `
kind: Namespace
apiVersion: /v1
Expand Down Expand Up @@ -682,4 +737,21 @@ spec:
- apiGroups: ["*"]
kinds: ["*"]
`

ExpansionRestrictCustomField = `
apiVersion: expansion.gatekeeper.sh/v1alpha1
kind: ExpansionTemplate
metadata:
name: expand-foo
spec:
applyTo:
- groups: [ "apps" ]
kinds: [ "FooTemplate" ]
versions: [ "v1" ]
templateSource: "spec.template"
generatedGVK:
kind: "Foo"
group: ""
version: "v1"
`
)
14 changes: 14 additions & 0 deletions pkg/gator/reader/read_resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,20 @@ func ReadConstraint(f fs.FS, path string) (*unstructured.Unstructured, error) {
return u, nil
}

func ReadExpansion(f fs.FS, path string) (*unstructured.Unstructured, error) {
u, err := ReadObject(f, path)
if err != nil {
return nil, err
}

gvk := u.GroupVersionKind()
if gvk.Group != "expansion.gatekeeper.sh" || gvk.Kind != "ExpansionTemplate" {
return nil, gator.ErrNotAnExpansion
}

return u, nil
}

// ReadK8sResources reads JSON or YAML k8s resources from an io.Reader,
// decoding them into Unstructured objects and returning those objects as a
// slice.
Expand Down
80 changes: 73 additions & 7 deletions pkg/gator/verify/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import (
"github.com/open-policy-agent/frameworks/constraint/pkg/client/reviews"
"github.com/open-policy-agent/frameworks/constraint/pkg/types"
"github.com/open-policy-agent/gatekeeper/v3/apis"
"github.com/open-policy-agent/gatekeeper/v3/pkg/expansion"
"github.com/open-policy-agent/gatekeeper/v3/pkg/gator"
"github.com/open-policy-agent/gatekeeper/v3/pkg/gator/expand"
"github.com/open-policy-agent/gatekeeper/v3/pkg/gator/reader"
mutationtypes "github.com/open-policy-agent/gatekeeper/v3/pkg/mutation/types"
"github.com/open-policy-agent/gatekeeper/v3/pkg/target"
Expand Down Expand Up @@ -179,6 +181,20 @@ func (r *Runner) runCases(ctx context.Context, suiteDir string, filter Filter, t
return c, nil
}

newExpander := func() (*expand.Expander, error) {
e, err := r.makeTestExpander(suiteDir, t)
if err != nil {
return nil, err
}

return e, nil
}

_, err := newExpander()
if err != nil {
return nil, err
}

results := make([]CaseResult, len(t.Cases))

for i, c := range t.Cases {
Expand All @@ -187,7 +203,7 @@ func (r *Runner) runCases(ctx context.Context, suiteDir string, filter Filter, t
continue
}

results[i] = r.runCase(ctx, newClient, suiteDir, c)
results[i] = r.runCase(ctx, newClient, newExpander, suiteDir, c)
}

return results, nil
Expand Down Expand Up @@ -216,6 +232,22 @@ func (r *Runner) makeTestClient(ctx context.Context, suiteDir string, t *Test) (
return client, nil
}

func (r *Runner) makeTestExpander(suiteDir string, t *Test) (*expand.Expander, error) {
// Support Mutator logic? Then we need to add support for mutators as well or do we just ignore them?
expansionPath := t.Expansion
if expansionPath == "" {
return nil, nil
}

et, err := reader.ReadExpansion(r.filesystem, path.Join(suiteDir, expansionPath))
if err != nil {
return nil, err
}

er, err := expand.NewExpander([]*unstructured.Unstructured{et})
return er, err
}

func (r *Runner) addConstraint(ctx context.Context, suiteDir, constraintPath string, client gator.Client) error {
if constraintPath == "" {
return fmt.Errorf("%w: missing constraint", gator.ErrInvalidSuite)
Expand Down Expand Up @@ -252,9 +284,9 @@ func (r *Runner) addTemplate(suiteDir, templatePath string, client gator.Client)
}

// RunCase executes a Case and returns the result of the run.
func (r *Runner) runCase(ctx context.Context, newClient func() (gator.Client, error), suiteDir string, tc *Case) CaseResult {
func (r *Runner) runCase(ctx context.Context, newClient func() (gator.Client, error), newExpander func() (*expand.Expander, error), suiteDir string, tc *Case) CaseResult {
start := time.Now()
trace, err := r.checkCase(ctx, newClient, suiteDir, tc)
trace, err := r.checkCase(ctx, newClient, newExpander, suiteDir, tc)

return CaseResult{
Name: tc.Name,
Expand All @@ -264,7 +296,7 @@ func (r *Runner) runCase(ctx context.Context, newClient func() (gator.Client, er
}
}

func (r *Runner) checkCase(ctx context.Context, newClient func() (gator.Client, error), suiteDir string, tc *Case) (trace *string, err error) {
func (r *Runner) checkCase(ctx context.Context, newClient func() (gator.Client, error), newExpander func() (*expand.Expander, error), suiteDir string, tc *Case) (trace *string, err error) {
if tc.Object == "" {
return nil, fmt.Errorf("%w: must define object", gator.ErrInvalidCase)
}
Expand All @@ -274,7 +306,7 @@ func (r *Runner) checkCase(ctx context.Context, newClient func() (gator.Client,
return nil, fmt.Errorf("%w: assertions must be non-empty", gator.ErrInvalidCase)
}

review, err := r.runReview(ctx, newClient, suiteDir, tc)
review, err := r.runReview(ctx, newClient, newExpander, suiteDir, tc)
if err != nil {
return nil, err
}
Expand All @@ -293,12 +325,17 @@ func (r *Runner) checkCase(ctx context.Context, newClient func() (gator.Client,
return trace, nil
}

func (r *Runner) runReview(ctx context.Context, newClient func() (gator.Client, error), suiteDir string, tc *Case) (*types.Responses, error) {
func (r *Runner) runReview(ctx context.Context, newClient func() (gator.Client, error), newExpander func() (*expand.Expander, error), suiteDir string, tc *Case) (*types.Responses, error) {
c, err := newClient()
if err != nil {
return nil, err
}

e, err := newExpander()
if err != nil {
return nil, err
}

toReviewPath := path.Join(suiteDir, tc.Object)
toReviewObjs, err := readObjects(r.filesystem, toReviewPath)
if err != nil {
Expand Down Expand Up @@ -327,7 +364,36 @@ func (r *Runner) runReview(ctx context.Context, newClient func() (gator.Client,
Object: *toReview,
Source: mutationtypes.SourceTypeOriginal,
}
return c.Review(ctx, au, reviews.EnforcementPoint(util.GatorEnforcementPoint))

review, err := c.Review(ctx, au, reviews.EnforcementPoint(util.GatorEnforcementPoint))
if err != nil {
return nil, fmt.Errorf("reviewing %v %s/%s: %w",
toReview.GroupVersionKind(), toReview.GetNamespace(), toReview.GetName(), err)
}

if e != nil {
resultants, err := e.Expand(toReview)
if err != nil {
return nil, fmt.Errorf("expanding resource %s: %w", toReview.GetName(), err)
}

for _, resultant := range resultants {
au := target.AugmentedUnstructured{
Object: *resultant.Obj,
Source: mutationtypes.SourceTypeGenerated,
}
resultantReview, err := c.Review(ctx, au, reviews.EnforcementPoint(util.GatorEnforcementPoint))
if err != nil {
return nil, fmt.Errorf("reviewing expanded resource %v %s/%s: %w",
resultant.Obj.GroupVersionKind(), resultant.Obj.GetNamespace(), resultant.Obj.GetName(), err)
}
expansion.OverrideEnforcementAction(resultant.EnforcementAction, resultantReview)
expansion.AggregateResponses(resultant.TemplateName, review, resultantReview)
expansion.AggregateStats(resultant.TemplateName, review, resultantReview)
}
}

return review, err
}

func (r *Runner) validateAndReviewAdmissionReviewRequest(ctx context.Context, c gator.Client, toReview *unstructured.Unstructured) (*types.Responses, error) {
Expand Down
62 changes: 62 additions & 0 deletions pkg/gator/verify/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1170,6 +1170,68 @@ func TestRunner_Run(t *testing.T) {
},
},
},
{
name: "expansion system",
suite: Suite{
Tests: []Test{
{
Name: "check custom field with expansion system",
Template: "template.yaml",
Constraint: "constraint.yaml",
Expansion: "expansion.yaml",
Cases: []*Case{
{
Name: "Foo Template object",
Object: "foo-template.yaml",
Assertions: []Assertion{{Message: ptr.To[string]("foo object has restricted custom field")}},
},
},
},
{
Name: "check custom field without expansion system",
Template: "template.yaml",
Constraint: "constraint.yaml",
Cases: []*Case{
{
Name: "Foo Template object",
Object: "foo-template.yaml",
Assertions: []Assertion{{Violations: gator.IntStrFromStr("no")}},
},
},
},
},
},
f: fstest.MapFS{
"template.yaml": &fstest.MapFile{
Data: []byte(fixtures.TemplateRestrictCustomField),
},
"constraint.yaml": &fstest.MapFile{
Data: []byte(fixtures.ConstraintRestrictCustomField),
},
"foo-template.yaml": &fstest.MapFile{
Data: []byte(fixtures.ObjectFooTemplate),
},
"expansion.yaml": &fstest.MapFile{
Data: []byte(fixtures.ExpansionRestrictCustomField),
},
},
want: SuiteResult{
TestResults: []TestResult{
{
Name: "check custom field with expansion system",
CaseResults: []CaseResult{
{Name: "Foo Template object"},
},
},
{
Name: "check custom field without expansion system",
CaseResults: []CaseResult{
{Name: "Foo Template object"},
},
},
},
},
},
}

for _, tc := range testCases {
Expand Down
4 changes: 4 additions & 0 deletions pkg/gator/verify/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ type Test struct {
// the Suite. Must be an instance of Template.
Constraint string `json:"constraint"`

// Expansion is the path to the Expansion, relative to the file defining
// the Suite.
Expansion string `json:"expansion"`

// Cases are the test cases to run on the instantiated Constraint.
// Mutually exclusive with Invalid.
Cases []*Case `json:"cases,omitempty"`
Expand Down
8 changes: 8 additions & 0 deletions test/gator/verify/allow_expansion.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: apps/v1
kind: FooTemplate
metadata:
name: foo-template
spec:
template:
foo: bar

8 changes: 8 additions & 0 deletions test/gator/verify/deny_expansion.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: apps/v1
kind: FooTemplate
metadata:
name: foo-template
spec:
template:
foo: qux

15 changes: 15 additions & 0 deletions test/gator/verify/expansion.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apiVersion: expansion.gatekeeper.sh/v1alpha1
kind: ExpansionTemplate
metadata:
name: expand-foo
spec:
applyTo:
- groups: [ "apps" ]
kinds: [ "FooTemplate" ]
versions: [ "v1" ]
templateSource: "spec.template"
generatedGVK:
kind: "FooIsBar"
group: ""
version: "v1"

Loading

0 comments on commit 31932a2

Please sign in to comment.