From e25933643f68abcec2d81157a226be72f4ad2565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charles-Edouard=20Br=C3=A9t=C3=A9ch=C3=A9?= Date: Tue, 5 Nov 2024 22:29:02 +0100 Subject: [PATCH] feat: add cel envoy lib (#162) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Charles-Edouard Brétéché --- Makefile | 36 +++- apis/v1alpha1/doc.go | 5 + apis/v1alpha1/types.go | 73 +++++++ apis/v1alpha1/zz_generated.deepcopy.go | 113 +++++++++++ apis/v1alpha1/zz_generated.register.go | 67 +++++++ ...nvoy.kyverno.io_authorizationpolicies.yaml | 108 ++++++++++ go.mod | 60 ++++-- go.sum | 122 ++++++++++-- hack/boilerplate.go.txt | 15 ++ .../policies/demo-policy.example.com.yaml | 29 +++ pkg/authz/cel/env.go | 39 ++++ pkg/authz/cel/env_test.go | 35 ++++ pkg/authz/cel/libs/envoy/impl.go | 186 ++++++++++++++++++ pkg/authz/cel/libs/envoy/lib.go | 108 ++++++++++ pkg/authz/cel/libs/jwt/lib.go | 64 ++++++ pkg/authz/cel/libs/jwt/lib_test.go | 45 +++++ pkg/authz/cel/libs/jwt/token.go | 51 +++++ pkg/authz/cel/utils/convert.go | 19 ++ pkg/authz/cel/variables.go | 67 +++++++ pkg/authz/grpc.go | 16 +- pkg/authz/service.go | 72 ++----- pkg/authz/service_test.go | 79 -------- pkg/commands/serve/command.go | 19 +- pkg/policy/compiler.go | 117 +++++++++++ pkg/policy/provider.go | 73 +++++++ 25 files changed, 1443 insertions(+), 175 deletions(-) create mode 100755 apis/v1alpha1/doc.go create mode 100644 apis/v1alpha1/types.go create mode 100644 apis/v1alpha1/zz_generated.deepcopy.go create mode 100644 apis/v1alpha1/zz_generated.register.go create mode 100644 config/crds/envoy.kyverno.io_authorizationpolicies.yaml create mode 100644 hack/boilerplate.go.txt create mode 100644 manifests/policies/demo-policy.example.com.yaml create mode 100644 pkg/authz/cel/env.go create mode 100644 pkg/authz/cel/env_test.go create mode 100644 pkg/authz/cel/libs/envoy/impl.go create mode 100644 pkg/authz/cel/libs/envoy/lib.go create mode 100644 pkg/authz/cel/libs/jwt/lib.go create mode 100644 pkg/authz/cel/libs/jwt/lib_test.go create mode 100644 pkg/authz/cel/libs/jwt/token.go create mode 100644 pkg/authz/cel/utils/convert.go create mode 100644 pkg/authz/cel/variables.go delete mode 100644 pkg/authz/service_test.go create mode 100644 pkg/policy/compiler.go create mode 100644 pkg/policy/provider.go diff --git a/Makefile b/Makefile index b77591e..218f167 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,10 @@ KIND := $(TOOLS_DIR)/kind KIND_VERSION := v0.22.0 KO ?= $(TOOLS_DIR)/ko KO_VERSION ?= v0.15.1 -TOOLS := $(HELM) $(KIND) $(KO) +CONTROLLER_GEN ?= $(TOOLS_DIR)/controller-gen +CONTROLLER_GEN_VERSION := latest +REGISTER_GEN ?= $(TOOLS_DIR)/register-gen +REGISTER_GEN_VERSION := v0.28.0 PIP ?= "pip" ifeq ($(GOOS), darwin) SED := gsed @@ -55,9 +58,19 @@ $(KO): @echo Install ko... >&2 @GOBIN=$(TOOLS_DIR) go install github.com/google/ko@$(KO_VERSION) +$(CONTROLLER_GEN): + @GOBIN=$(TOOLS_DIR) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_GEN_VERSION) + +$(REGISTER_GEN): + @GOBIN=$(TOOLS_DIR) go install k8s.io/code-generator/cmd/register-gen@$(REGISTER_GEN_VERSION) + .PHONY: install-tools install-tools: ## Install tools -install-tools: $(TOOLS) +install-tools: $(HELM) +install-tools: $(KIND) +install-tools: $(KO) +install-tools: $(CONTROLLER_GEN) +install-tools: $(REGISTER_GEN) .PHONY: clean-tools clean-tools: ## Remove installed tools @@ -77,6 +90,15 @@ $(PACKAGE_SHIM): $(GOPATH_SHIM) @echo Create package shim... >&2 @mkdir -p $(GOPATH_SHIM)/src/github.com/$(ORG) && ln -s -f ${PWD} $(PACKAGE_SHIM) +.PHONY: codegen-crds +codegen-crds: ## Generate CRDs +codegen-crds: $(CONTROLLER_GEN) +codegen-crds: $(REGISTER_GEN) + @echo Generate CRDs... >&2 + @$(CONTROLLER_GEN) paths=./apis/v1alpha1/... object + @$(CONTROLLER_GEN) paths=./apis/v1alpha1/... crd:crdVersions=v1,ignoreUnexportedFields=true,generateEmbeddedObjectMeta=false output:dir=./config/crds + @$(REGISTER_GEN) --input-dirs=./apis/v1alpha1 --go-header-file=./hack/boilerplate.go.txt --output-base=. + .PHONY: codegen-mkdocs codegen-mkdocs: ## Generate mkdocs website @echo Generate mkdocs website... >&2 @@ -88,6 +110,7 @@ codegen-mkdocs: ## Generate mkdocs website .PHONY: codegen codegen: ## Rebuild all generated code and docs codegen: codegen-mkdocs +codegen: codegen-crds .PHONY: verify-codegen verify-codegen: ## Verify all generated code and docs are up to date @@ -121,7 +144,7 @@ build: ## Build build: fmt build: vet build: - @echo "Build..." >&2 + @echo Build... >&2 @LD_FLAGS=$(LD_FLAGS) go build . ############## @@ -137,7 +160,7 @@ ko-build: ## Build Docker image with ko ko-build: fmt ko-build: vet ko-build: $(KO) - @echo "Build Docker image with ko..." >&2 + @echo Build Docker image with ko... >&2 @LD_FLAGS=$(LD_FLAGS) KO_DOCKER_REPO=$(KO_REGISTRY) $(KO) build . --preserve-import-paths --tags=$(KO_TAGS) .PHONY: ko-publish @@ -146,7 +169,7 @@ ko-publish: fmt ko-publish: vet ko-publish: ko-login ko-publish: $(KO) - @echo "Publish Docker image with ko..." >&2 + @echo Publish Docker image with ko... >&2 @LD_FLAGS=$(LD_FLAGS) KO_DOCKER_REPO=$(REGISTRY)/$(REPO)/$(IMAGE) $(KO) build . --bare --tags=$(KO_TAGS) --platform=$(KO_PLATFORMS) ########## @@ -264,7 +287,10 @@ install-kyverno-sidecar-injector: $(HELM) .PHONY: install-kyverno-authz-server install-kyverno-authz-server: ## Install kyverno-authz-server chart install-kyverno-authz-server: kind-load-image +install-kyverno-authz-server: codegen-crds install-kyverno-authz-server: $(HELM) + @echo Install CRDs... >&2 + @kubectl apply -f config/crds @echo Build kyverno-authz-server dependecy... >&2 @$(HELM) dependency build --skip-refresh ./charts/kyverno-authz-server @echo Install kyverno-authz-server chart... >&2 diff --git a/apis/v1alpha1/doc.go b/apis/v1alpha1/doc.go new file mode 100755 index 0000000..84374b9 --- /dev/null +++ b/apis/v1alpha1/doc.go @@ -0,0 +1,5 @@ +// +k8s:openapi-gen=true +// +k8s:deepcopy-gen=package +// +groupName=envoy.kyverno.io + +package v1alpha1 diff --git a/apis/v1alpha1/types.go b/apis/v1alpha1/types.go new file mode 100644 index 0000000..9cf4f45 --- /dev/null +++ b/apis/v1alpha1/types.go @@ -0,0 +1,73 @@ +package v1alpha1 + +import ( + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +kubebuilder:object:root=true +// +kubebuilder:resource:scope=Cluster + +type AuthorizationPolicy struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec AuthorizationPolicySpec `json:"spec"` +} + +type AuthorizationPolicySpec struct { + // FailurePolicy defines how to handle failures for the policy. Failures can + // occur from CEL expression parse errors, type check errors, runtime errors and invalid + // or mis-configured policy definitions. + // + // FailurePolicy does not define how validations that evaluate to false are handled. + // + // Allowed values are Ignore or Fail. Defaults to Fail. + // +optional + FailurePolicy *admissionregistrationv1.FailurePolicyType `json:"failurePolicy,omitempty"` + + // Variables contain definitions of variables that can be used in composition of other expressions. + // Each variable is defined as a named CEL expression. + // The variables defined here will be available under `variables` in other expressions of the policy + // except MatchConditions because MatchConditions are evaluated before the rest of the policy. + // + // The expression of a variable can refer to other variables defined earlier in the list but not those after. + // Thus, Variables must be sorted by the order of first appearance and acyclic. + // +patchMergeKey=name + // +patchStrategy=merge + // +listType=map + // +listMapKey=name + // +optional + Variables []admissionregistrationv1.Variable `json:"variables,omitempty" patchStrategy:"merge" patchMergeKey:"name"` + + // Authorizations contain CEL expressions which is used to apply the authorization. + // +listType=atomic + // +optional + Authorizations []Authorization `json:"authorizations,omitempty"` +} + +func (s *AuthorizationPolicySpec) GetFailurePolicy() admissionregistrationv1.FailurePolicyType { + if s.FailurePolicy == nil { + return admissionregistrationv1.Fail + } + return *s.FailurePolicy +} + +type Authorization struct { + // Expression represents the expression which will be evaluated by CEL. + // ref: https://github.com/google/cel-spec + // CEL expressions have access to CEL variables as well as some other useful variables: + // + // - 'object' - The object from the incoming request. (https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto#service-auth-v3-checkrequest) + // + // CEL expressions are expected to return an envoy CheckResponse (https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto#service-auth-v3-checkresponse). + // +required + Expression string `json:"expression"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type AuthorizationPolicyList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + Items []AuthorizationPolicy `json:"items"` +} diff --git a/apis/v1alpha1/zz_generated.deepcopy.go b/apis/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 0000000..4637eda --- /dev/null +++ b/apis/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,113 @@ +//go:build !ignore_autogenerated + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "k8s.io/api/admissionregistration/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Authorization) DeepCopyInto(out *Authorization) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Authorization. +func (in *Authorization) DeepCopy() *Authorization { + if in == nil { + return nil + } + out := new(Authorization) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuthorizationPolicy) DeepCopyInto(out *AuthorizationPolicy) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizationPolicy. +func (in *AuthorizationPolicy) DeepCopy() *AuthorizationPolicy { + if in == nil { + return nil + } + out := new(AuthorizationPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AuthorizationPolicy) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuthorizationPolicyList) DeepCopyInto(out *AuthorizationPolicyList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]AuthorizationPolicy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizationPolicyList. +func (in *AuthorizationPolicyList) DeepCopy() *AuthorizationPolicyList { + if in == nil { + return nil + } + out := new(AuthorizationPolicyList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AuthorizationPolicyList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuthorizationPolicySpec) DeepCopyInto(out *AuthorizationPolicySpec) { + *out = *in + if in.FailurePolicy != nil { + in, out := &in.FailurePolicy, &out.FailurePolicy + *out = new(v1.FailurePolicyType) + **out = **in + } + if in.Variables != nil { + in, out := &in.Variables, &out.Variables + *out = make([]v1.Variable, len(*in)) + copy(*out, *in) + } + if in.Authorizations != nil { + in, out := &in.Authorizations, &out.Authorizations + *out = make([]Authorization, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizationPolicySpec. +func (in *AuthorizationPolicySpec) DeepCopy() *AuthorizationPolicySpec { + if in == nil { + return nil + } + out := new(AuthorizationPolicySpec) + in.DeepCopyInto(out) + return out +} diff --git a/apis/v1alpha1/zz_generated.register.go b/apis/v1alpha1/zz_generated.register.go new file mode 100644 index 0000000..d6d2ee7 --- /dev/null +++ b/apis/v1alpha1/zz_generated.register.go @@ -0,0 +1,67 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by register-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName specifies the group name used to register the objects. +const GroupName = "envoy.kyverno.io" + +// GroupVersion specifies the group and the version used to register the objects. +var GroupVersion = v1.GroupVersion{Group: GroupName, Version: "v1alpha1"} + +// SchemeGroupVersion is group version used to register these objects +// Deprecated: use GroupVersion instead. +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + // localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes. + SchemeBuilder runtime.SchemeBuilder + localSchemeBuilder = &SchemeBuilder + // Depreciated: use Install instead + AddToScheme = localSchemeBuilder.AddToScheme + Install = localSchemeBuilder.AddToScheme +) + +func init() { + // We only register manually written functions here. The registration of the + // generated functions takes place in the generated files. The separation + // makes the code compile even when the generated files are missing. + localSchemeBuilder.Register(addKnownTypes) +} + +// Adds the list of known types to Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &AuthorizationPolicy{}, + &AuthorizationPolicyList{}, + ) + // AddToGroupVersion allows the serialization of client types like ListOptions. + v1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/config/crds/envoy.kyverno.io_authorizationpolicies.yaml b/config/crds/envoy.kyverno.io_authorizationpolicies.yaml new file mode 100644 index 0000000..d1a0c9e --- /dev/null +++ b/config/crds/envoy.kyverno.io_authorizationpolicies.yaml @@ -0,0 +1,108 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.5 + name: authorizationpolicies.envoy.kyverno.io +spec: + group: envoy.kyverno.io + names: + kind: AuthorizationPolicy + listKind: AuthorizationPolicyList + plural: authorizationpolicies + singular: authorizationpolicy + scope: Cluster + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + authorizations: + description: Authorizations contain CEL expressions which is used + to apply the authorization. + items: + properties: + expression: + description: |- + Expression represents the expression which will be evaluated by CEL. + ref: https://github.com/google/cel-spec + CEL expressions have access to CEL variables as well as some other useful variables: + + - 'object' - The object from the incoming request. (https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto#service-auth-v3-checkrequest) + + CEL expressions are expected to return an envoy CheckResponse (https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto#service-auth-v3-checkresponse). + type: string + required: + - expression + type: object + type: array + x-kubernetes-list-type: atomic + failurePolicy: + description: |- + FailurePolicy defines how to handle failures for the policy. Failures can + occur from CEL expression parse errors, type check errors, runtime errors and invalid + or mis-configured policy definitions. + + FailurePolicy does not define how validations that evaluate to false are handled. + + Allowed values are Ignore or Fail. Defaults to Fail. + type: string + variables: + description: |- + Variables contain definitions of variables that can be used in composition of other expressions. + Each variable is defined as a named CEL expression. + The variables defined here will be available under `variables` in other expressions of the policy + except MatchConditions because MatchConditions are evaluated before the rest of the policy. + + The expression of a variable can refer to other variables defined earlier in the list but not those after. + Thus, Variables must be sorted by the order of first appearance and acyclic. + items: + description: Variable is the definition of a variable that is used + for composition. A variable is defined as a named expression. + properties: + expression: + description: |- + Expression is the expression that will be evaluated as the value of the variable. + The CEL expression has access to the same identifiers as the CEL expressions in Validation. + type: string + name: + description: |- + Name is the name of the variable. The name must be a valid CEL identifier and unique among all variables. + The variable can be accessed in other expressions through `variables` + For example, if name is "foo", the variable will be available as `variables.foo` + type: string + required: + - expression + - name + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true diff --git a/go.mod b/go.mod index 50acb62..2e9247c 100644 --- a/go.mod +++ b/go.mod @@ -1,61 +1,91 @@ module github.com/kyverno/kyverno-envoy-plugin -go 1.22.8 +go 1.23.0 require ( github.com/envoyproxy/go-control-plane v0.13.1 + github.com/google/cel-go v0.20.1 github.com/jmespath-community/go-jmespath v1.1.2-0.20240117150817-e430401a2172 github.com/kyverno/kyverno-json v0.0.3-alpha.2 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 go.uber.org/multierr v1.11.0 gomodules.xyz/jsonpatch/v2 v2.4.0 - google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 + google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 google.golang.org/grpc v1.67.1 k8s.io/apimachinery v0.31.2 -) - -require ( - github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/x448/float16 v0.8.4 // indirect + k8s.io/apiserver v0.31.2 + k8s.io/client-go v0.31.2 + sigs.k8s.io/controller-runtime v0.17.0 ) require ( github.com/IGLOU-EU/go-wildcard v1.0.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/aquilax/truncate v1.0.0 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.11.2 // indirect github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect + github.com/evanphx/json-patch/v5 v5.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-logr/logr v1.4.2 // indirect + github.com/go-openapi/jsonpointer v0.20.2 // indirect + github.com/go-openapi/jsonreference v0.20.4 // indirect + github.com/go-openapi/swag v0.22.9 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gopherjs/gopherjs v1.17.2 // indirect + github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jinzhu/copier v0.4.0 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/jtolds/gls v4.20.0+incompatible // indirect github.com/kyverno/kyverno v1.5.0-rc1.0.20240202083228-5f0d53fe3482 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stoewer/go-strcase v1.3.0 // indirect + github.com/x448/float16 v0.8.4 // indirect github.com/zach-klippenstein/goregen v0.0.0-20160303162051-795b5e3961ea // indirect - golang.org/x/crypto v0.26.0 // indirect - golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/text v0.17.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/oauth2 v0.22.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect + golang.org/x/time v0.5.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 // indirect google.golang.org/protobuf v1.35.1 gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.31.2 + k8s.io/component-base v0.31.2 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 - sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 72f638b..ddf0a8e 100644 --- a/go.sum +++ b/go.sum @@ -1,30 +1,59 @@ github.com/IGLOU-EU/go-wildcard v1.0.3 h1:r8T46+8/9V1STciXJomTWRpPEv4nGJATDbJkdU0Nou0= github.com/IGLOU-EU/go-wildcard v1.0.3/go.mod h1:/qeV4QLmydCbwH0UMQJmXDryrFKJknWi/jjO8IiuQfY= +github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/aquilax/truncate v1.0.0 h1:UgIGS8U/aZ4JyOJ2h3xcF5cSQ06+gGBnjxH2RUHJe0U= github.com/aquilax/truncate v1.0.0/go.mod h1:BeMESIDMlvlS3bmg4BVvBbbZUNwWtS8uzYPAKXwwhLw= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20 h1:N+3sFI5GUjRKBi+i0TxYVST9h4Ie192jJWpHvthBBgg= -github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8ETbOasdwEV+avkR75ZzsVV9WI= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.11.2 h1:1onLa9DcsMYO9P+CXaL0dStDqQ2EHHXLiz+BtnqkLAU= +github.com/emicklei/go-restful/v3 v3.11.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.13.1 h1:vPfJZCkob6yTMEgS+0TwfTUfbHjfy/6vOJ8hUWX/uXE= github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw= github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= +github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= +github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= +github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= +github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= +github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE= +github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84= +github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= +github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= +github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -33,14 +62,22 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gxui v0.0.0-20151028112939-f85e0a97b3a4 h1:OL2d27ueTKnlQJoqLW2fc9pWYulFnJYLWzomGV7HqZo= github.com/google/gxui v0.0.0-20151028112939-f85e0a97b3a4/go.mod h1:Pw1H1OjSNHiqeuxAduB1BKYXIwFtsyrY47nEqSgEiCM= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jmespath-community/go-jmespath v1.1.2-0.20240117150817-e430401a2172 h1:XQYEhx+bEiWn6eiHFivu4wEHm91FoZ/gCvoLZK6Ze5Y= github.com/jmespath-community/go-jmespath v1.1.2-0.20240117150817-e430401a2172/go.mod h1:j4OeykGPBbhX3rw4AOPGXSmX2/zuWXktm704A4MtHFs= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= @@ -55,11 +92,20 @@ github.com/kyverno/kyverno v1.5.0-rc1.0.20240202083228-5f0d53fe3482 h1:mGQbOhxoH github.com/kyverno/kyverno v1.5.0-rc1.0.20240202083228-5f0d53fe3482/go.mod h1:uEm7WtaqOsPP3Jx6EOkO2PjHu6vf0MFaMD6w1ol7hAQ= github.com/kyverno/kyverno-json v0.0.3-alpha.2 h1:uB/Nbwa1lz81wK+6RXD/0UjBJOwv9/RkhTPl4BlZRbI= github.com/kyverno/kyverno-json v0.0.3-alpha.2/go.mod h1:EmCRYvDrIbMSmFouwYTdDee37xpWsr4vWhiJsxBDXSc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= @@ -67,6 +113,14 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -78,8 +132,15 @@ github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= +github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= @@ -90,45 +151,57 @@ github.com/zach-klippenstein/goregen v0.0.0-20160303162051-795b5e3961ea h1:Cyhwe github.com/zach-klippenstein/goregen v0.0.0-20160303162051-795b5e3961ea/go.mod h1:eNr558nEUjP8acGw8FFjTeWvSgU1stO7FAO6eknhHe4= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= +golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 h1:2oV8dfuIkM1Ti7DwXc0BJfnwr9csz4TDXI9EmiI+Rbw= +google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38/go.mod h1:vuAjtvlwkDKF6L1GQ0SokiRLCGFfeBUXWr/aFFkHACc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= @@ -141,20 +214,33 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0= k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk= +k8s.io/apiextensions-apiserver v0.29.2 h1:UK3xB5lOWSnhaCk0RFZ0LUacPZz9RY4wi/yt2Iu+btg= +k8s.io/apiextensions-apiserver v0.29.2/go.mod h1:aLfYjpA5p3OwtqNXQFkhJ56TB+spV8Gc4wfMhUA3/b8= k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw= k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/apiserver v0.31.2 h1:VUzOEUGRCDi6kX1OyQ801m4A7AUPglpsmGvdsekmcI4= +k8s.io/apiserver v0.31.2/go.mod h1:o3nKZR7lPlJqkU5I3Ove+Zx3JuoFjQobGX1Gctw6XuE= +k8s.io/client-go v0.31.2 h1:Y2F4dxU5d3AQj+ybwSMqQnpZH9F30//1ObxOKlTI9yc= +k8s.io/client-go v0.31.2/go.mod h1:NPa74jSVR/+eez2dFsEIHNa+3o09vtNaWwWwb1qSxSs= +k8s.io/component-base v0.31.2 h1:Z1J1LIaC0AV+nzcPRFqfK09af6bZ4D1nAOpWsy9owlA= +k8s.io/component-base v0.31.2/go.mod h1:9PeyyFN/drHjtJZMCTkSpQJS3U9OXORnHQqMLDz0sUQ= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= -k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 h1:MDF6h2H/h4tbzmtIKTuctcwZmY0tY9mD9fNT47QO6HI= +k8s.io/utils v0.0.0-20240921022957-49e7df575cb6/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.17.0 h1:fjJQf8Ukya+VjogLO6/bNX9HE6Y2xpsO5+fyS26ur/s= +sigs.k8s.io/controller-runtime v0.17.0/go.mod h1:+MngTvIQQQhfXtwfdGw/UOQ/aIaqsYywfCINOtwMO/s= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt new file mode 100644 index 0000000..0926592 --- /dev/null +++ b/hack/boilerplate.go.txt @@ -0,0 +1,15 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ diff --git a/manifests/policies/demo-policy.example.com.yaml b/manifests/policies/demo-policy.example.com.yaml new file mode 100644 index 0000000..1cd3374 --- /dev/null +++ b/manifests/policies/demo-policy.example.com.yaml @@ -0,0 +1,29 @@ +apiVersion: envoy.kyverno.io/v1alpha1 +kind: AuthorizationPolicy +metadata: + name: demo-policy.example.com +spec: + variables: + - name: request_headers + expression: object.attributes.request.http.headers + - name: force_unauthenticated + expression: variables.?request_headers["x-force-unauthenticated"].orValue("disabled") == "enabled" + - name: force_authorized + expression: variables.?request_headers["x-force-authorized"].orValue("false") == "true" + - name: metadata + expression: '{"my-new-metadata": "my-new-value"}' + authorizations: + - expression: > + !variables.force_unauthenticated && variables.force_authorized + ? envoy + .Allowed() + .WithHeader(envoy.Header("x-validated-by", "my-security-checkpoint")) + .WithoutHeader("user-agent") + .Response() + .WithMetadata(variables.metadata) + : envoy + .Denied(variables.force_unauthenticated ? 401 : 403) + .WithBody(variables.force_unauthenticated ? "Authentication Failed" : "Unauthorized Request") + .WithHeader(envoy.Header("x-validated-by", "my-security-checkpoint")) + .Response() + .WithMetadata(variables.metadata) diff --git a/pkg/authz/cel/env.go b/pkg/authz/cel/env.go new file mode 100644 index 0000000..9fb11f4 --- /dev/null +++ b/pkg/authz/cel/env.go @@ -0,0 +1,39 @@ +package cel + +import ( + "github.com/google/cel-go/cel" + "github.com/google/cel-go/ext" + "github.com/kyverno/kyverno-envoy-plugin/pkg/authz/cel/libs/envoy" + "github.com/kyverno/kyverno-envoy-plugin/pkg/authz/cel/libs/jwt" + "k8s.io/apiserver/pkg/cel/library" +) + +func NewEnv() (*cel.Env, error) { + // create new cel env + return cel.NewEnv( + // configure env + cel.HomogeneousAggregateLiterals(), + cel.EagerlyValidateDeclarations(true), + cel.DefaultUTCTimeZone(true), + cel.CrossTypeNumericComparisons(true), + // register common libs + cel.OptionalTypes(), + ext.Bindings(), + ext.Encoders(), + ext.Lists(), + ext.Math(), + ext.Protos(), + ext.Sets(), + ext.Strings(), + // register kubernetes libs + library.CIDR(), + library.Format(), + library.IP(), + library.Lists(), + library.Regex(), + library.URLs(), + // register our libs + envoy.Lib(), + jwt.Lib(), + ) +} diff --git a/pkg/authz/cel/env_test.go b/pkg/authz/cel/env_test.go new file mode 100644 index 0000000..60e86a1 --- /dev/null +++ b/pkg/authz/cel/env_test.go @@ -0,0 +1,35 @@ +package cel + +import ( + "reflect" + "testing" + + authv3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" + "github.com/google/cel-go/interpreter" + "github.com/stretchr/testify/assert" +) + +func TestNewEnv(t *testing.T) { + source := ` +envoy + .Denied(401) + .WithBody("Authentication Failed") + .WithHeader(envoy.Header("foo", "bar").KeepEmptyValue()) + .Response() + .WithMetadata({"my-new-metadata": "my-new-value"}) + .WithMessage("hello") +` + env, err := NewEnv() + assert.NoError(t, err) + ast, issues := env.Compile(source) + assert.Nil(t, issues) + prog, err := env.Program(ast) + assert.NoError(t, err) + assert.NotNil(t, prog) + out, _, err := prog.Eval(interpreter.EmptyActivation()) + assert.NoError(t, err) + assert.NotNil(t, out) + a, err := out.ConvertToNative(reflect.TypeFor[*authv3.CheckResponse]()) + assert.NoError(t, err) + assert.NotNil(t, a) +} diff --git a/pkg/authz/cel/libs/envoy/impl.go b/pkg/authz/cel/libs/envoy/impl.go new file mode 100644 index 0000000..16b6dc4 --- /dev/null +++ b/pkg/authz/cel/libs/envoy/impl.go @@ -0,0 +1,186 @@ +package envoy + +import ( + corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + authv3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" + typesv3 "github.com/envoyproxy/go-control-plane/envoy/type/v3" + "github.com/google/cel-go/common/types" + "github.com/google/cel-go/common/types/ref" + "github.com/kyverno/kyverno-envoy-plugin/pkg/authz/cel/utils" + status "google.golang.org/genproto/googleapis/rpc/status" + "google.golang.org/grpc/codes" + "google.golang.org/protobuf/types/known/structpb" +) + +type impl struct { + types.Adapter +} + +func (c *impl) allowed() ref.Val { + r := &authv3.OkHttpResponse{} + return c.NativeToValue(r) +} + +func (c *impl) ok_with_header(ok ref.Val, header ref.Val) ref.Val { + if ok, err := utils.ConvertToNative[*authv3.OkHttpResponse](ok); err != nil { + return types.WrapErr(err) + } else if header, err := utils.ConvertToNative[*corev3.HeaderValueOption](header); err != nil { + return types.WrapErr(err) + } else { + ok.Headers = append(ok.Headers, header) + return c.NativeToValue(ok) + } +} + +func (c *impl) ok_without_header(ok ref.Val, header ref.Val) ref.Val { + if ok, err := utils.ConvertToNative[*authv3.OkHttpResponse](ok); err != nil { + return types.WrapErr(err) + } else if header, err := utils.ConvertToNative[string](header); err != nil { + return types.WrapErr(err) + } else { + ok.HeadersToRemove = append(ok.HeadersToRemove, header) + return c.NativeToValue(ok) + } +} + +func (c *impl) ok_with_response_header(ok ref.Val, header ref.Val) ref.Val { + if ok, err := utils.ConvertToNative[*authv3.OkHttpResponse](ok); err != nil { + return types.WrapErr(err) + } else if header, err := utils.ConvertToNative[*corev3.HeaderValueOption](header); err != nil { + return types.WrapErr(err) + } else { + ok.ResponseHeadersToAdd = append(ok.ResponseHeadersToAdd, header) + return c.NativeToValue(ok) + } +} + +func (c *impl) ok_with_query_param(ok ref.Val, param ref.Val) ref.Val { + if ok, err := utils.ConvertToNative[*authv3.OkHttpResponse](ok); err != nil { + return types.WrapErr(err) + } else if param, err := utils.ConvertToNative[*corev3.QueryParameter](param); err != nil { + return types.WrapErr(err) + } else { + ok.QueryParametersToSet = append(ok.QueryParametersToSet, param) + return c.NativeToValue(ok) + } +} + +func (c *impl) ok_without_query_param(ok ref.Val, param ref.Val) ref.Val { + if ok, err := utils.ConvertToNative[*authv3.OkHttpResponse](ok); err != nil { + return types.WrapErr(err) + } else if param, err := utils.ConvertToNative[string](param); err != nil { + return types.WrapErr(err) + } else { + ok.QueryParametersToRemove = append(ok.QueryParametersToRemove, param) + return c.NativeToValue(ok) + } +} + +func (c *impl) denied(code ref.Val) ref.Val { + if code, err := utils.ConvertToNative[typesv3.StatusCode](code); err != nil { + return types.WrapErr(err) + } else { + return c.NativeToValue(&authv3.DeniedHttpResponse{Status: &typesv3.HttpStatus{Code: code}}) + } +} + +func (c *impl) denied_with_body(denied ref.Val, body ref.Val) ref.Val { + if denied, err := utils.ConvertToNative[*authv3.DeniedHttpResponse](denied); err != nil { + return types.WrapErr(err) + } else if body, err := utils.ConvertToNative[string](body); err != nil { + return types.WrapErr(err) + } else { + denied.Body = body + return c.NativeToValue(denied) + } +} + +func (c *impl) denied_with_header(denied ref.Val, header ref.Val) ref.Val { + if denied, err := utils.ConvertToNative[*authv3.DeniedHttpResponse](denied); err != nil { + return types.WrapErr(err) + } else if header, err := utils.ConvertToNative[*corev3.HeaderValueOption](header); err != nil { + return types.WrapErr(err) + } else { + denied.Headers = append(denied.Headers, header) + return c.NativeToValue(denied) + } +} + +func (c *impl) header_key_value(key ref.Val, value ref.Val) ref.Val { + if key, err := utils.ConvertToNative[string](key); err != nil { + return types.WrapErr(err) + } else if value, err := utils.ConvertToNative[string](value); err != nil { + return types.WrapErr(err) + } else { + return c.NativeToValue(&corev3.HeaderValueOption{Header: &corev3.HeaderValue{Key: key, Value: value}}) + } +} + +func (c *impl) header_keep_empty_value(header ref.Val) ref.Val { + return c.header_keep_empty_value_bool(header, types.True) +} + +func (c *impl) header_keep_empty_value_bool(header ref.Val, flag ref.Val) ref.Val { + if header, err := utils.ConvertToNative[*corev3.HeaderValueOption](header); err != nil { + return types.WrapErr(err) + } else if flag, err := utils.ConvertToNative[bool](flag); err != nil { + return types.WrapErr(err) + } else { + header.KeepEmptyValue = flag + return c.NativeToValue(header) + } +} + +func (c *impl) response_code(code ref.Val) ref.Val { + if code, err := utils.ConvertToNative[codes.Code](code); err != nil { + return types.WrapErr(err) + } else { + return c.NativeToValue(&authv3.CheckResponse{ + Status: &status.Status{Code: int32(code)}, + }) + } +} + +func (c *impl) response_ok(ok ref.Val) ref.Val { + if ok, err := utils.ConvertToNative[*authv3.OkHttpResponse](ok); err != nil { + return types.WrapErr(err) + } else { + return c.NativeToValue(&authv3.CheckResponse{ + Status: &status.Status{Code: int32(codes.OK)}, + HttpResponse: &authv3.CheckResponse_OkResponse{OkResponse: ok}, + }) + } +} + +func (c *impl) response_denied(denied ref.Val) ref.Val { + if denied, err := utils.ConvertToNative[*authv3.DeniedHttpResponse](denied); err != nil { + return types.WrapErr(err) + } else { + return c.NativeToValue(&authv3.CheckResponse{ + Status: &status.Status{Code: int32(codes.PermissionDenied)}, + HttpResponse: &authv3.CheckResponse_DeniedResponse{DeniedResponse: denied}, + }) + } +} + +func (c *impl) response_with_message(response ref.Val, message ref.Val) ref.Val { + if response, err := utils.ConvertToNative[*authv3.CheckResponse](response); err != nil { + return types.WrapErr(err) + } else if message, err := utils.ConvertToNative[string](message); err != nil { + return types.WrapErr(err) + } else { + response.Status.Message = message + return c.NativeToValue(response) + } +} + +func (c *impl) response_with_metadata(response ref.Val, metadata ref.Val) ref.Val { + if response, err := utils.ConvertToNative[*authv3.CheckResponse](response); err != nil { + return types.WrapErr(err) + } else if metadata, err := utils.ConvertToNative[*structpb.Struct](metadata); err != nil { + return types.WrapErr(err) + } else { + response.DynamicMetadata = metadata + return c.NativeToValue(response) + } +} diff --git a/pkg/authz/cel/libs/envoy/lib.go b/pkg/authz/cel/libs/envoy/lib.go new file mode 100644 index 0000000..277c8c9 --- /dev/null +++ b/pkg/authz/cel/libs/envoy/lib.go @@ -0,0 +1,108 @@ +package envoy + +import ( + authv3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" + "github.com/google/cel-go/cel" + "github.com/google/cel-go/common/types" + "github.com/google/cel-go/common/types/ref" +) + +// envoy types +var ( + CheckRequest = types.NewObjectType("envoy.service.auth.v3.CheckRequest") + CheckResponse = types.NewObjectType("envoy.service.auth.v3.CheckResponse") + OkHttpResponse = types.NewObjectType("envoy.service.auth.v3.OkHttpResponse") + DeniedHttpResponse = types.NewObjectType("envoy.service.auth.v3.DeniedHttpResponse") + Metadata = types.NewObjectType("google.protobuf.Struct") + HeaderValueOption = types.NewObjectType("envoy.config.core.v3.HeaderValueOption") + QueryParameter = types.NewObjectType("envoy.config.core.v3.QueryParameter") +) + +type lib struct{} + +func Lib() cel.EnvOption { + // create the cel lib env option + return cel.Lib(&lib{}) +} + +func (c *lib) CompileOptions() []cel.EnvOption { + return []cel.EnvOption{ + // register envoy protobuf messages + cel.Types((*authv3.CheckRequest)(nil), (*authv3.CheckResponse)(nil)), + // extend environment with function overloads + c.extendEnv, + } +} + +func (*lib) ProgramOptions() []cel.ProgramOption { + return []cel.ProgramOption{} +} + +func (*lib) extendEnv(env *cel.Env) (*cel.Env, error) { + // create implementation, recording the envoy types aware adapter + impl := impl{ + Adapter: env.CELTypeAdapter(), + } + // build our function overloads + libraryDecls := map[string][]cel.FunctionOpt{ + "envoy.Allowed": { + cel.Overload("allowed", []*cel.Type{}, OkHttpResponse, cel.FunctionBinding(func(values ...ref.Val) ref.Val { return impl.allowed() })), + }, + "envoy.Denied": { + cel.Overload("denied", []*cel.Type{types.IntType}, DeniedHttpResponse, cel.UnaryBinding(impl.denied)), + }, + "envoy.Response": { + cel.Overload("response_code", []*cel.Type{types.IntType}, CheckResponse, cel.UnaryBinding(impl.response_code)), + cel.Overload("response_ok", []*cel.Type{OkHttpResponse}, CheckResponse, cel.UnaryBinding(impl.response_ok)), + cel.Overload("response_denied", []*cel.Type{DeniedHttpResponse}, CheckResponse, cel.UnaryBinding(impl.response_denied)), + }, + "envoy.Null": { + cel.Overload("null", []*cel.Type{}, CheckResponse, cel.FunctionBinding(func(values ...ref.Val) ref.Val { + return impl.Adapter.NativeToValue((*authv3.CheckResponse)(nil)) + })), + }, + "envoy.Header": { + cel.Overload("header_key_value", []*cel.Type{types.StringType, types.StringType}, HeaderValueOption, cel.BinaryBinding(impl.header_key_value)), + }, + "WithBody": { + cel.MemberOverload("denied_with_body", []*cel.Type{DeniedHttpResponse, types.StringType}, DeniedHttpResponse, cel.BinaryBinding(impl.denied_with_body)), + }, + "WithHeader": { + cel.MemberOverload("ok_with_header", []*cel.Type{OkHttpResponse, HeaderValueOption}, OkHttpResponse, cel.BinaryBinding(impl.ok_with_header)), + cel.MemberOverload("denied_with_header", []*cel.Type{DeniedHttpResponse, HeaderValueOption}, DeniedHttpResponse, cel.BinaryBinding(impl.denied_with_header)), + }, + "WithoutHeader": { + cel.MemberOverload("ok_without_header", []*cel.Type{OkHttpResponse, types.StringType}, OkHttpResponse, cel.BinaryBinding(impl.ok_without_header)), + }, + "WithResponseHeader": { + cel.MemberOverload("ok_with_response_header", []*cel.Type{OkHttpResponse, HeaderValueOption}, OkHttpResponse, cel.BinaryBinding(impl.ok_with_response_header)), + }, + "WithQueryParam": { + cel.MemberOverload("ok_with_query_param", []*cel.Type{OkHttpResponse, QueryParameter}, OkHttpResponse, cel.BinaryBinding(impl.ok_with_query_param)), + }, + "WithoutQueryParam": { + cel.MemberOverload("ok_without_query_param", []*cel.Type{OkHttpResponse, types.StringType}, OkHttpResponse, cel.BinaryBinding(impl.ok_without_query_param)), + }, + "KeepEmptyValue": { + cel.MemberOverload("header_keep_empty_value", []*cel.Type{HeaderValueOption}, HeaderValueOption, cel.UnaryBinding(impl.header_keep_empty_value)), + cel.MemberOverload("header_keep_empty_value_bool", []*cel.Type{HeaderValueOption, types.BoolType}, HeaderValueOption, cel.BinaryBinding(impl.header_keep_empty_value_bool)), + }, + "Response": { + cel.MemberOverload("ok_response", []*cel.Type{OkHttpResponse}, CheckResponse, cel.UnaryBinding(impl.response_ok)), + cel.MemberOverload("denied_response", []*cel.Type{DeniedHttpResponse}, CheckResponse, cel.UnaryBinding(impl.response_denied)), + }, + "WithMessage": { + cel.MemberOverload("response_with_message", []*cel.Type{CheckResponse, types.StringType}, CheckResponse, cel.BinaryBinding(impl.response_with_message)), + }, + "WithMetadata": { + cel.MemberOverload("response_with_metadata", []*cel.Type{CheckResponse, Metadata}, CheckResponse, cel.BinaryBinding(impl.response_with_metadata)), + }, + } + // create env options corresponding to our function overloads + options := []cel.EnvOption{} + for name, overloads := range libraryDecls { + options = append(options, cel.Function(name, overloads...)) + } + // extend environment with our function overloads + return env.Extend(options...) +} diff --git a/pkg/authz/cel/libs/jwt/lib.go b/pkg/authz/cel/libs/jwt/lib.go new file mode 100644 index 0000000..da225dc --- /dev/null +++ b/pkg/authz/cel/libs/jwt/lib.go @@ -0,0 +1,64 @@ +package jwt + +import ( + "github.com/golang-jwt/jwt" + "github.com/google/cel-go/cel" + "github.com/google/cel-go/common/types" + "github.com/google/cel-go/common/types/ref" +) + +type lib struct{} + +func Lib() cel.EnvOption { + // create the cel lib env option + return cel.Lib(&lib{}) +} + +func (*lib) LibraryName() string { + return "kyverno.jwt" +} + +func (c *lib) CompileOptions() []cel.EnvOption { + return []cel.EnvOption{ + // extend environment with function overloads + c.extendEnv, + } +} + +func (*lib) ProgramOptions() []cel.ProgramOption { + return []cel.ProgramOption{} +} + +func (*lib) extendEnv(env *cel.Env) (*cel.Env, error) { + // build our function overloads + libraryDecls := map[string][]cel.FunctionOpt{ + "jwt.Decode": { + cel.Overload("decode_string_string", []*cel.Type{types.StringType, types.StringType}, TokenType, cel.BinaryBinding(decode)), + }, + } + // create env options corresponding to our function overloads + options := []cel.EnvOption{} + for name, overloads := range libraryDecls { + options = append(options, cel.Function(name, overloads...)) + } + // extend environment with our function overloads + return env.Extend(options...) +} + +func decode(token ref.Val, key ref.Val) ref.Val { + t, ok := token.(types.String) + if !ok { + return types.MaybeNoSuchOverloadErr(token) + } + k, ok := key.(types.String) + if !ok { + return types.MaybeNoSuchOverloadErr(key) + } + out, err := jwt.Parse(string(t), func(*jwt.Token) (any, error) { + return []byte(k), nil + }) + if err != nil { + return types.WrapErr(err) + } + return Token{Token: out} +} diff --git a/pkg/authz/cel/libs/jwt/lib_test.go b/pkg/authz/cel/libs/jwt/lib_test.go new file mode 100644 index 0000000..f995845 --- /dev/null +++ b/pkg/authz/cel/libs/jwt/lib_test.go @@ -0,0 +1,45 @@ +package jwt + +import ( + "testing" + + "github.com/golang-jwt/jwt" + "github.com/google/cel-go/common/types" + "github.com/google/cel-go/common/types/ref" + "github.com/stretchr/testify/assert" +) + +func Test_decode(t *testing.T) { + tests := []struct { + name string + token ref.Val + key ref.Val + want jwt.Token + }{{ + token: types.String("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIyNDEwODE1MzksIm5iZiI6MTUxNDg1MTEzOSwicm9sZSI6Imd1ZXN0Iiwic3ViIjoiWVd4cFkyVT0ifQ.ja1bgvIt47393ba_WbSBm35NrUhdxM4mOVQN8iXz8lk"), + key: types.String("secret"), + want: jwt.Token{ + Header: map[string]any{ + "alg": "HS256", + "typ": "JWT", + }, + Claims: jwt.MapClaims{ + "exp": float64(2241081539), + "nbf": float64(1514851139), + "role": "guest", + "sub": "YWxpY2U=", + }, + Valid: true, + }, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + out := decode(tt.token, tt.key) + got, ok := out.(Token) + assert.True(t, ok) + assert.Equal(t, tt.want.Header, got.Header) + assert.Equal(t, tt.want.Claims, got.Claims) + assert.Equal(t, tt.want.Valid, got.Valid) + }) + } +} diff --git a/pkg/authz/cel/libs/jwt/token.go b/pkg/authz/cel/libs/jwt/token.go new file mode 100644 index 0000000..e24b914 --- /dev/null +++ b/pkg/authz/cel/libs/jwt/token.go @@ -0,0 +1,51 @@ +package jwt + +import ( + "fmt" + "reflect" + + "github.com/golang-jwt/jwt" + "github.com/google/cel-go/common/types" + "github.com/google/cel-go/common/types/ref" +) + +var TokenType = types.NewObjectType("kyverno.jwt.Token") + +// Token provdes a CEL representation of a jwt.Token +type Token struct { + *jwt.Token +} + +func (d Token) ConvertToNative(typeDesc reflect.Type) (interface{}, error) { + if reflect.TypeOf(d.Token).AssignableTo(typeDesc) { + return d.Token, nil + } + return nil, fmt.Errorf("type conversion error from 'Token' to '%v'", typeDesc) +} + +func (d Token) ConvertToType(typeVal ref.Type) ref.Val { + switch typeVal { + case TokenType: + return d + case types.TypeType: + return TokenType + default: + return types.NewErr("type conversion error from '%s' to '%s'", TokenType, typeVal) + } +} + +func (d Token) Equal(other ref.Val) ref.Val { + otherToken, ok := other.(Token) + if !ok { + return types.MaybeNoSuchOverloadErr(other) + } + return types.Bool(d.Token.Raw == otherToken.Token.Raw) +} + +func (d Token) Type() ref.Type { + return TokenType +} + +func (d Token) Value() any { + return d.Token +} diff --git a/pkg/authz/cel/utils/convert.go b/pkg/authz/cel/utils/convert.go new file mode 100644 index 0000000..43c12fd --- /dev/null +++ b/pkg/authz/cel/utils/convert.go @@ -0,0 +1,19 @@ +package utils + +import ( + "reflect" + + "github.com/google/cel-go/common/types/ref" +) + +func ConvertToNative[T any](value ref.Val) (T, error) { + // try to convert value to native type + response, err := value.ConvertToNative(reflect.TypeFor[T]()) + // if it failed return default value for T and error + if err != nil { + var t T + return t, err + } + // return the converted value + return response.(T), nil +} diff --git a/pkg/authz/cel/variables.go b/pkg/authz/cel/variables.go new file mode 100644 index 0000000..d9faf0b --- /dev/null +++ b/pkg/authz/cel/variables.go @@ -0,0 +1,67 @@ +package cel + +import ( + "github.com/google/cel-go/common/types" + "github.com/google/cel-go/common/types/ref" +) + +var ( + VariablesType = types.NewObjectType("kyverno.variables") + variablesTypeType = types.NewTypeTypeWithParam(VariablesType) +) + +type variablesProvider struct { + inner types.Provider + fields map[string]*types.Type + names []string +} + +func NewVariablesProvider(inner types.Provider) *variablesProvider { + return &variablesProvider{ + inner: inner, + fields: make(map[string]*types.Type), + } +} + +func (p *variablesProvider) RegisterField(name string, t *types.Type) { + p.fields[name] = t + p.names = append(p.names, name) +} + +func (p *variablesProvider) EnumValue(enumName string) ref.Val { + return p.inner.EnumValue(enumName) +} + +func (p *variablesProvider) FindIdent(identName string) (ref.Val, bool) { + return p.inner.FindIdent(identName) +} + +func (p *variablesProvider) FindStructType(structType string) (*types.Type, bool) { + if structType == VariablesType.DeclaredTypeName() { + return variablesTypeType, true + } + return p.inner.FindStructType(structType) +} + +func (p *variablesProvider) FindStructFieldNames(structType string) ([]string, bool) { + if structType == VariablesType.DeclaredTypeName() { + return p.names, true + } + return p.inner.FindStructFieldNames(structType) +} + +func (p *variablesProvider) FindStructFieldType(structType, fieldName string) (*types.FieldType, bool) { + if structType == VariablesType.DeclaredTypeName() { + if t, ok := p.fields[fieldName]; ok { + return &types.FieldType{ + Type: t, + }, true + } + return nil, false + } + return p.inner.FindStructFieldType(structType, fieldName) +} + +func (p *variablesProvider) NewValue(structType string, fields map[string]ref.Val) ref.Val { + return p.inner.NewValue(structType, fields) +} diff --git a/pkg/authz/grpc.go b/pkg/authz/grpc.go index abbfb85..ec5c830 100644 --- a/pkg/authz/grpc.go +++ b/pkg/authz/grpc.go @@ -5,16 +5,27 @@ import ( "net" authv3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" + "github.com/kyverno/kyverno-envoy-plugin/pkg/policy" "github.com/kyverno/kyverno-envoy-plugin/pkg/server" "google.golang.org/grpc" + "k8s.io/client-go/rest" ) -func NewGrpcServer(network, addr string) server.ServerFunc { +func NewGrpcServer(network, addr string, config *rest.Config) server.ServerFunc { return func(ctx context.Context) error { // create a server s := grpc.NewServer() + // create compiler + compiler := policy.NewCompiler() + // create provider + provider, err := policy.NewKubeProvider(ctx, config, compiler) + if err != nil { + return err + } // setup our authorization service - svc := &service{} + svc := &service{ + provider: provider, + } // register our authorization service authv3.RegisterAuthorizationServer(s, svc) // create a listener @@ -22,6 +33,7 @@ func NewGrpcServer(network, addr string) server.ServerFunc { if err != nil { return err } + // run server return server.RunGrpc(ctx, s, l) } } diff --git a/pkg/authz/service.go b/pkg/authz/service.go index 3734d89..d9165a2 100644 --- a/pkg/authz/service.go +++ b/pkg/authz/service.go @@ -2,72 +2,38 @@ package authz import ( "context" - "encoding/json" "fmt" authv3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" - typev3 "github.com/envoyproxy/go-control-plane/envoy/type/v3" - "google.golang.org/genproto/googleapis/rpc/status" - "google.golang.org/grpc/codes" - "google.golang.org/protobuf/encoding/protojson" + "github.com/kyverno/kyverno-envoy-plugin/pkg/policy" ) -type service struct{} +type service struct { + provider policy.Provider +} func (s *service) Check(ctx context.Context, req *authv3.CheckRequest) (*authv3.CheckResponse, error) { - // parse request - resource, err := convert(req.GetAttributes()) + response, err := s.check(ctx, req) if err != nil { - return nil, err - } - fmt.Println(resource) - // evaluate policies - if r := allow(); r != nil { - return r, nil + fmt.Println(err) } - return deny("foo"), nil + return response, err } -// convert takes an AttributeContext and marshals it into an unstructured map -func convert(attrs *authv3.AttributeContext) (map[string]any, error) { - // create a new Marshaler - marshaler := protojson.MarshalOptions{ - Multiline: true, - Indent: " ", - AllowPartial: true, - } - // marshal the AttributeContext to json - jsonData, err := marshaler.Marshal(attrs) +func (s *service) check(ctx context.Context, req *authv3.CheckRequest) (*authv3.CheckResponse, error) { + // fetch policies + policies, err := s.provider.CompiledPolicies(ctx) if err != nil { - return nil, fmt.Errorf("maarshaling attributes to json failed: %w", err) - } - // unmarshal the json into an any - var resource map[string]any - if err := json.Unmarshal(jsonData, &resource); err != nil { return nil, err } - return resource, nil -} - -func allow() *authv3.CheckResponse { - return &authv3.CheckResponse{ - Status: &status.Status{Code: int32(codes.OK)}, - HttpResponse: &authv3.CheckResponse_OkResponse{}, - DynamicMetadata: nil, - } -} - -func deny(denialReason string) *authv3.CheckResponse { - return &authv3.CheckResponse{ - Status: &status.Status{ - Code: int32(codes.PermissionDenied), - }, - HttpResponse: &authv3.CheckResponse_DeniedResponse{ - DeniedResponse: &authv3.DeniedHttpResponse{ - Status: &typev3.HttpStatus{Code: typev3.StatusCode_Forbidden}, - Body: fmt.Sprintf("Request denied by Kyverno JSON engine. Reason: %s", denialReason), - }, - }, - DynamicMetadata: nil, + for _, policy := range policies { + result, err := policy(req) + if err != nil { + return nil, err + } + if result != nil { + return result, nil + } } + return nil, nil } diff --git a/pkg/authz/service_test.go b/pkg/authz/service_test.go deleted file mode 100644 index 2cf9ddc..0000000 --- a/pkg/authz/service_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package authz - -import ( - "testing" - - authv3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" - "github.com/stretchr/testify/assert" -) - -func Test_convert(t *testing.T) { - tests := []struct { - name string - attrs *authv3.AttributeContext - want map[string]any - wantErr bool - }{ - { - name: "empty AttributeContext", - attrs: &authv3.AttributeContext{ - Source: nil, - Destination: nil, - Request: nil, - ContextExtensions: nil, - }, - want: map[string]any{}, - wantErr: false, - }, - { - name: "non-empty AttributeContext", - attrs: &authv3.AttributeContext{ - Source: &authv3.AttributeContext_Peer{ - Principal: "test-principal", - }, - Destination: &authv3.AttributeContext_Peer{ - Principal: "test-destination", - }, - Request: &authv3.AttributeContext_Request{ - Time: nil, - Http: &authv3.AttributeContext_HttpRequest{ - Method: "GET", - Path: "/test", - }, - }, - ContextExtensions: map[string]string{ - "test-key": "test-value", - }, - }, - want: map[string]any{ - "source": map[string]any{ - "principal": "test-principal", - }, - "destination": map[string]any{ - "principal": "test-destination", - }, - "request": map[string]any{ - "http": map[string]any{ - "method": "GET", - "path": "/test", - }, - }, - "contextExtensions": map[string]any{ - "test-key": "test-value", - }, - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := convert(tt.attrs) - if tt.wantErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - assert.Equal(t, tt.want, got) - }) - } -} diff --git a/pkg/commands/serve/command.go b/pkg/commands/serve/command.go index 863caac..4ae1348 100644 --- a/pkg/commands/serve/command.go +++ b/pkg/commands/serve/command.go @@ -8,12 +8,14 @@ import ( "github.com/spf13/cobra" "go.uber.org/multierr" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/tools/clientcmd" ) func Command() *cobra.Command { var httpAddress string var grpcAddress string var grpcNetwork string + var kubeConfigOverrides clientcmd.ConfigOverrides command := &cobra.Command{ Use: "serve", Short: "Start the kyverno-envoy-plugin server", @@ -22,7 +24,16 @@ func Command() *cobra.Command { return signals.Do(context.Background(), func(ctx context.Context) error { // track errors var httpErr, grpcErr error - func(ctx context.Context) { + err := func(ctx context.Context) error { + // create a rest config + kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + clientcmd.NewDefaultClientConfigLoadingRules(), + &kubeConfigOverrides, + ) + config, err := kubeConfig.ClientConfig() + if err != nil { + return err + } // create a wait group var group wait.Group // wait all tasks in the group are over @@ -31,7 +42,7 @@ func Command() *cobra.Command { ctx, cancel := context.WithCancel(ctx) // create http and grpc servers http := authz.NewHttpServer(httpAddress) - grpc := authz.NewGrpcServer(grpcNetwork, grpcAddress) + grpc := authz.NewGrpcServer(grpcNetwork, grpcAddress, config) // run servers group.StartWithContext(ctx, func(ctx context.Context) { // cancel context at the end @@ -43,13 +54,15 @@ func Command() *cobra.Command { defer cancel() grpcErr = grpc.Run(ctx) }) + return nil }(ctx) - return multierr.Combine(httpErr, grpcErr) + return multierr.Combine(err, httpErr, grpcErr) }) }, } command.Flags().StringVar(&httpAddress, "http-address", ":9080", "Address to listen on for health checks") command.Flags().StringVar(&grpcAddress, "grpc-address", ":9081", "Address to listen on") command.Flags().StringVar(&grpcNetwork, "grpc-network", "tcp", "Network to listen on") + clientcmd.BindOverrideFlags(&kubeConfigOverrides, command.Flags(), clientcmd.RecommendedConfigOverrideFlags("kube-")) return command } diff --git a/pkg/policy/compiler.go b/pkg/policy/compiler.go new file mode 100644 index 0000000..451832f --- /dev/null +++ b/pkg/policy/compiler.go @@ -0,0 +1,117 @@ +package policy + +import ( + "errors" + + authv3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" + "github.com/google/cel-go/cel" + "github.com/google/cel-go/common/types" + "github.com/google/cel-go/common/types/ref" + "github.com/kyverno/kyverno-envoy-plugin/apis/v1alpha1" + engine "github.com/kyverno/kyverno-envoy-plugin/pkg/authz/cel" + "github.com/kyverno/kyverno-envoy-plugin/pkg/authz/cel/libs/envoy" + "github.com/kyverno/kyverno-envoy-plugin/pkg/authz/cel/utils" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + "k8s.io/apiserver/pkg/cel/lazy" +) + +const ( + VariablesKey = "variables" + ObjectKey = "object" +) + +type PolicyFunc func(*authv3.CheckRequest) (*authv3.CheckResponse, error) + +type Compiler interface { + Compile(v1alpha1.AuthorizationPolicy) (PolicyFunc, error) +} + +func NewCompiler() Compiler { + return &compiler{} +} + +type compiler struct{} + +func (c *compiler) Compile(policy v1alpha1.AuthorizationPolicy) (PolicyFunc, error) { + variables := map[string]cel.Program{} + var authorizations []cel.Program + base, err := engine.NewEnv() + if err != nil { + return nil, err + } + provider := engine.NewVariablesProvider(base.CELTypeProvider()) + env, err := base.Extend( + cel.Variable(ObjectKey, envoy.CheckRequest), + cel.Variable(VariablesKey, engine.VariablesType), + cel.CustomTypeProvider(provider), + ) + if err != nil { + return nil, err + } + for _, variable := range policy.Spec.Variables { + ast, issues := env.Compile(variable.Expression) + if err := issues.Err(); err != nil { + return nil, err + } + provider.RegisterField(variable.Name, ast.OutputType()) + prog, err := env.Program(ast) + if err != nil { + return nil, err + } + variables[variable.Name] = prog + } + for _, rule := range policy.Spec.Authorizations { + ast, issues := env.Compile(rule.Expression) + if err := issues.Err(); err != nil { + return nil, err + } + if !ast.OutputType().IsExactType(envoy.CheckResponse) { + return nil, errors.New("rule output is expected to be of type envoy.service.auth.v3.CheckResponse") + } + prog, err := env.Program(ast) + if err != nil { + return nil, err + } + authorizations = append(authorizations, prog) + } + eval := func(r *authv3.CheckRequest) (*authv3.CheckResponse, error) { + vars := lazy.NewMapValue(engine.VariablesType) + data := map[string]any{ + ObjectKey: r, + VariablesKey: vars, + } + for name, variable := range variables { + vars.Append(name, func(*lazy.MapValue) ref.Val { + out, _, err := variable.Eval(data) + if out != nil { + return out + } + if err != nil { + return types.WrapErr(err) + } + return nil + }) + } + for _, rule := range authorizations { + out, _, err := rule.Eval(data) + if err != nil { + return nil, err + } + response, err := utils.ConvertToNative[*authv3.CheckResponse](out) + if err != nil { + return nil, err + } + if response != nil { + return response, nil + } + } + return nil, nil + } + return func(r *authv3.CheckRequest) (*authv3.CheckResponse, error) { + response, err := eval(r) + if err != nil && policy.Spec.GetFailurePolicy() == admissionregistrationv1.Fail { + return nil, err + } + return response, nil + }, nil +} diff --git a/pkg/policy/provider.go b/pkg/policy/provider.go new file mode 100644 index 0000000..3aaa9e3 --- /dev/null +++ b/pkg/policy/provider.go @@ -0,0 +1,73 @@ +package policy + +import ( + "context" + + "github.com/kyverno/kyverno-envoy-plugin/apis/v1alpha1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type Provider interface { + CompiledPolicies(context.Context) ([]PolicyFunc, error) +} + +func NewKubeProvider(ctx context.Context, config *rest.Config, compiler Compiler) (Provider, error) { + scheme := runtime.NewScheme() + if err := v1alpha1.Install(scheme); err != nil { + return nil, err + } + // create kubernetes client + // TODO: do we want to use a cache ? + cache, err := cache.New(config, cache.Options{ + Scheme: scheme, + }) + if err != nil { + return nil, err + } + client, err := client.New(config, client.Options{ + Cache: &client.CacheOptions{ + Reader: cache, + }, + Scheme: scheme, + }) + if err != nil { + return nil, err + } + go func() { + if err := cache.Start(ctx); err != nil { + // TODO: better error handling + panic(err) + } + }() + // TODO: use the result of the wait + cache.WaitForCacheSync(ctx) + return &kubeProvider{ + client: client, + compiler: compiler, + }, nil +} + +type kubeProvider struct { + client client.Client + compiler Compiler +} + +func (p *kubeProvider) CompiledPolicies(ctx context.Context) ([]PolicyFunc, error) { + // fetch policies + var policies v1alpha1.AuthorizationPolicyList + if err := p.client.List(ctx, &policies, &client.ListOptions{}); err != nil { + return nil, err + } + var out []PolicyFunc + for _, policy := range policies.Items { + compiled, err := p.compiler.Compile(policy) + if err != nil { + return nil, err + } + out = append(out, compiled) + } + return out, nil +}