diff --git a/Makefile b/Makefile index 231288f8..5fbe5221 100644 --- a/Makefile +++ b/Makefile @@ -111,9 +111,12 @@ BOILERPLATE_FILE := hack/boilerplate/boilerplate.generatego.txt .PHONY: generate generate: manifests deepcopy register clientsets ## Generate manifests, deepcopy code, and clientsets. +# TODO: Remove the python post-processing patch once XAccessPolicy v1alpha1 is fully implemented +# and flipped to `served: true` instead of v0alpha0. .PHONY: manifests manifests: controller-gen ## Generate CustomResourceDefinition objects. $(CONTROLLER_GEN) rbac:roleName=manager-role crd paths="./api/..." output:crd:artifacts:config=k8s/crds + python3 -c "p='k8s/crds/agentic.networking.x-k8s.io_xaccesspolicies.yaml'; text=open(p).read(); parts=text.split(' - name: v1alpha1'); parts[1]=parts[1].replace(' served: true', ' served: false', 1); open(p,'w').write(' - name: v1alpha1'.join(parts))" .PHONY: deepcopy deepcopy: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. @@ -133,7 +136,7 @@ clientsets: ## Generate clientsets, listers, and informers. ./' .PHONY: register -register: ## Generate register code for CRDs under ./api/v0alpha0 +register: ## Generate register code for CRDs under ./api/v0alpha0 and ./api/v1alpha1 @echo "--- Ensuring code-generator is in module cache..." @go mod download k8s.io/code-generator @echo "+++ Generating register code for api/v0alpha0..." @@ -141,6 +144,12 @@ register: ## Generate register code for CRDs under ./api/v0alpha0 kube::codegen::gen_register \ --boilerplate $(BOILERPLATE_FILE) \ ./api/v0alpha0' + @echo "+++ Generating register code for api/v1alpha1..." + @bash -c 'source $(CODEGEN_SCRIPT); \ + kube::codegen::gen_register \ + --boilerplate $(BOILERPLATE_FILE) \ + ./api/v1alpha1' + ## @ Dependencies diff --git a/api/v0alpha0/accesspolicy_types.go b/api/v0alpha0/accesspolicy_types.go index 69b7936e..8b0740cc 100644 --- a/api/v0alpha0/accesspolicy_types.go +++ b/api/v0alpha0/accesspolicy_types.go @@ -205,6 +205,7 @@ type AccessPolicyStatus struct { // +genclient // +kubebuilder:object:root=true // +kubebuilder:subresource:status +// +kubebuilder:storageversion // XAccessPolicy is the Schema for the accesspolicies API. type XAccessPolicy struct { diff --git a/api/v1alpha1/accesspolicy_types.go b/api/v1alpha1/accesspolicy_types.go new file mode 100644 index 00000000..e0d0128a --- /dev/null +++ b/api/v1alpha1/accesspolicy_types.go @@ -0,0 +1,302 @@ +/* +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. +*/ + +// IMPORTANT: Run "make generate" to regenerate code after modifying this file + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" +) + +// AccessPolicySpec defines the desired state of AccessPolicy. +// +// Implementations SHOULD return a regular HTTP formatted response if the policy is enforced against non-MCP traffic. +// Implementations MAY return a JSON-RPC formatted response if the policy is enforced against MCP traffic. +// +kubebuilder:validation:XValidation:rule="self.action == 'ExternalAuth' ? has(self.externalAuth) : true",message="externalAuth must be specified when action is set to 'ExternalAuth'" +type AccessPolicySpec struct { + // TargetRefs specifies the targets of the AccessPolicy. + // An AccessPolicy must target at least one resource. + // There is one kind of TargetRef with "Core" support: + // + // * Gateway + // + // This API may be extended in the future to support additional kinds of targetRefs. + // Implementations may support additional kinds in an implementation specific manner. + // +required + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=10 + // +listType=atomic + // +kubebuilder:validation:XValidation:rule="self.all(ref, ref.kind == self[0].kind)",message="All targetRefs must have the same Kind" + TargetRefs []gwapiv1.LocalPolicyTargetReferenceWithSectionName `json:"targetRefs"` + + // Action specifies the action to be taken when rules match. + // Evaluation logic: + // 1. ExternalAuth runs before all other Allow policies. + // 2. If an ExternalAuth server denies the request, the request is denied. + // 3. If it allows the request, processing continues for all other allow policies for that target. + // 4. The request is allowed only if all allow policies allow it. + // +required + Action AccessPolicyActionType `json:"action"` + + // ExternalAuth specifies an external auth filter to be used for authorization. + // Core support is limited to 1 ExternalAuth callout per target. + // +optional + ExternalAuth *gwapiv1.HTTPExternalAuthFilter `json:"externalAuth,omitempty"` + + // Rules defines a list of rules to be applied to the target. + // An AccessPolicy must have at least one rule. + // +required + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=10 + // +listType=atomic + // +kubebuilder:validation:XValidation:rule="self.all(r, self.filter(x, x.name == r.name).size() == 1)",message="AccessRule names must be unique" + Rules []AccessRule `json:"rules"` +} + +// AccessPolicyActionType identifies a type of action for access policy. +// +kubebuilder:validation:Enum=Allow;ExternalAuth +type AccessPolicyActionType string + +const ( + // ActionTypeAllow is used to identify that the request should be allowed if rules match. + ActionTypeAllow AccessPolicyActionType = "Allow" + + // ActionTypeExternalAuth is used to identify that the request should be delegated to an external auth service if rules match. + ActionTypeExternalAuth AccessPolicyActionType = "ExternalAuth" +) + +// AccessRule specifies an authorization rule for a specified target. +type AccessRule struct { + // Name specifies the name of the rule. + // This follows the DNS Subdomain naming convention. + // See: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names + // +required + // +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$` + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=63 + Name string `json:"name"` + // Source specifies the source of the request. + // +required + Source AccessRuleSource `json:"source"` + // Authorization specifies the authorization rule to be applied to requests from the source. + // If omitted, all access from the specified source is allowed. + // +optional + Authorization *AuthorizationRule `json:"authorization,omitempty"` +} + +// AccessRuleSource specifies the source of a request. +// +// Type must be set to indicate the source type. +// Similarly, either SPIFFE or Serviceaccount can be set based on the type. +type AccessRuleSource struct { + // +unionDiscriminator + // +required + Type AuthorizationSourceType `json:"type"` + + // spiffe specifies an identity that is matched by this rule. + // + // spiffe identities must be specified as SPIFFE-formatted URIs following the pattern: + // spiffe:/// + // + // The exact workload identifier structure is implementation-specific. + // This will likely change in the future. + // + // SPIFFE identities for authorization can be derived in various ways by the underlying + // implementation. Common methods include: + // - From peer mTLS certificates: The identity is extracted from the client's + // mTLS certificate presented during connection establishment. + // - From IP-to-identity mappings: The implementation might maintain a dynamic + // mapping between source IP addresses (pod IPs) and their associated + // identities (e.g., Service Account, SPIFFE IDs). + // - From JWTs or other request-level authentication tokens. + // + // +optional + SPIFFE *AuthorizationSourceSPIFFE `json:"spiffe,omitempty"` + + // serviceAccount specifies a Kubernetes Service Account that is + // matched by this rule. A request originating from a pod associated with + // this Service Account will match the rule. + // + // The Service Account listed here is expected to exist within the same + // trust domain as the targeted workload. Cross-trust-domain access should + // instead be expressed using the `SPIFFE` field. + // +optional + ServiceAccount *AuthorizationSourceServiceAccount `json:"serviceAccount,omitempty"` +} + +// AuthorizationSourceType identifies a type of source for authorization. +// +kubebuilder:validation:Enum=ServiceAccount;SPIFFE +type AuthorizationSourceType string + +const ( + // AuthorizationSourceTypeSPIFFE is used to identify a request matches a SPIFFE Identity. + AuthorizationSourceTypeSPIFFE AuthorizationSourceType = "SPIFFE" + + // AuthorizationSourceTypeServiceAccount is used to identify a request matches a ServiceAccount from within the cluster. + AuthorizationSourceTypeServiceAccount AuthorizationSourceType = "ServiceAccount" +) + +// +kubebuilder:validation:Pattern=`^spiffe://[a-z0-9._-]+(?:/[A-Za-z0-9._-]+)*$` +type AuthorizationSourceSPIFFE string + +type AuthorizationSourceServiceAccount struct { + // Namespace is the namespace of the ServiceAccount + // If not specified, current namespace (the namespace of the policy) is used. + // +optional + Namespace string `json:"namespace,omitempty"` + + // Name is the name of the ServiceAccount. + // +required + Name string `json:"name"` +} + +// AuthorizationRule defines the specific authorization criteria that requests must meet. +type AuthorizationRule struct { + // +unionDiscriminator + // +required + Type AuthorizationRuleType `json:"type"` + + // MCP defines MCP-specific matching criteria. + // If omitted, the policy does not check MCP-level parameters, allowing all MCP traffic that + // successfully passes through the matched HTTP routing envelope. + // +optional + MCP MCPAttributes `json:"mcp,omitempty"` +} + +// MCPAttributes defines the protocol-specific attributes for MCP authorization. +type MCPAttributes struct { + // Methods is a list of specific MCP functional methods to match. + // If specified, only MCP requests with a method + // that matches one of these items will be authorized. + // If empty or omitted, no method-level allowlisting is applied, meaning all + // MCP methods (e.g., all tools, prompts, and resources) are permitted. + // +kubebuilder:validation:MaxItems=10 + // +optional + // +listType=map + // +listMapKey=name + Methods []MCPMethod `json:"methods,omitempty"` +} + +// MCPMethod defines a specific MCP method and its associated parameters. +// +kubebuilder:validation:XValidation:rule="has(self.params) && self.params.size() > 0 ? self.name in ['prompts/get', 'tools/call', 'resources/subscribe', 'resources/unsubscribe', 'resources/read'] : true",message="Params can only be specified for get, call, subscribe, unsubscribe, or read methods" +type MCPMethod struct { + // Name is the MCP method to match against (e.g., 'tools/call'). + // Allowed values: + // 1. 'tools', 'prompts', 'resources': Matches all sub-methods under these categories. + // 2. 'prompts/list', 'tools/list', 'resources/list', 'resources/templates/list'. + // 3. 'prompts/get', 'tools/call', 'resources/subscribe', 'resources/unsubscribe', 'resources/read'. + // Parameters cannot be specified for categories 1 and 2. + // +required + Name MCPMethodName `json:"name"` + + // Params allows matching against specific arguments in the MCP request. + // Only valid for 'get', 'call', 'subscribe', 'unsubscribe', and 'read' methods. + // If empty or omitted, parameter-level allowlisting is not applied, meaning the method + // is authorized regardless of the arguments passed in the request. + // +optional + // +listType=set + // +kubebuilder:validation:MaxItems=10 + Params []MCPMethodParam `json:"params,omitempty"` +} + +// +kubebuilder:validation:MaxLength=20 +type MCPMethodParam string + +// MCPMethodName defines the allowed MCP methods for matching. +// +kubebuilder:validation:Enum=tools;prompts;resources;prompts/list;tools/list;resources/list;resources/templates/list;prompts/get;tools/call;resources/subscribe;resources/unsubscribe;resources/read +type MCPMethodName string + +// AuthorizationRuleType identifies a type of authorization rule. +// +kubebuilder:validation:Enum=Inline +type AuthorizationRuleType string + +const ( + // AuthorizationRuleTypeInline is used to identify authorization rules + // declared as attributes inside the policy (inline) + AuthorizationRuleTypeInline AuthorizationRuleType = "Inline" +) + +const ( + // PolicyConditionAccepted indicates whether the policy has been accepted by the controller. + // + // Possible reasons for this condition to be True are: + // + // * "Accepted" + // + // Possible reasons for this condition to be False are: + // + // * "LimitPerTargetExceeded" + // + PolicyConditionAccepted gwapiv1.PolicyConditionType = "Accepted" + + // This reason is used with the "Accepted" condition when the policy + // has been accepted by the controller. + PolicyReasonAccepted gwapiv1.PolicyConditionReason = "Accepted" + + // This reason is used with the "Accepted" condition when the policy + // was rejected because the maximum number of policies per target was exceeded. + PolicyLimitPerTargetExceeded gwapiv1.PolicyConditionReason = "LimitPerTargetExceeded" +) + +// AccessPolicyStatus defines the observed state of AccessPolicy. +type AccessPolicyStatus struct { + // For Policy Status API conventions, see: + // https://gateway-api.sigs.k8s.io/geps/gep-713/#the-status-stanza-of-policy-objects + // + // Ancestors is a list of ancestor resources (usually Gateway or Mesh) of + // the policy target which are enforcement points for the + // policy, and the status of the policy with respect to each ancestor. + + // +required + // +listType=atomic + // +kubebuilder:validation:MaxItems=16 + Ancestors []gwapiv1.PolicyAncestorStatus `json:"ancestors"` +} + +// +genclient +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status + +// XAccessPolicy is the Schema for the accesspolicies API. +type XAccessPolicy struct { + metav1.TypeMeta `json:",inline"` + + // metadata is a standard object metadata. + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + + // spec defines the desired state of AccessPolicy. + // +required + Spec AccessPolicySpec `json:"spec"` + + // status defines the observed state of AccessPolicy. + // +optional + Status AccessPolicyStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// XAccessPolicyList contains a list of AccessPolicy. +type XAccessPolicyList struct { + metav1.TypeMeta `json:",inline"` + // metadata is a standard list metadata. + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + Items []XAccessPolicy `json:"items"` +} diff --git a/api/v1alpha1/doc.go b/api/v1alpha1/doc.go new file mode 100644 index 00000000..f8bd07d9 --- /dev/null +++ b/api/v1alpha1/doc.go @@ -0,0 +1,19 @@ +/* +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. +*/ + +// +k8s:deepcopy-gen=package +// +groupName=agentic.networking.x-k8s.io +package v1alpha1 diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000..34a085a1 --- /dev/null +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,260 @@ +//go:build !ignore_autogenerated + +/* +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 controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/gateway-api/apis/v1" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AccessPolicySpec) DeepCopyInto(out *AccessPolicySpec) { + *out = *in + if in.TargetRefs != nil { + in, out := &in.TargetRefs, &out.TargetRefs + *out = make([]v1.LocalPolicyTargetReferenceWithSectionName, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ExternalAuth != nil { + in, out := &in.ExternalAuth, &out.ExternalAuth + *out = new(v1.HTTPExternalAuthFilter) + (*in).DeepCopyInto(*out) + } + if in.Rules != nil { + in, out := &in.Rules, &out.Rules + *out = make([]AccessRule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessPolicySpec. +func (in *AccessPolicySpec) DeepCopy() *AccessPolicySpec { + if in == nil { + return nil + } + out := new(AccessPolicySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AccessPolicyStatus) DeepCopyInto(out *AccessPolicyStatus) { + *out = *in + if in.Ancestors != nil { + in, out := &in.Ancestors, &out.Ancestors + *out = make([]v1.PolicyAncestorStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessPolicyStatus. +func (in *AccessPolicyStatus) DeepCopy() *AccessPolicyStatus { + if in == nil { + return nil + } + out := new(AccessPolicyStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AccessRule) DeepCopyInto(out *AccessRule) { + *out = *in + in.Source.DeepCopyInto(&out.Source) + if in.Authorization != nil { + in, out := &in.Authorization, &out.Authorization + *out = new(AuthorizationRule) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessRule. +func (in *AccessRule) DeepCopy() *AccessRule { + if in == nil { + return nil + } + out := new(AccessRule) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AccessRuleSource) DeepCopyInto(out *AccessRuleSource) { + *out = *in + if in.SPIFFE != nil { + in, out := &in.SPIFFE, &out.SPIFFE + *out = new(AuthorizationSourceSPIFFE) + **out = **in + } + if in.ServiceAccount != nil { + in, out := &in.ServiceAccount, &out.ServiceAccount + *out = new(AuthorizationSourceServiceAccount) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessRuleSource. +func (in *AccessRuleSource) DeepCopy() *AccessRuleSource { + if in == nil { + return nil + } + out := new(AccessRuleSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuthorizationRule) DeepCopyInto(out *AuthorizationRule) { + *out = *in + in.MCP.DeepCopyInto(&out.MCP) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizationRule. +func (in *AuthorizationRule) DeepCopy() *AuthorizationRule { + if in == nil { + return nil + } + out := new(AuthorizationRule) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuthorizationSourceServiceAccount) DeepCopyInto(out *AuthorizationSourceServiceAccount) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizationSourceServiceAccount. +func (in *AuthorizationSourceServiceAccount) DeepCopy() *AuthorizationSourceServiceAccount { + if in == nil { + return nil + } + out := new(AuthorizationSourceServiceAccount) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MCPAttributes) DeepCopyInto(out *MCPAttributes) { + *out = *in + if in.Methods != nil { + in, out := &in.Methods, &out.Methods + *out = make([]MCPMethod, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MCPAttributes. +func (in *MCPAttributes) DeepCopy() *MCPAttributes { + if in == nil { + return nil + } + out := new(MCPAttributes) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MCPMethod) DeepCopyInto(out *MCPMethod) { + *out = *in + if in.Params != nil { + in, out := &in.Params, &out.Params + *out = make([]MCPMethodParam, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MCPMethod. +func (in *MCPMethod) DeepCopy() *MCPMethod { + if in == nil { + return nil + } + out := new(MCPMethod) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *XAccessPolicy) DeepCopyInto(out *XAccessPolicy) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new XAccessPolicy. +func (in *XAccessPolicy) DeepCopy() *XAccessPolicy { + if in == nil { + return nil + } + out := new(XAccessPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *XAccessPolicy) 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 *XAccessPolicyList) DeepCopyInto(out *XAccessPolicyList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]XAccessPolicy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new XAccessPolicyList. +func (in *XAccessPolicyList) DeepCopy() *XAccessPolicyList { + if in == nil { + return nil + } + out := new(XAccessPolicyList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *XAccessPolicyList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/api/v1alpha1/zz_generated.register.go b/api/v1alpha1/zz_generated.register.go new file mode 100644 index 00000000..e8f38004 --- /dev/null +++ b/api/v1alpha1/zz_generated.register.go @@ -0,0 +1,71 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +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" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName specifies the group name used to register the objects. +const GroupName = "agentic.networking.x-k8s.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 + // Deprecated: 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, + &XAccessPolicy{}, + &XAccessPolicyList{}, + ) + // AddToGroupVersion allows the serialization of client types like ListOptions. + v1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/k8s/client/clientset/versioned/clientset.go b/k8s/client/clientset/versioned/clientset.go index 47d8ece8..76a192ae 100644 --- a/k8s/client/clientset/versioned/clientset.go +++ b/k8s/client/clientset/versioned/clientset.go @@ -26,17 +26,20 @@ import ( rest "k8s.io/client-go/rest" flowcontrol "k8s.io/client-go/util/flowcontrol" agenticv0alpha0 "sigs.k8s.io/kube-agentic-networking/k8s/client/clientset/versioned/typed/api/v0alpha0" + agenticv1alpha1 "sigs.k8s.io/kube-agentic-networking/k8s/client/clientset/versioned/typed/api/v1alpha1" ) type Interface interface { Discovery() discovery.DiscoveryInterface AgenticV0alpha0() agenticv0alpha0.AgenticV0alpha0Interface + AgenticV1alpha1() agenticv1alpha1.AgenticV1alpha1Interface } // Clientset contains the clients for groups. type Clientset struct { *discovery.DiscoveryClient agenticV0alpha0 *agenticv0alpha0.AgenticV0alpha0Client + agenticV1alpha1 *agenticv1alpha1.AgenticV1alpha1Client } // AgenticV0alpha0 retrieves the AgenticV0alpha0Client @@ -44,6 +47,11 @@ func (c *Clientset) AgenticV0alpha0() agenticv0alpha0.AgenticV0alpha0Interface { return c.agenticV0alpha0 } +// AgenticV1alpha1 retrieves the AgenticV1alpha1Client +func (c *Clientset) AgenticV1alpha1() agenticv1alpha1.AgenticV1alpha1Interface { + return c.agenticV1alpha1 +} + // Discovery retrieves the DiscoveryClient func (c *Clientset) Discovery() discovery.DiscoveryInterface { if c == nil { @@ -92,6 +100,10 @@ func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, if err != nil { return nil, err } + cs.agenticV1alpha1, err = agenticv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient) + if err != nil { + return nil, err + } cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient) if err != nil { @@ -114,6 +126,7 @@ func NewForConfigOrDie(c *rest.Config) *Clientset { func New(c rest.Interface) *Clientset { var cs Clientset cs.agenticV0alpha0 = agenticv0alpha0.New(c) + cs.agenticV1alpha1 = agenticv1alpha1.New(c) cs.DiscoveryClient = discovery.NewDiscoveryClient(c) return &cs diff --git a/k8s/client/clientset/versioned/fake/clientset_generated.go b/k8s/client/clientset/versioned/fake/clientset_generated.go index 049c3b86..9efb1c73 100644 --- a/k8s/client/clientset/versioned/fake/clientset_generated.go +++ b/k8s/client/clientset/versioned/fake/clientset_generated.go @@ -28,6 +28,8 @@ import ( clientset "sigs.k8s.io/kube-agentic-networking/k8s/client/clientset/versioned" agenticv0alpha0 "sigs.k8s.io/kube-agentic-networking/k8s/client/clientset/versioned/typed/api/v0alpha0" fakeagenticv0alpha0 "sigs.k8s.io/kube-agentic-networking/k8s/client/clientset/versioned/typed/api/v0alpha0/fake" + agenticv1alpha1 "sigs.k8s.io/kube-agentic-networking/k8s/client/clientset/versioned/typed/api/v1alpha1" + fakeagenticv1alpha1 "sigs.k8s.io/kube-agentic-networking/k8s/client/clientset/versioned/typed/api/v1alpha1/fake" ) // NewSimpleClientset returns a clientset that will respond with the provided objects. @@ -103,3 +105,8 @@ var ( func (c *Clientset) AgenticV0alpha0() agenticv0alpha0.AgenticV0alpha0Interface { return &fakeagenticv0alpha0.FakeAgenticV0alpha0{Fake: &c.Fake} } + +// AgenticV1alpha1 retrieves the AgenticV1alpha1Client +func (c *Clientset) AgenticV1alpha1() agenticv1alpha1.AgenticV1alpha1Interface { + return &fakeagenticv1alpha1.FakeAgenticV1alpha1{Fake: &c.Fake} +} diff --git a/k8s/client/clientset/versioned/fake/register.go b/k8s/client/clientset/versioned/fake/register.go index 3124fbca..b92b67c0 100644 --- a/k8s/client/clientset/versioned/fake/register.go +++ b/k8s/client/clientset/versioned/fake/register.go @@ -25,6 +25,7 @@ import ( serializer "k8s.io/apimachinery/pkg/runtime/serializer" utilruntime "k8s.io/apimachinery/pkg/util/runtime" agenticv0alpha0 "sigs.k8s.io/kube-agentic-networking/api/v0alpha0" + agenticv1alpha1 "sigs.k8s.io/kube-agentic-networking/api/v1alpha1" ) var scheme = runtime.NewScheme() @@ -32,6 +33,7 @@ var codecs = serializer.NewCodecFactory(scheme) var localSchemeBuilder = runtime.SchemeBuilder{ agenticv0alpha0.AddToScheme, + agenticv1alpha1.AddToScheme, } // AddToScheme adds all types of this clientset into the given scheme. This allows composition diff --git a/k8s/client/clientset/versioned/scheme/register.go b/k8s/client/clientset/versioned/scheme/register.go index fba23e1e..a1a52656 100644 --- a/k8s/client/clientset/versioned/scheme/register.go +++ b/k8s/client/clientset/versioned/scheme/register.go @@ -25,6 +25,7 @@ import ( serializer "k8s.io/apimachinery/pkg/runtime/serializer" utilruntime "k8s.io/apimachinery/pkg/util/runtime" agenticv0alpha0 "sigs.k8s.io/kube-agentic-networking/api/v0alpha0" + agenticv1alpha1 "sigs.k8s.io/kube-agentic-networking/api/v1alpha1" ) var Scheme = runtime.NewScheme() @@ -32,6 +33,7 @@ var Codecs = serializer.NewCodecFactory(Scheme) var ParameterCodec = runtime.NewParameterCodec(Scheme) var localSchemeBuilder = runtime.SchemeBuilder{ agenticv0alpha0.AddToScheme, + agenticv1alpha1.AddToScheme, } // AddToScheme adds all types of this clientset into the given scheme. This allows composition diff --git a/k8s/client/clientset/versioned/typed/api/v1alpha1/api_client.go b/k8s/client/clientset/versioned/typed/api/v1alpha1/api_client.go new file mode 100644 index 00000000..6da2fddf --- /dev/null +++ b/k8s/client/clientset/versioned/typed/api/v1alpha1/api_client.go @@ -0,0 +1,101 @@ +/* +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 client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + http "net/http" + + rest "k8s.io/client-go/rest" + apiv1alpha1 "sigs.k8s.io/kube-agentic-networking/api/v1alpha1" + scheme "sigs.k8s.io/kube-agentic-networking/k8s/client/clientset/versioned/scheme" +) + +type AgenticV1alpha1Interface interface { + RESTClient() rest.Interface + XAccessPoliciesGetter +} + +// AgenticV1alpha1Client is used to interact with features provided by the agentic.networking.x-k8s.io group. +type AgenticV1alpha1Client struct { + restClient rest.Interface +} + +func (c *AgenticV1alpha1Client) XAccessPolicies(namespace string) XAccessPolicyInterface { + return newXAccessPolicies(c, namespace) +} + +// NewForConfig creates a new AgenticV1alpha1Client for the given config. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). +func NewForConfig(c *rest.Config) (*AgenticV1alpha1Client, error) { + config := *c + setConfigDefaults(&config) + httpClient, err := rest.HTTPClientFor(&config) + if err != nil { + return nil, err + } + return NewForConfigAndClient(&config, httpClient) +} + +// NewForConfigAndClient creates a new AgenticV1alpha1Client for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +func NewForConfigAndClient(c *rest.Config, h *http.Client) (*AgenticV1alpha1Client, error) { + config := *c + setConfigDefaults(&config) + client, err := rest.RESTClientForConfigAndClient(&config, h) + if err != nil { + return nil, err + } + return &AgenticV1alpha1Client{client}, nil +} + +// NewForConfigOrDie creates a new AgenticV1alpha1Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *AgenticV1alpha1Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new AgenticV1alpha1Client for the given RESTClient. +func New(c rest.Interface) *AgenticV1alpha1Client { + return &AgenticV1alpha1Client{c} +} + +func setConfigDefaults(config *rest.Config) { + gv := apiv1alpha1.SchemeGroupVersion + config.GroupVersion = &gv + config.APIPath = "/apis" + config.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion() + + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *AgenticV1alpha1Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/k8s/client/clientset/versioned/typed/api/v1alpha1/doc.go b/k8s/client/clientset/versioned/typed/api/v1alpha1/doc.go new file mode 100644 index 00000000..df51baa4 --- /dev/null +++ b/k8s/client/clientset/versioned/typed/api/v1alpha1/doc.go @@ -0,0 +1,20 @@ +/* +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 client-gen. DO NOT EDIT. + +// This package has the automatically generated typed clients. +package v1alpha1 diff --git a/k8s/client/clientset/versioned/typed/api/v1alpha1/fake/doc.go b/k8s/client/clientset/versioned/typed/api/v1alpha1/fake/doc.go new file mode 100644 index 00000000..16f44399 --- /dev/null +++ b/k8s/client/clientset/versioned/typed/api/v1alpha1/fake/doc.go @@ -0,0 +1,20 @@ +/* +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 client-gen. DO NOT EDIT. + +// Package fake has the automatically generated clients. +package fake diff --git a/k8s/client/clientset/versioned/typed/api/v1alpha1/fake/fake_api_client.go b/k8s/client/clientset/versioned/typed/api/v1alpha1/fake/fake_api_client.go new file mode 100644 index 00000000..e476e7c5 --- /dev/null +++ b/k8s/client/clientset/versioned/typed/api/v1alpha1/fake/fake_api_client.go @@ -0,0 +1,40 @@ +/* +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 client-gen. DO NOT EDIT. + +package fake + +import ( + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" + v1alpha1 "sigs.k8s.io/kube-agentic-networking/k8s/client/clientset/versioned/typed/api/v1alpha1" +) + +type FakeAgenticV1alpha1 struct { + *testing.Fake +} + +func (c *FakeAgenticV1alpha1) XAccessPolicies(namespace string) v1alpha1.XAccessPolicyInterface { + return newFakeXAccessPolicies(c, namespace) +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeAgenticV1alpha1) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/k8s/client/clientset/versioned/typed/api/v1alpha1/fake/fake_xaccesspolicy.go b/k8s/client/clientset/versioned/typed/api/v1alpha1/fake/fake_xaccesspolicy.go new file mode 100644 index 00000000..f08b9c52 --- /dev/null +++ b/k8s/client/clientset/versioned/typed/api/v1alpha1/fake/fake_xaccesspolicy.go @@ -0,0 +1,52 @@ +/* +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 client-gen. DO NOT EDIT. + +package fake + +import ( + gentype "k8s.io/client-go/gentype" + v1alpha1 "sigs.k8s.io/kube-agentic-networking/api/v1alpha1" + apiv1alpha1 "sigs.k8s.io/kube-agentic-networking/k8s/client/clientset/versioned/typed/api/v1alpha1" +) + +// fakeXAccessPolicies implements XAccessPolicyInterface +type fakeXAccessPolicies struct { + *gentype.FakeClientWithList[*v1alpha1.XAccessPolicy, *v1alpha1.XAccessPolicyList] + Fake *FakeAgenticV1alpha1 +} + +func newFakeXAccessPolicies(fake *FakeAgenticV1alpha1, namespace string) apiv1alpha1.XAccessPolicyInterface { + return &fakeXAccessPolicies{ + gentype.NewFakeClientWithList[*v1alpha1.XAccessPolicy, *v1alpha1.XAccessPolicyList]( + fake.Fake, + namespace, + v1alpha1.SchemeGroupVersion.WithResource("xaccesspolicies"), + v1alpha1.SchemeGroupVersion.WithKind("XAccessPolicy"), + func() *v1alpha1.XAccessPolicy { return &v1alpha1.XAccessPolicy{} }, + func() *v1alpha1.XAccessPolicyList { return &v1alpha1.XAccessPolicyList{} }, + func(dst, src *v1alpha1.XAccessPolicyList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.XAccessPolicyList) []*v1alpha1.XAccessPolicy { + return gentype.ToPointerSlice(list.Items) + }, + func(list *v1alpha1.XAccessPolicyList, items []*v1alpha1.XAccessPolicy) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, + } +} diff --git a/k8s/client/clientset/versioned/typed/api/v1alpha1/generated_expansion.go b/k8s/client/clientset/versioned/typed/api/v1alpha1/generated_expansion.go new file mode 100644 index 00000000..8493701b --- /dev/null +++ b/k8s/client/clientset/versioned/typed/api/v1alpha1/generated_expansion.go @@ -0,0 +1,21 @@ +/* +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 client-gen. DO NOT EDIT. + +package v1alpha1 + +type XAccessPolicyExpansion interface{} diff --git a/k8s/client/clientset/versioned/typed/api/v1alpha1/xaccesspolicy.go b/k8s/client/clientset/versioned/typed/api/v1alpha1/xaccesspolicy.go new file mode 100644 index 00000000..c063664d --- /dev/null +++ b/k8s/client/clientset/versioned/typed/api/v1alpha1/xaccesspolicy.go @@ -0,0 +1,70 @@ +/* +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 client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + gentype "k8s.io/client-go/gentype" + apiv1alpha1 "sigs.k8s.io/kube-agentic-networking/api/v1alpha1" + scheme "sigs.k8s.io/kube-agentic-networking/k8s/client/clientset/versioned/scheme" +) + +// XAccessPoliciesGetter has a method to return a XAccessPolicyInterface. +// A group's client should implement this interface. +type XAccessPoliciesGetter interface { + XAccessPolicies(namespace string) XAccessPolicyInterface +} + +// XAccessPolicyInterface has methods to work with XAccessPolicy resources. +type XAccessPolicyInterface interface { + Create(ctx context.Context, xAccessPolicy *apiv1alpha1.XAccessPolicy, opts v1.CreateOptions) (*apiv1alpha1.XAccessPolicy, error) + Update(ctx context.Context, xAccessPolicy *apiv1alpha1.XAccessPolicy, opts v1.UpdateOptions) (*apiv1alpha1.XAccessPolicy, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, xAccessPolicy *apiv1alpha1.XAccessPolicy, opts v1.UpdateOptions) (*apiv1alpha1.XAccessPolicy, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*apiv1alpha1.XAccessPolicy, error) + List(ctx context.Context, opts v1.ListOptions) (*apiv1alpha1.XAccessPolicyList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *apiv1alpha1.XAccessPolicy, err error) + XAccessPolicyExpansion +} + +// xAccessPolicies implements XAccessPolicyInterface +type xAccessPolicies struct { + *gentype.ClientWithList[*apiv1alpha1.XAccessPolicy, *apiv1alpha1.XAccessPolicyList] +} + +// newXAccessPolicies returns a XAccessPolicies +func newXAccessPolicies(c *AgenticV1alpha1Client, namespace string) *xAccessPolicies { + return &xAccessPolicies{ + gentype.NewClientWithList[*apiv1alpha1.XAccessPolicy, *apiv1alpha1.XAccessPolicyList]( + "xaccesspolicies", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *apiv1alpha1.XAccessPolicy { return &apiv1alpha1.XAccessPolicy{} }, + func() *apiv1alpha1.XAccessPolicyList { return &apiv1alpha1.XAccessPolicyList{} }, + ), + } +} diff --git a/k8s/client/informers/externalversions/api/interface.go b/k8s/client/informers/externalversions/api/interface.go index 586c2295..f9e55f2e 100644 --- a/k8s/client/informers/externalversions/api/interface.go +++ b/k8s/client/informers/externalversions/api/interface.go @@ -20,6 +20,7 @@ package api import ( v0alpha0 "sigs.k8s.io/kube-agentic-networking/k8s/client/informers/externalversions/api/v0alpha0" + v1alpha1 "sigs.k8s.io/kube-agentic-networking/k8s/client/informers/externalversions/api/v1alpha1" internalinterfaces "sigs.k8s.io/kube-agentic-networking/k8s/client/informers/externalversions/internalinterfaces" ) @@ -27,6 +28,8 @@ import ( type Interface interface { // V0alpha0 provides access to shared informers for resources in V0alpha0. V0alpha0() v0alpha0.Interface + // V1alpha1 provides access to shared informers for resources in V1alpha1. + V1alpha1() v1alpha1.Interface } type group struct { @@ -44,3 +47,8 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList func (g *group) V0alpha0() v0alpha0.Interface { return v0alpha0.New(g.factory, g.namespace, g.tweakListOptions) } + +// V1alpha1 returns a new v1alpha1.Interface. +func (g *group) V1alpha1() v1alpha1.Interface { + return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions) +} diff --git a/k8s/client/informers/externalversions/api/v1alpha1/interface.go b/k8s/client/informers/externalversions/api/v1alpha1/interface.go new file mode 100644 index 00000000..5471f7f2 --- /dev/null +++ b/k8s/client/informers/externalversions/api/v1alpha1/interface.go @@ -0,0 +1,45 @@ +/* +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 informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + internalinterfaces "sigs.k8s.io/kube-agentic-networking/k8s/client/informers/externalversions/internalinterfaces" +) + +// Interface provides access to all the informers in this group version. +type Interface interface { + // XAccessPolicies returns a XAccessPolicyInformer. + XAccessPolicies() XAccessPolicyInformer +} + +type version struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// XAccessPolicies returns a XAccessPolicyInformer. +func (v *version) XAccessPolicies() XAccessPolicyInformer { + return &xAccessPolicyInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/k8s/client/informers/externalversions/api/v1alpha1/xaccesspolicy.go b/k8s/client/informers/externalversions/api/v1alpha1/xaccesspolicy.go new file mode 100644 index 00000000..6e9baebb --- /dev/null +++ b/k8s/client/informers/externalversions/api/v1alpha1/xaccesspolicy.go @@ -0,0 +1,102 @@ +/* +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 informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + time "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" + kubeagenticnetworkingapiv1alpha1 "sigs.k8s.io/kube-agentic-networking/api/v1alpha1" + versioned "sigs.k8s.io/kube-agentic-networking/k8s/client/clientset/versioned" + internalinterfaces "sigs.k8s.io/kube-agentic-networking/k8s/client/informers/externalversions/internalinterfaces" + apiv1alpha1 "sigs.k8s.io/kube-agentic-networking/k8s/client/listers/api/v1alpha1" +) + +// XAccessPolicyInformer provides access to a shared informer and lister for +// XAccessPolicies. +type XAccessPolicyInformer interface { + Informer() cache.SharedIndexInformer + Lister() apiv1alpha1.XAccessPolicyLister +} + +type xAccessPolicyInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewXAccessPolicyInformer constructs a new informer for XAccessPolicy type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewXAccessPolicyInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredXAccessPolicyInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredXAccessPolicyInformer constructs a new informer for XAccessPolicy type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredXAccessPolicyInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.AgenticV1alpha1().XAccessPolicies(namespace).List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.AgenticV1alpha1().XAccessPolicies(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.AgenticV1alpha1().XAccessPolicies(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.AgenticV1alpha1().XAccessPolicies(namespace).Watch(ctx, options) + }, + }, client), + &kubeagenticnetworkingapiv1alpha1.XAccessPolicy{}, + resyncPeriod, + indexers, + ) +} + +func (f *xAccessPolicyInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredXAccessPolicyInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *xAccessPolicyInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&kubeagenticnetworkingapiv1alpha1.XAccessPolicy{}, f.defaultInformer) +} + +func (f *xAccessPolicyInformer) Lister() apiv1alpha1.XAccessPolicyLister { + return apiv1alpha1.NewXAccessPolicyLister(f.Informer().GetIndexer()) +} diff --git a/k8s/client/informers/externalversions/generic.go b/k8s/client/informers/externalversions/generic.go index 9f2f9b54..9119284f 100644 --- a/k8s/client/informers/externalversions/generic.go +++ b/k8s/client/informers/externalversions/generic.go @@ -24,6 +24,7 @@ import ( schema "k8s.io/apimachinery/pkg/runtime/schema" cache "k8s.io/client-go/tools/cache" v0alpha0 "sigs.k8s.io/kube-agentic-networking/api/v0alpha0" + v1alpha1 "sigs.k8s.io/kube-agentic-networking/api/v1alpha1" ) // GenericInformer is type of SharedIndexInformer which will locate and delegate to other @@ -58,6 +59,10 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource case v0alpha0.SchemeGroupVersion.WithResource("xbackends"): return &genericInformer{resource: resource.GroupResource(), informer: f.Agentic().V0alpha0().XBackends().Informer()}, nil + // Group=agentic.networking.x-k8s.io, Version=v1alpha1 + case v1alpha1.SchemeGroupVersion.WithResource("xaccesspolicies"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Agentic().V1alpha1().XAccessPolicies().Informer()}, nil + } return nil, fmt.Errorf("no informer found for %v", resource) diff --git a/k8s/client/listers/api/v1alpha1/expansion_generated.go b/k8s/client/listers/api/v1alpha1/expansion_generated.go new file mode 100644 index 00000000..17ae7eba --- /dev/null +++ b/k8s/client/listers/api/v1alpha1/expansion_generated.go @@ -0,0 +1,27 @@ +/* +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 lister-gen. DO NOT EDIT. + +package v1alpha1 + +// XAccessPolicyListerExpansion allows custom methods to be added to +// XAccessPolicyLister. +type XAccessPolicyListerExpansion interface{} + +// XAccessPolicyNamespaceListerExpansion allows custom methods to be added to +// XAccessPolicyNamespaceLister. +type XAccessPolicyNamespaceListerExpansion interface{} diff --git a/k8s/client/listers/api/v1alpha1/xaccesspolicy.go b/k8s/client/listers/api/v1alpha1/xaccesspolicy.go new file mode 100644 index 00000000..6b000585 --- /dev/null +++ b/k8s/client/listers/api/v1alpha1/xaccesspolicy.go @@ -0,0 +1,70 @@ +/* +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 lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" + apiv1alpha1 "sigs.k8s.io/kube-agentic-networking/api/v1alpha1" +) + +// XAccessPolicyLister helps list XAccessPolicies. +// All objects returned here must be treated as read-only. +type XAccessPolicyLister interface { + // List lists all XAccessPolicies in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.XAccessPolicy, err error) + // XAccessPolicies returns an object that can list and get XAccessPolicies. + XAccessPolicies(namespace string) XAccessPolicyNamespaceLister + XAccessPolicyListerExpansion +} + +// xAccessPolicyLister implements the XAccessPolicyLister interface. +type xAccessPolicyLister struct { + listers.ResourceIndexer[*apiv1alpha1.XAccessPolicy] +} + +// NewXAccessPolicyLister returns a new XAccessPolicyLister. +func NewXAccessPolicyLister(indexer cache.Indexer) XAccessPolicyLister { + return &xAccessPolicyLister{listers.New[*apiv1alpha1.XAccessPolicy](indexer, apiv1alpha1.Resource("xaccesspolicy"))} +} + +// XAccessPolicies returns an object that can list and get XAccessPolicies. +func (s *xAccessPolicyLister) XAccessPolicies(namespace string) XAccessPolicyNamespaceLister { + return xAccessPolicyNamespaceLister{listers.NewNamespaced[*apiv1alpha1.XAccessPolicy](s.ResourceIndexer, namespace)} +} + +// XAccessPolicyNamespaceLister helps list and get XAccessPolicies. +// All objects returned here must be treated as read-only. +type XAccessPolicyNamespaceLister interface { + // List lists all XAccessPolicies in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.XAccessPolicy, err error) + // Get retrieves the XAccessPolicy from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*apiv1alpha1.XAccessPolicy, error) + XAccessPolicyNamespaceListerExpansion +} + +// xAccessPolicyNamespaceLister implements the XAccessPolicyNamespaceLister +// interface. +type xAccessPolicyNamespaceLister struct { + listers.ResourceIndexer[*apiv1alpha1.XAccessPolicy] +} diff --git a/k8s/crds/agentic.networking.x-k8s.io_xaccesspolicies.yaml b/k8s/crds/agentic.networking.x-k8s.io_xaccesspolicies.yaml index 6815c33a..73bd75ee 100644 --- a/k8s/crds/agentic.networking.x-k8s.io_xaccesspolicies.yaml +++ b/k8s/crds/agentic.networking.x-k8s.io_xaccesspolicies.yaml @@ -774,3 +774,819 @@ spec: storage: true subresources: status: {} + - name: v1alpha1 + schema: + openAPIV3Schema: + description: XAccessPolicy is the Schema for the accesspolicies API. + 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: + description: spec defines the desired state of AccessPolicy. + properties: + action: + description: |- + Action specifies the action to be taken when rules match. + Evaluation logic: + 1. ExternalAuth runs before all other Allow policies. + 2. If an ExternalAuth server denies the request, the request is denied. + 3. If it allows the request, processing continues for all other allow policies for that target. + 4. The request is allowed only if all allow policies allow it. + enum: + - Allow + - ExternalAuth + type: string + externalAuth: + description: |- + ExternalAuth specifies an external auth filter to be used for authorization. + Core support is limited to 1 ExternalAuth callout per target. + properties: + backendRef: + description: |- + BackendRef is a reference to a backend to send authorization + requests to. + + The backend must speak the selected protocol (GRPC or HTTP) on the + referenced port. + + If the backend service requires TLS, use BackendTLSPolicy to tell the + implementation to supply the TLS details to be used to connect to that + backend. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') ? + has(self.port) : true' + forwardBody: + description: |- + ForwardBody controls if requests to the authorization server should include + the body of the client request; and if so, how big that body is allowed + to be. + + It is expected that implementations will buffer the request body up to + `forwardBody.maxSize` bytes. Bodies over that size must be rejected with a + 4xx series error (413 or 403 are common examples), and fail processing + of the filter. + + If unset, or `forwardBody.maxSize` is set to `0`, then the body will not + be forwarded. + + Feature Name: HTTPRouteExternalAuthForwardBody + properties: + maxSize: + description: |- + MaxSize specifies how large in bytes the largest body that will be buffered + and sent to the authorization server. If the body size is larger than + `maxSize`, then the body sent to the authorization server must be + truncated to `maxSize` bytes. + + Experimental note: This behavior needs to be checked against + various dataplanes; it may need to be changed. + See https://github.com/kubernetes-sigs/gateway-api/pull/4001#discussion_r2291405746 + for more. + + If 0, the body will not be sent to the authorization server. + type: integer + type: object + grpc: + description: |- + GRPCAuthConfig contains configuration for communication with ext_authz + protocol-speaking backends. + + If unset, implementations must assume the default behavior for each + included field is intended. + properties: + allowedHeaders: + description: |- + AllowedRequestHeaders specifies what headers from the client request + will be sent to the authorization server. + + If this list is empty, then all headers must be sent. + + If the list has entries, only those entries must be sent. + items: + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + type: object + http: + description: |- + HTTPAuthConfig contains configuration for communication with HTTP-speaking + backends. + + If unset, implementations must assume the default behavior for each + included field is intended. + properties: + allowedHeaders: + description: |- + AllowedRequestHeaders specifies what additional headers from the client request + will be sent to the authorization server. + + The following headers must always be sent to the authorization server, + regardless of this setting: + + * `Host` + * `Method` + * `Path` + * `Content-Length` + * `Authorization` + + If this list is empty, then only those headers must be sent. + + Note that `Content-Length` has a special behavior, in that the length + sent must be correct for the actual request to the external authorization + server - that is, it must reflect the actual number of bytes sent in the + body of the request to the authorization server. + + So if the `forwardBody` stanza is unset, or `forwardBody.maxSize` is set + to `0`, then `Content-Length` must be `0`. If `forwardBody.maxSize` is set + to anything other than `0`, then the `Content-Length` of the authorization + request must be set to the actual number of bytes forwarded. + items: + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + allowedResponseHeaders: + description: |- + AllowedResponseHeaders specifies what headers from the authorization response + will be copied into the request to the backend. + + If this list is empty, then all headers from the authorization server + except Authority or Host must be copied. + items: + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + path: + description: |- + Path sets the prefix that paths from the client request will have added + when forwarded to the authorization server. + + When empty or unspecified, no prefix is added. + + Valid values are the same as the "value" regex for path values in the `match` + stanza, and the validation regex will screen out invalid paths in the same way. + Even with the validation, implementations MUST sanitize this input before using it + directly. + maxLength: 1024 + pattern: ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$ + type: string + type: object + protocol: + description: |- + ExternalAuthProtocol describes which protocol to use when communicating with an + ext_authz authorization server. + + When this is set to GRPC, each backend must use the Envoy ext_authz protocol + on the port specified in `backendRefs`. Requests and responses are defined + in the protobufs explained at: + https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto + + When this is set to HTTP, each backend must respond with a `200` status + code in on a successful authorization. Any other code is considered + an authorization failure. + + Feature Names: + GRPC Support - HTTPRouteExternalAuthGRPC + HTTP Support - HTTPRouteExternalAuthHTTP + enum: + - HTTP + - GRPC + type: string + required: + - backendRef + - protocol + type: object + x-kubernetes-validations: + - message: grpc must be specified when protocol is set to 'GRPC' + rule: 'self.protocol == ''GRPC'' ? has(self.grpc) : true' + - message: protocol must be 'GRPC' when grpc is set + rule: 'has(self.grpc) ? self.protocol == ''GRPC'' : true' + - message: http must be specified when protocol is set to 'HTTP' + rule: 'self.protocol == ''HTTP'' ? has(self.http) : true' + - message: protocol must be 'HTTP' when http is set + rule: 'has(self.http) ? self.protocol == ''HTTP'' : true' + rules: + description: |- + Rules defines a list of rules to be applied to the target. + An AccessPolicy must have at least one rule. + items: + description: AccessRule specifies an authorization rule for a specified + target. + properties: + authorization: + description: |- + Authorization specifies the authorization rule to be applied to requests from the source. + If omitted, all access from the specified source is allowed. + properties: + mcp: + description: |- + MCP defines MCP-specific matching criteria. + If omitted, the policy does not check MCP-level parameters, allowing all MCP traffic that + successfully passes through the matched HTTP routing envelope. + properties: + methods: + description: |- + Methods is a list of specific MCP functional methods to match. + If specified, only MCP requests with a method + that matches one of these items will be authorized. + If empty or omitted, no method-level allowlisting is applied, meaning all + MCP methods (e.g., all tools, prompts, and resources) are permitted. + items: + description: MCPMethod defines a specific MCP method + and its associated parameters. + properties: + name: + description: |- + Name is the MCP method to match against (e.g., 'tools/call'). + Allowed values: + 1. 'tools', 'prompts', 'resources': Matches all sub-methods under these categories. + 2. 'prompts/list', 'tools/list', 'resources/list', 'resources/templates/list'. + 3. 'prompts/get', 'tools/call', 'resources/subscribe', 'resources/unsubscribe', 'resources/read'. + Parameters cannot be specified for categories 1 and 2. + enum: + - tools + - prompts + - resources + - prompts/list + - tools/list + - resources/list + - resources/templates/list + - prompts/get + - tools/call + - resources/subscribe + - resources/unsubscribe + - resources/read + type: string + params: + description: |- + Params allows matching against specific arguments in the MCP request. + Only valid for 'get', 'call', 'subscribe', 'unsubscribe', and 'read' methods. + If empty or omitted, parameter-level allowlisting is not applied, meaning the method + is authorized regardless of the arguments passed in the request. + items: + maxLength: 20 + type: string + maxItems: 10 + type: array + x-kubernetes-list-type: set + required: + - name + type: object + x-kubernetes-validations: + - message: Params can only be specified for get, call, + subscribe, unsubscribe, or read methods + rule: 'has(self.params) && self.params.size() > + 0 ? self.name in [''prompts/get'', ''tools/call'', + ''resources/subscribe'', ''resources/unsubscribe'', + ''resources/read''] : true' + maxItems: 10 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: AuthorizationRuleType identifies a type of + authorization rule. + enum: + - Inline + type: string + required: + - type + type: object + name: + description: |- + Name specifies the name of the rule. + This follows the DNS Subdomain naming convention. + See: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + source: + description: Source specifies the source of the request. + properties: + serviceAccount: + description: |- + serviceAccount specifies a Kubernetes Service Account that is + matched by this rule. A request originating from a pod associated with + this Service Account will match the rule. + + The Service Account listed here is expected to exist within the same + trust domain as the targeted workload. Cross-trust-domain access should + instead be expressed using the `SPIFFE` field. + properties: + name: + description: Name is the name of the ServiceAccount. + type: string + namespace: + description: |- + Namespace is the namespace of the ServiceAccount + If not specified, current namespace (the namespace of the policy) is used. + type: string + required: + - name + type: object + spiffe: + description: |- + spiffe specifies an identity that is matched by this rule. + + spiffe identities must be specified as SPIFFE-formatted URIs following the pattern: + spiffe:/// + + The exact workload identifier structure is implementation-specific. + This will likely change in the future. + + SPIFFE identities for authorization can be derived in various ways by the underlying + implementation. Common methods include: + - From peer mTLS certificates: The identity is extracted from the client's + mTLS certificate presented during connection establishment. + - From IP-to-identity mappings: The implementation might maintain a dynamic + mapping between source IP addresses (pod IPs) and their associated + identities (e.g., Service Account, SPIFFE IDs). + - From JWTs or other request-level authentication tokens. + pattern: ^spiffe://[a-z0-9._-]+(?:/[A-Za-z0-9._-]+)*$ + type: string + type: + description: AuthorizationSourceType identifies a type of + source for authorization. + enum: + - ServiceAccount + - SPIFFE + type: string + required: + - type + type: object + required: + - name + - source + type: object + maxItems: 10 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + x-kubernetes-validations: + - message: AccessRule names must be unique + rule: self.all(r, self.filter(x, x.name == r.name).size() == 1) + targetRefs: + description: |- + TargetRefs specifies the targets of the AccessPolicy. + An AccessPolicy must target at least one resource. + There is one kind of TargetRef with "Core" support: + + * Gateway + + This API may be extended in the future to support additional kinds of targetRefs. + Implementations may support additional kinds in an implementation specific manner. + items: + description: |- + LocalPolicyTargetReferenceWithSectionName identifies an API object to apply a + direct policy to. This should be used as part of Policy resources that can + target single resources. For more information on how this policy attachment + mode works, and a sample Policy resource, refer to the policy attachment + documentation for Gateway API. + + Note: This should only be used for direct policy attachment when references + to SectionName are actually needed. In all other cases, + LocalPolicyTargetReference should be used. + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + maxItems: 10 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + x-kubernetes-validations: + - message: All targetRefs must have the same Kind + rule: self.all(ref, ref.kind == self[0].kind) + required: + - action + - rules + - targetRefs + type: object + x-kubernetes-validations: + - message: externalAuth must be specified when action is set to 'ExternalAuth' + rule: 'self.action == ''ExternalAuth'' ? has(self.externalAuth) : true' + status: + description: status defines the observed state of AccessPolicy. + properties: + ancestors: + items: + description: |- + PolicyAncestorStatus describes the status of a route with respect to an + associated Ancestor. + + Ancestors refer to objects that are either the Target of a policy or above it + in terms of object hierarchy. For example, if a policy targets a Service, the + Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and + the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most + useful object to place Policy status on, so we recommend that implementations + SHOULD use Gateway as the PolicyAncestorStatus object unless the designers + have a _very_ good reason otherwise. + + In the context of policy attachment, the Ancestor is used to distinguish which + resource results in a distinct application of this policy. For example, if a policy + targets a Service, it may have a distinct result per attached Gateway. + + Policies targeting the same resource may have different effects depending on the + ancestors of those resources. For example, different Gateways targeting the same + Service may have different capabilities, especially if they have different underlying + implementations. + + For example, in BackendTLSPolicy, the Policy attaches to a Service that is + used as a backend in a HTTPRoute that is itself attached to a Gateway. + In this case, the relevant object for status is the Gateway, and that is the + ancestor object referred to in this status. + + Note that a parent is also an ancestor, so for objects where the parent is the + relevant object for status, this struct SHOULD still be used. + + This struct is intended to be used in a slice that's effectively a map, + with a composite key made up of the AncestorRef and the ControllerName. + properties: + ancestorRef: + description: |- + AncestorRef corresponds with a ParentRef in the spec that this + PolicyAncestorStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + conditions: + description: |- + Conditions describes the status of the Policy with respect to the given Ancestor. + + + + Notes for implementors: + + Conditions are a listType `map`, which means that they function like a + map with a key of the `type` field _in the k8s apiserver_. + + This means that implementations must obey some rules when updating this + section. + + * Implementations MUST perform a read-modify-write cycle on this field + before modifying it. That is, when modifying this field, implementations + must be confident they have fetched the most recent version of this field, + and ensure that changes they make are on that recent version. + * Implementations MUST NOT remove or reorder Conditions that they are not + directly responsible for. For example, if an implementation sees a Condition + with type `special.io/SomeField`, it MUST NOT remove, change or update that + Condition. + * Implementations MUST always _merge_ changes into Conditions of the same Type, + rather than creating more than one Condition of the same Type. + * Implementations MUST always update the `observedGeneration` field of the + Condition to the `metadata.generation` of the Gateway at the time of update creation. + * If the `observedGeneration` of a Condition is _greater than_ the value the + implementation knows about, then it MUST NOT perform the update on that Condition, + but must wait for a future reconciliation and status update. (The assumption is that + the implementation's copy of the object is stale and an update will be re-triggered + if relevant.) + + + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + required: + - ancestorRef + - conditions + - controllerName + type: object + maxItems: 16 + type: array + x-kubernetes-list-type: atomic + required: + - ancestors + type: object + required: + - spec + type: object + served: false + storage: false + subresources: + status: {} diff --git a/k8s/crds/agentic.prototype.x-k8s.io_xaccesspolicies.yaml b/k8s/crds/agentic.prototype.x-k8s.io_xaccesspolicies.yaml deleted file mode 100644 index 6815c33a..00000000 --- a/k8s/crds/agentic.prototype.x-k8s.io_xaccesspolicies.yaml +++ /dev/null @@ -1,776 +0,0 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.19.0 - name: xaccesspolicies.agentic.networking.x-k8s.io -spec: - group: agentic.networking.x-k8s.io - names: - kind: XAccessPolicy - listKind: XAccessPolicyList - plural: xaccesspolicies - singular: xaccesspolicy - scope: Namespaced - versions: - - name: v0alpha0 - schema: - openAPIV3Schema: - description: XAccessPolicy is the Schema for the accesspolicies API. - 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: - description: spec defines the desired state of AccessPolicy. - properties: - rules: - description: |- - Rules defines a list of rules to be applied to the target. - An AccessPolicy must have at least one rule. - items: - description: |- - AccessRule specifies an authorization rule for the targeted backend. - If the tool list is empty, the rule denies access to all tools from Source. - properties: - authorization: - description: Authorization specifies the authorization rule - to be applied to requests from the source. - properties: - externalAuth: - description: |- - ExternalAuth specifies an external auth filter to be used for authorization. - - Support: Extended - properties: - backendRef: - description: |- - BackendRef is a reference to a backend to send authorization - requests to. - - The backend must speak the selected protocol (GRPC or HTTP) on the - referenced port. - - If the backend service requires TLS, use BackendTLSPolicy to tell the - implementation to supply the TLS details to be used to connect to that - backend. - properties: - group: - default: "" - description: |- - Group is the group of the referent. For example, "gateway.networking.k8s.io". - When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: |- - Kind is the Kubernetes resource kind of the referent. For example - "Service". - - Defaults to "Service" when not specified. - - ExternalName services can refer to CNAME DNS records that may live - outside of the cluster and as such are difficult to reason about in - terms of conformance. They also may not be safe to forward to (see - CVE-2021-25740 for more information). Implementations SHOULD NOT - support ExternalName Services. - - Support: Core (Services with a type other than ExternalName) - - Support: Implementation-specific (Services with type ExternalName) - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: |- - Namespace is the namespace of the backend. When unspecified, the local - namespace is inferred. - - Note that when a namespace different than the local namespace is specified, - a ReferenceGrant object is required in the referent namespace to allow that - namespace's owner to accept the reference. See the ReferenceGrant - documentation for details. - - Support: Core - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: |- - Port specifies the destination port number to use for this resource. - Port is required when the referent is a Kubernetes Service. In this - case, the port number is the service port number, not the target port. - For other resources, destination port might be derived from the referent - resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name - type: object - x-kubernetes-validations: - - message: Must have port for Service reference - rule: '(size(self.group) == 0 && self.kind == ''Service'') - ? has(self.port) : true' - forwardBody: - description: |- - ForwardBody controls if requests to the authorization server should include - the body of the client request; and if so, how big that body is allowed - to be. - - It is expected that implementations will buffer the request body up to - `forwardBody.maxSize` bytes. Bodies over that size must be rejected with a - 4xx series error (413 or 403 are common examples), and fail processing - of the filter. - - If unset, or `forwardBody.maxSize` is set to `0`, then the body will not - be forwarded. - - Feature Name: HTTPRouteExternalAuthForwardBody - properties: - maxSize: - description: |- - MaxSize specifies how large in bytes the largest body that will be buffered - and sent to the authorization server. If the body size is larger than - `maxSize`, then the body sent to the authorization server must be - truncated to `maxSize` bytes. - - Experimental note: This behavior needs to be checked against - various dataplanes; it may need to be changed. - See https://github.com/kubernetes-sigs/gateway-api/pull/4001#discussion_r2291405746 - for more. - - If 0, the body will not be sent to the authorization server. - type: integer - type: object - grpc: - description: |- - GRPCAuthConfig contains configuration for communication with ext_authz - protocol-speaking backends. - - If unset, implementations must assume the default behavior for each - included field is intended. - properties: - allowedHeaders: - description: |- - AllowedRequestHeaders specifies what headers from the client request - will be sent to the authorization server. - - If this list is empty, then all headers must be sent. - - If the list has entries, only those entries must be sent. - items: - type: string - maxItems: 64 - type: array - x-kubernetes-list-type: set - type: object - http: - description: |- - HTTPAuthConfig contains configuration for communication with HTTP-speaking - backends. - - If unset, implementations must assume the default behavior for each - included field is intended. - properties: - allowedHeaders: - description: |- - AllowedRequestHeaders specifies what additional headers from the client request - will be sent to the authorization server. - - The following headers must always be sent to the authorization server, - regardless of this setting: - - * `Host` - * `Method` - * `Path` - * `Content-Length` - * `Authorization` - - If this list is empty, then only those headers must be sent. - - Note that `Content-Length` has a special behavior, in that the length - sent must be correct for the actual request to the external authorization - server - that is, it must reflect the actual number of bytes sent in the - body of the request to the authorization server. - - So if the `forwardBody` stanza is unset, or `forwardBody.maxSize` is set - to `0`, then `Content-Length` must be `0`. If `forwardBody.maxSize` is set - to anything other than `0`, then the `Content-Length` of the authorization - request must be set to the actual number of bytes forwarded. - items: - type: string - maxItems: 64 - type: array - x-kubernetes-list-type: set - allowedResponseHeaders: - description: |- - AllowedResponseHeaders specifies what headers from the authorization response - will be copied into the request to the backend. - - If this list is empty, then all headers from the authorization server - except Authority or Host must be copied. - items: - type: string - maxItems: 64 - type: array - x-kubernetes-list-type: set - path: - description: |- - Path sets the prefix that paths from the client request will have added - when forwarded to the authorization server. - - When empty or unspecified, no prefix is added. - - Valid values are the same as the "value" regex for path values in the `match` - stanza, and the validation regex will screen out invalid paths in the same way. - Even with the validation, implementations MUST sanitize this input before using it - directly. - maxLength: 1024 - pattern: ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$ - type: string - type: object - protocol: - description: |- - ExternalAuthProtocol describes which protocol to use when communicating with an - ext_authz authorization server. - - When this is set to GRPC, each backend must use the Envoy ext_authz protocol - on the port specified in `backendRefs`. Requests and responses are defined - in the protobufs explained at: - https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto - - When this is set to HTTP, each backend must respond with a `200` status - code in on a successful authorization. Any other code is considered - an authorization failure. - - Feature Names: - GRPC Support - HTTPRouteExternalAuthGRPC - HTTP Support - HTTPRouteExternalAuthHTTP - enum: - - HTTP - - GRPC - type: string - required: - - backendRef - - protocol - type: object - x-kubernetes-validations: - - message: grpc must be specified when protocol is set to - 'GRPC' - rule: 'self.protocol == ''GRPC'' ? has(self.grpc) : true' - - message: protocol must be 'GRPC' when grpc is set - rule: 'has(self.grpc) ? self.protocol == ''GRPC'' : true' - - message: http must be specified when protocol is set to - 'HTTP' - rule: 'self.protocol == ''HTTP'' ? has(self.http) : true' - - message: protocol must be 'HTTP' when http is set - rule: 'has(self.http) ? self.protocol == ''HTTP'' : true' - tools: - description: Tools specifies a list of tools inline. - items: - type: string - type: array - x-kubernetes-list-type: set - type: - description: AuthorizationRuleType identifies a type of - authorization rule. - enum: - - InlineTools - - ExternalAuth - type: string - required: - - type - type: object - x-kubernetes-validations: - - message: tools must be specified when type is set to 'InlineTools' - rule: 'self.type == ''InlineTools'' ? has(self.tools) : true' - - message: externalAuth must be specified when type is set to - 'ExternalAuth' - rule: 'self.type == ''ExternalAuth'' ? has(self.externalAuth) - : true' - - message: only one of tools or externalAuth can be specified - rule: '!(has(self.tools) && has(self.externalAuth))' - name: - description: Name specifies the name of the rule. - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - source: - description: Source specifies the source of the request. - properties: - serviceAccount: - description: |- - serviceAccount specifies a Kubernetes Service Account that is - matched by this rule. A request originating from a pod associated with - this Service Account will match the rule. - - The Service Account listed here is expected to exist within the same - trust domain as the targeted workload. Cross-trust-domain access should - instead be expressed using the `SPIFFE` field. - properties: - name: - description: Name is the name of the ServiceAccount. - type: string - namespace: - description: |- - Namespace is the namespace of the ServiceAccount - If not specified, current namespace (the namespace of the policy) is used. - type: string - required: - - name - type: object - spiffe: - description: |- - spiffe specifies an identity that is matched by this rule. - - spiffe identities must be specified as SPIFFE-formatted URIs following the pattern: - spiffe:/// - - The exact workload identifier structure is implementation-specific. - This will likely change in the future. - - SPIFFE identities for authorization can be derived in various ways by the underlying - implementation. Common methods include: - - From peer mTLS certificates: The identity is extracted from the client's - mTLS certificate presented during connection establishment. - - From IP-to-identity mappings: The implementation might maintain a dynamic - mapping between source IP addresses (pod IPs) and their associated - identities (e.g., Service Account, SPIFFE IDs). - - From JWTs or other request-level authentication tokens. - pattern: ^spiffe://[a-z0-9._-]+(?:/[A-Za-z0-9._-]+)*$ - type: string - type: - description: AuthorizationSourceType identifies a type of - source for authorization. - enum: - - ServiceAccount - - SPIFFE - type: string - required: - - type - type: object - required: - - name - - source - type: object - maxItems: 10 - minItems: 1 - type: array - x-kubernetes-list-type: atomic - x-kubernetes-validations: - - message: AccessRule names must be unique - rule: self.all(r, self.filter(x, x.name == r.name).size() == 1) - - message: a maximum of one rule per policy can specify 'ExternalAuth' - authorization type - rule: self.filter(r, has(r.authorization) && r.authorization.type - == 'ExternalAuth').size() <= 1 - targetRefs: - description: |- - TargetRefs specifies the targets of the AccessPolicy. - An AccessPolicy must target at least one resource. - items: - description: |- - LocalPolicyTargetReferenceWithSectionName identifies an API object to apply a - direct policy to. This should be used as part of Policy resources that can - target single resources. For more information on how this policy attachment - mode works, and a sample Policy resource, refer to the policy attachment - documentation for Gateway API. - - Note: This should only be used for direct policy attachment when references - to SectionName are actually needed. In all other cases, - LocalPolicyTargetReference should be used. - properties: - group: - description: Group is the group of the target resource. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the target resource. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the target resource. - maxLength: 253 - minLength: 1 - type: string - sectionName: - description: |- - SectionName is the name of a section within the target resource. When - unspecified, this targetRef targets the entire resource. In the following - resources, SectionName is interpreted as the following: - - * Gateway: Listener name - * HTTPRoute: HTTPRouteRule name - * Service: Port name - - If a SectionName is specified, but does not exist on the targeted object, - the Policy must fail to attach, and the policy implementation should record - a `ResolvedRefs` or similar Condition in the Policy's status. - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - group - - kind - - name - type: object - maxItems: 10 - minItems: 1 - type: array - x-kubernetes-list-type: atomic - x-kubernetes-validations: - - message: TargetRef must have group agentic.networking.x-k8s.io and - kind XBackend, or group gateway.networking.k8s.io and kind Gateway - rule: self.all(x, (x.group == 'agentic.networking.x-k8s.io' && x.kind - == 'XBackend') || (x.group == 'gateway.networking.k8s.io' && x.kind - == 'Gateway')) - - message: All targetRefs must have the same Kind - rule: self.all(ref, ref.kind == self[0].kind) - required: - - rules - - targetRefs - type: object - status: - description: status defines the observed state of AccessPolicy. - properties: - ancestors: - description: |- - Ancestors is a list of ancestor resources (usually Backend) that are - associated with the policy, and the status of the policy with respect to - each ancestor. - - This field is inherited from the Gateway API Policy status definition. - For more details, see the upstream documentation: - https://gateway-api.sigs.k8s.io/reference/spec/#policyancestorstatus - items: - description: |- - PolicyAncestorStatus describes the status of a route with respect to an - associated Ancestor. - - Ancestors refer to objects that are either the Target of a policy or above it - in terms of object hierarchy. For example, if a policy targets a Service, the - Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and - the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most - useful object to place Policy status on, so we recommend that implementations - SHOULD use Gateway as the PolicyAncestorStatus object unless the designers - have a _very_ good reason otherwise. - - In the context of policy attachment, the Ancestor is used to distinguish which - resource results in a distinct application of this policy. For example, if a policy - targets a Service, it may have a distinct result per attached Gateway. - - Policies targeting the same resource may have different effects depending on the - ancestors of those resources. For example, different Gateways targeting the same - Service may have different capabilities, especially if they have different underlying - implementations. - - For example, in BackendTLSPolicy, the Policy attaches to a Service that is - used as a backend in a HTTPRoute that is itself attached to a Gateway. - In this case, the relevant object for status is the Gateway, and that is the - ancestor object referred to in this status. - - Note that a parent is also an ancestor, so for objects where the parent is the - relevant object for status, this struct SHOULD still be used. - - This struct is intended to be used in a slice that's effectively a map, - with a composite key made up of the AncestorRef and the ControllerName. - properties: - ancestorRef: - description: |- - AncestorRef corresponds with a ParentRef in the spec that this - PolicyAncestorStatus struct describes the status of. - properties: - group: - default: gateway.networking.k8s.io - description: |- - Group is the group of the referent. - When unspecified, "gateway.networking.k8s.io" is inferred. - To set the core API group (such as for a "Service" kind referent), - Group must be explicitly set to "" (empty string). - - Support: Core - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: |- - Kind is kind of the referent. - - There are two kinds of parent resources with "Core" support: - - * Gateway (Gateway conformance profile) - * Service (Mesh conformance profile, ClusterIP Services only) - - Support for other resources is Implementation-Specific. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: |- - Name is the name of the referent. - - Support: Core - maxLength: 253 - minLength: 1 - type: string - namespace: - description: |- - Namespace is the namespace of the referent. When unspecified, this refers - to the local namespace of the Route. - - Note that there are specific rules for ParentRefs which cross namespace - boundaries. Cross-namespace references are only valid if they are explicitly - allowed by something in the namespace they are referring to. For example: - Gateway has the AllowedRoutes field, and ReferenceGrant provides a - generic way to enable any other kind of cross-namespace reference. - - - ParentRefs from a Route to a Service in the same namespace are "producer" - routes, which apply default routing rules to inbound connections from - any namespace to the Service. - - ParentRefs from a Route to a Service in a different namespace are - "consumer" routes, and these routing rules are only applied to outbound - connections originating from the same namespace as the Route, for which - the intended destination of the connections are a Service targeted as a - ParentRef of the Route. - - - Support: Core - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: |- - Port is the network port this Route targets. It can be interpreted - differently based on the type of parent resource. - - When the parent resource is a Gateway, this targets all listeners - listening on the specified port that also support this kind of Route(and - select this Route). It's not recommended to set `Port` unless the - networking behaviors specified in a Route must apply to a specific port - as opposed to a listener(s) whose port(s) may be changed. When both Port - and SectionName are specified, the name and port of the selected listener - must match both specified values. - - - When the parent resource is a Service, this targets a specific port in the - Service spec. When both Port (experimental) and SectionName are specified, - the name and port of the selected port must match both specified values. - - - Implementations MAY choose to support other parent resources. - Implementations supporting other types of parent resources MUST clearly - document how/if Port is interpreted. - - For the purpose of status, an attachment is considered successful as - long as the parent resource accepts it partially. For example, Gateway - listeners can restrict which Routes can attach to them by Route kind, - namespace, or hostname. If 1 of 2 Gateway listeners accept attachment - from the referencing Route, the Route MUST be considered successfully - attached. If no Gateway listeners accept attachment from this Route, - the Route MUST be considered detached from the Gateway. - - Support: Extended - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: |- - SectionName is the name of a section within the target resource. In the - following resources, SectionName is interpreted as the following: - - * Gateway: Listener name. When both Port (experimental) and SectionName - are specified, the name and port of the selected listener must match - both specified values. - * Service: Port name. When both Port (experimental) and SectionName - are specified, the name and port of the selected listener must match - both specified values. - - Implementations MAY choose to support attaching Routes to other resources. - If that is the case, they MUST clearly document how SectionName is - interpreted. - - When unspecified (empty string), this will reference the entire resource. - For the purpose of status, an attachment is considered successful if at - least one section in the parent resource accepts it. For example, Gateway - listeners can restrict which Routes can attach to them by Route kind, - namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from - the referencing Route, the Route MUST be considered successfully - attached. If no Gateway listeners accept attachment from this Route, the - Route MUST be considered detached from the Gateway. - - Support: Core - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - conditions: - description: |- - Conditions describes the status of the Policy with respect to the given Ancestor. - - - - Notes for implementors: - - Conditions are a listType `map`, which means that they function like a - map with a key of the `type` field _in the k8s apiserver_. - - This means that implementations must obey some rules when updating this - section. - - * Implementations MUST perform a read-modify-write cycle on this field - before modifying it. That is, when modifying this field, implementations - must be confident they have fetched the most recent version of this field, - and ensure that changes they make are on that recent version. - * Implementations MUST NOT remove or reorder Conditions that they are not - directly responsible for. For example, if an implementation sees a Condition - with type `special.io/SomeField`, it MUST NOT remove, change or update that - Condition. - * Implementations MUST always _merge_ changes into Conditions of the same Type, - rather than creating more than one Condition of the same Type. - * Implementations MUST always update the `observedGeneration` field of the - Condition to the `metadata.generation` of the Gateway at the time of update creation. - * If the `observedGeneration` of a Condition is _greater than_ the value the - implementation knows about, then it MUST NOT perform the update on that Condition, - but must wait for a future reconciliation and status update. (The assumption is that - the implementation's copy of the object is stale and an update will be re-triggered - if relevant.) - - - items: - description: Condition contains details for one aspect of - the current state of this API Resource. - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, - Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - controllerName: - description: |- - ControllerName is a domain/path string that indicates the name of the - controller that wrote this status. This corresponds with the - controllerName field on GatewayClass. - - Example: "example.net/gateway-controller". - - The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are - valid Kubernetes names - (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). - - Controllers MUST populate this field when writing status. Controllers should ensure that - entries to status populated with their ControllerName are cleaned up when they are no - longer necessary. - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - required: - - ancestorRef - - conditions - - controllerName - type: object - maxItems: 16 - type: array - x-kubernetes-list-type: atomic - required: - - ancestors - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} diff --git a/tests/cel/accesspolicy_v1alpha1_test.go b/tests/cel/accesspolicy_v1alpha1_test.go new file mode 100644 index 00000000..1d01116c --- /dev/null +++ b/tests/cel/accesspolicy_v1alpha1_test.go @@ -0,0 +1,249 @@ +/* +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. +*/ + +package main + +import ( + "context" + "fmt" + "strings" + "testing" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" + + "sigs.k8s.io/kube-agentic-networking/api/v1alpha1" +) + +func TestValidateXAccessPolicyV1Alpha1(t *testing.T) { + ctx := context.Background() + basePolicy := v1alpha1.XAccessPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-v1alpha1", + Namespace: metav1.NamespaceDefault, + }, + Spec: v1alpha1.AccessPolicySpec{ + TargetRefs: []gwapiv1.LocalPolicyTargetReferenceWithSectionName{ + { + LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "my-gateway", + }, + }, + }, + Action: v1alpha1.ActionTypeAllow, + Rules: []v1alpha1.AccessRule{ + { + Name: "rule-1", + Source: v1alpha1.AccessRuleSource{ + Type: v1alpha1.AuthorizationSourceTypeServiceAccount, + ServiceAccount: &v1alpha1.AuthorizationSourceServiceAccount{ + Name: "sa-1", + }, + }, + }, + }, + }, + } + + testCases := []struct { + desc string + mutate func(p *v1alpha1.XAccessPolicy) + wantErrors []string + }{ + { + desc: "valid policy with Allow action", + mutate: func(_ *v1alpha1.XAccessPolicy) { + }, + }, + { + desc: "heterogeneous target kinds", + mutate: func(p *v1alpha1.XAccessPolicy) { + p.Spec.TargetRefs = append(p.Spec.TargetRefs, gwapiv1.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{ + Group: "agentic.networking.x-k8s.io", + Kind: "XBackend", + Name: "my-backend", + }, + }) + }, + wantErrors: []string{"All targetRefs must have the same Kind"}, + }, + { + desc: "valid policy with ExternalAuth action", + mutate: func(p *v1alpha1.XAccessPolicy) { + p.Spec.Action = v1alpha1.ActionTypeExternalAuth + p.Spec.ExternalAuth = &gwapiv1.HTTPExternalAuthFilter{ + ExternalAuthProtocol: gwapiv1.HTTPRouteExternalAuthGRPCProtocol, + BackendRef: gwapiv1.BackendObjectReference{ + Name: "ext-auth-svc", + Port: ptrTo(gwapiv1.PortNumber(50051)), + }, + GRPCAuthConfig: &gwapiv1.GRPCAuthConfig{}, + } + }, + }, + { + desc: "missing externalAuth when Action is ExternalAuth", + mutate: func(p *v1alpha1.XAccessPolicy) { + p.Spec.Action = v1alpha1.ActionTypeExternalAuth + }, + wantErrors: []string{"externalAuth must be specified when action is set to 'ExternalAuth'"}, + }, + { + desc: "duplicate rule names", + mutate: func(p *v1alpha1.XAccessPolicy) { + p.Spec.Rules = append(p.Spec.Rules, v1alpha1.AccessRule{ + Name: "rule-1", + Source: v1alpha1.AccessRuleSource{ + Type: v1alpha1.AuthorizationSourceTypeServiceAccount, + ServiceAccount: &v1alpha1.AuthorizationSourceServiceAccount{ + Name: "sa-2", + }, + }, + }) + }, + wantErrors: []string{"AccessRule names must be unique"}, + }, + { + desc: "invalid SPIFFE ID pattern", + mutate: func(p *v1alpha1.XAccessPolicy) { + spiffe := v1alpha1.AuthorizationSourceSPIFFE("not-a-spiffe-id") + p.Spec.Rules[0].Source = v1alpha1.AccessRuleSource{ + Type: v1alpha1.AuthorizationSourceTypeSPIFFE, + SPIFFE: &spiffe, + } + }, + wantErrors: []string{"spec.rules[0].source.spiffe in body should match '^spiffe://[a-z0-9._-]+(?:/[A-Za-z0-9._-]+)*$'"}, + }, + { + desc: "valid SPIFFE ID", + mutate: func(p *v1alpha1.XAccessPolicy) { + spiffe := v1alpha1.AuthorizationSourceSPIFFE("spiffe://trust.domain/workload") + p.Spec.Rules[0].Source = v1alpha1.AccessRuleSource{ + Type: v1alpha1.AuthorizationSourceTypeSPIFFE, + SPIFFE: &spiffe, + } + }, + }, + { + desc: "rule name too long", + mutate: func(p *v1alpha1.XAccessPolicy) { + p.Spec.Rules[0].Name = strings.Repeat("a", 64) + }, + wantErrors: []string{"may not be more than 63 bytes"}, + }, + { + desc: "too many targets", + mutate: func(p *v1alpha1.XAccessPolicy) { + for i := 0; i < 10; i++ { + p.Spec.TargetRefs = append(p.Spec.TargetRefs, gwapiv1.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "my-gateway", + }, + }) + } + }, + wantErrors: []string{"must have at most 10 items"}, + }, + { + desc: "too many rules", + mutate: func(p *v1alpha1.XAccessPolicy) { + for i := 0; i < 10; i++ { + p.Spec.Rules = append(p.Spec.Rules, v1alpha1.AccessRule{ + Name: fmt.Sprintf("rule-%d", i+2), + Source: v1alpha1.AccessRuleSource{ + Type: v1alpha1.AuthorizationSourceTypeServiceAccount, + ServiceAccount: &v1alpha1.AuthorizationSourceServiceAccount{ + Name: "sa-1", + }, + }, + }) + } + }, + wantErrors: []string{"must have at most 10 items"}, + }, + { + desc: "valid MCP method attributes with Inline type", + mutate: func(p *v1alpha1.XAccessPolicy) { + p.Spec.Rules[0].Authorization = &v1alpha1.AuthorizationRule{ + Type: v1alpha1.AuthorizationRuleTypeInline, + MCP: v1alpha1.MCPAttributes{ + Methods: []v1alpha1.MCPMethod{ + { + Name: "tools/call", + Params: []v1alpha1.MCPMethodParam{ + "param1", + }, + }, + }, + }, + } + }, + }, + { + desc: "invalid MCP method with params on prompts/list", + mutate: func(p *v1alpha1.XAccessPolicy) { + p.Spec.Rules[0].Authorization = &v1alpha1.AuthorizationRule{ + Type: v1alpha1.AuthorizationRuleTypeInline, + MCP: v1alpha1.MCPAttributes{ + Methods: []v1alpha1.MCPMethod{ + { + Name: "prompts/list", + Params: []v1alpha1.MCPMethodParam{ + "param1", + }, + }, + }, + }, + } + }, + wantErrors: []string{"Params can only be specified for get, call, subscribe, unsubscribe, or read methods"}, + }, + } + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + p := basePolicy.DeepCopy() + p.Name = fmt.Sprintf("foo-%v", time.Now().UnixNano()) + + if tc.mutate != nil { + tc.mutate(p) + } + err := k8sClient.Create(ctx, p) + + if (len(tc.wantErrors) != 0) != (err != nil) { + t.Fatalf("Unexpected response while creating XAccessPolicy; got err=\n%v\n;want error=%v", err, tc.wantErrors != nil) + } + + if err != nil { + var missingErrorStrings []string + for _, wantError := range tc.wantErrors { + if !celErrorStringMatches(err.Error(), wantError) { + missingErrorStrings = append(missingErrorStrings, wantError) + } + } + if len(missingErrorStrings) != 0 { + t.Errorf("Unexpected response while creating XAccessPolicy; got err=\n%v\n;missing strings within error=%q", err, missingErrorStrings) + } + } + }) + } +} diff --git a/tests/cel/main_test.go b/tests/cel/main_test.go index 1c282066..089be9a9 100644 --- a/tests/cel/main_test.go +++ b/tests/cel/main_test.go @@ -24,10 +24,13 @@ import ( "testing" "sigs.k8s.io/kube-agentic-networking/api/v0alpha0" + "sigs.k8s.io/kube-agentic-networking/api/v1alpha1" corev1 "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" @@ -44,6 +47,7 @@ func TestMain(m *testing.M) { var err error utilruntime.Must(v0alpha0.Install(scheme)) + utilruntime.Must(v1alpha1.Install(scheme)) // Add core APIs in case we refer secrets, services and configmaps utilruntime.Must(corev1.AddToScheme(scheme)) @@ -62,17 +66,20 @@ func TestMain(m *testing.M) { // If the envvar is not passed, the latest GA will be used k8sVersion := os.Getenv("K8S_VERSION") + // TODO: Revert to standard envtest.CRDInstallOptions{Paths: []string{...}} instead of custom loadAndModifyCRDs + // once XAccessPolicy v1alpha1 is fully implemented and flipped to `served: true` in the manifests. + var crds []*apiextensionsv1.CustomResourceDefinition + crds, err = loadAndModifyCRDs(filepath.Join("..", "..", "k8s", "crds")) + if err != nil { + panic(fmt.Sprintf("Failed to load and modify CRDs: %v", err)) + } + testEnv = &envtest.Environment{ Scheme: scheme, + CRDs: crds, ErrorIfCRDPathMissing: true, DownloadBinaryAssets: true, DownloadBinaryAssetsVersion: k8sVersion, - CRDInstallOptions: envtest.CRDInstallOptions{ - Paths: []string{ - filepath.Join("..", "..", "k8s", "crds"), - }, - CleanUpAfterUse: true, - }, } restConfig, err = testEnv.Start() @@ -98,6 +105,44 @@ func TestMain(m *testing.M) { os.Exit(rc) } +func loadAndModifyCRDs(dir string) ([]*apiextensionsv1.CustomResourceDefinition, error) { + files, err := os.ReadDir(dir) + if err != nil { + return nil, err + } + + var crds []*apiextensionsv1.CustomResourceDefinition + for _, file := range files { + if file.IsDir() || !strings.HasSuffix(file.Name(), ".yaml") { + continue + } + + path := filepath.Join(dir, file.Name()) + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + crd := &apiextensionsv1.CustomResourceDefinition{} + if err := yaml.Unmarshal(data, crd); err != nil { + return nil, fmt.Errorf("failed to unmarshal CRD from %s: %w", path, err) + } + + // If this is the XAccessPolicy CRD, activate v1alpha1 for CEL testing + if crd.Name == "xaccesspolicies.agentic.networking.x-k8s.io" { + for i, version := range crd.Spec.Versions { + if version.Name == "v1alpha1" { + crd.Spec.Versions[i].Served = true + } + } + } + + crds = append(crds, crd) + } + + return crds, nil +} + func ptrTo[T any](a T) *T { return &a } diff --git a/tests/crd/crd_test.go b/tests/crd/crd_test.go index 45d0c55f..590d7d00 100644 --- a/tests/crd/crd_test.go +++ b/tests/crd/crd_test.go @@ -28,12 +28,15 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/yaml" "sigs.k8s.io/controller-runtime/pkg/envtest" "sigs.k8s.io/kube-agentic-networking/api/v0alpha0" + "sigs.k8s.io/kube-agentic-networking/api/v1alpha1" ) func TestCRDValidation(t *testing.T) { @@ -44,22 +47,25 @@ func TestCRDValidation(t *testing.T) { var kubectlLocation, kubeconfigLocation string utilruntime.Must(v0alpha0.Install(scheme)) + utilruntime.Must(v1alpha1.Install(scheme)) utilruntime.Must(corev1.AddToScheme(scheme)) k8sVersion := os.Getenv("K8S_VERSION") t.Run("should be able to start test environment", func(_ *testing.T) { + // TODO: Revert to standard envtest.CRDInstallOptions{Paths: []string{...}} once XAccessPolicy v1alpha1 is fully implemented and flipped to `served: true` in manifests. + var crds []*apiextensionsv1.CustomResourceDefinition + crds, err = loadAndModifyCRDs(filepath.Join("..", "..", "k8s", "crds")) + if err != nil { + panic(fmt.Sprintf("Failed to load and modify CRDs: %v", err)) + } + testEnv = &envtest.Environment{ Scheme: scheme, + CRDs: crds, ErrorIfCRDPathMissing: true, DownloadBinaryAssets: true, DownloadBinaryAssetsVersion: k8sVersion, - CRDInstallOptions: envtest.CRDInstallOptions{ - Paths: []string{ - filepath.Join("..", "..", "k8s", "crds"), - }, - CleanUpAfterUse: true, - }, } _, err = testEnv.Start() @@ -82,6 +88,7 @@ func TestCRDValidation(t *testing.T) { apiResources, err := executeKubectlCommand(t, kubectlLocation, kubeconfigLocation, []string{"api-resources"}) require.NoError(t, err) require.Contains(t, apiResources, "agentic.networking.x-k8s.io/v0alpha0") + require.Contains(t, apiResources, "agentic.networking.x-k8s.io/v1alpha1") }) t.Run("should be able to install valid examples", func(t *testing.T) { @@ -141,3 +148,41 @@ func getInvalidExamplesFiles(t *testing.T) ([]string, error) { }) return files, err } + +func loadAndModifyCRDs(dir string) ([]*apiextensionsv1.CustomResourceDefinition, error) { + files, err := os.ReadDir(dir) + if err != nil { + return nil, err + } + + var crds []*apiextensionsv1.CustomResourceDefinition + for _, file := range files { + if file.IsDir() || !strings.HasSuffix(file.Name(), ".yaml") { + continue + } + + path := filepath.Join(dir, file.Name()) + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + crd := &apiextensionsv1.CustomResourceDefinition{} + if err := yaml.Unmarshal(data, crd); err != nil { + return nil, fmt.Errorf("failed to unmarshal CRD from %s: %w", path, err) + } + + // If this is the XAccessPolicy CRD, activate v1alpha1 for testing validation + if crd.Name == "xaccesspolicies.agentic.networking.x-k8s.io" { + for i, version := range crd.Spec.Versions { + if version.Name == "v1alpha1" { + crd.Spec.Versions[i].Served = true + } + } + } + + crds = append(crds, crd) + } + + return crds, nil +} diff --git a/tests/crd/examples/invalid/policy-v1alpha1-invalid-mcp-method-params.yaml b/tests/crd/examples/invalid/policy-v1alpha1-invalid-mcp-method-params.yaml new file mode 100644 index 00000000..a80f094a --- /dev/null +++ b/tests/crd/examples/invalid/policy-v1alpha1-invalid-mcp-method-params.yaml @@ -0,0 +1,23 @@ +apiVersion: agentic.networking.x-k8s.io/v1alpha1 +kind: XAccessPolicy +metadata: + name: policy-v1alpha1-invalid-mcp-method-params +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: my-gateway + action: Allow + rules: + - name: allow-sa + source: + type: ServiceAccount + serviceAccount: + name: my-sa + authorization: + type: Inline + mcp: + methods: + - name: prompts/list + params: + - disallowedParam diff --git a/tests/crd/examples/invalid/policy-v1alpha1-wrong-action-ext-auth.yaml b/tests/crd/examples/invalid/policy-v1alpha1-wrong-action-ext-auth.yaml new file mode 100644 index 00000000..e27997e1 --- /dev/null +++ b/tests/crd/examples/invalid/policy-v1alpha1-wrong-action-ext-auth.yaml @@ -0,0 +1,16 @@ +apiVersion: agentic.networking.x-k8s.io/v1alpha1 +kind: XAccessPolicy +metadata: + name: policy-v1alpha1-wrong-action-ext-auth +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: my-gateway + action: ExternalAuth + rules: + - name: allow-sa + source: + type: ServiceAccount + serviceAccount: + name: my-sa diff --git a/tests/crd/examples/invalid/policy-v1alpha1-wrong-target-kind.yaml b/tests/crd/examples/invalid/policy-v1alpha1-wrong-target-kind.yaml new file mode 100644 index 00000000..09070c5e --- /dev/null +++ b/tests/crd/examples/invalid/policy-v1alpha1-wrong-target-kind.yaml @@ -0,0 +1,19 @@ +apiVersion: agentic.networking.x-k8s.io/v1alpha1 +kind: XAccessPolicy +metadata: + name: policy-v1alpha1-wrong-target-kind +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: my-gateway + - group: agentic.networking.x-k8s.io + kind: XBackend + name: my-backend + action: Allow + rules: + - name: allow-sa + source: + type: ServiceAccount + serviceAccount: + name: my-sa diff --git a/tests/crd/examples/valid/accesspolicy_v1alpha1.yaml b/tests/crd/examples/valid/accesspolicy_v1alpha1.yaml new file mode 100644 index 00000000..9f864d22 --- /dev/null +++ b/tests/crd/examples/valid/accesspolicy_v1alpha1.yaml @@ -0,0 +1,27 @@ +apiVersion: agentic.networking.x-k8s.io/v1alpha1 +kind: XAccessPolicy +metadata: + name: valid-policy-v1alpha1 +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: my-gateway + action: Allow + rules: + - name: allow-sa + source: + type: ServiceAccount + serviceAccount: + name: my-sa + - name: allow-spiffe + source: + type: SPIFFE + spiffe: spiffe://example.com/my-service + authorization: + type: Inline + mcp: + methods: + - name: tools/call + params: + - param1