Compare commits

..

1 Commits

Author SHA1 Message Date
renovate[bot]
f335e95f24 chore(deps): update capsule-proxy docker tag to v0.11.2 2026-03-13 10:42:13 +00:00
23 changed files with 6 additions and 785 deletions

View File

@@ -30,7 +30,7 @@ jobs:
timeout-minutes: 5
continue-on-error: true
- uses: creekorful/goreportcard-action@1f35ced8cdac2cba28c9a2f2288a16aacfd507f9 # v1.0
- uses: anchore/sbom-action/download-syft@a0a65128ee20bfc2cba8a1e7fc6ca46a88149706
- uses: anchore/sbom-action/download-syft@57aae528053a48a3f6235f2d9461b05fbcb7366d
- name: Install Cosign
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
- name: Run GoReleaser

View File

@@ -15,7 +15,7 @@ jobs:
pull-requests: write
steps:
- name: Close stale pull requests
uses: actions/stale@db5d06a4c82d5e94513c09c406638111df61f63e
uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f
with:
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 30 days.'
stale-pr-message: 'This pull request has been marked as stale because it has been inactive for more than 30 days. Please update this pull request or it will be automatically closed in 30 days.'

View File

@@ -30,9 +30,4 @@ type NamespaceRuleEnforceBody struct {
// Define registries which are allowed to be used within this tenant
// The rules are aggregated, since you can use Regular Expressions the match registry endpoints
Registries []api.OCIRegistry `json:"registries,omitempty"`
// Define gateway rules for this namespace, restricting which Gateway resources
// Routes may reference and optionally injecting a default Gateway parentRef.
// +optional
Gateways *api.GatewayRuleSpec `json:"gateways,omitempty"`
}

View File

@@ -464,11 +464,6 @@ func (in *NamespaceRuleEnforceBody) DeepCopyInto(out *NamespaceRuleEnforceBody)
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Gateways != nil {
in, out := &in.Gateways, &out.Gateways
*out = new(api.GatewayRuleSpec)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespaceRuleEnforceBody.

View File

@@ -1,6 +1,6 @@
dependencies:
- name: capsule-proxy
repository: oci://ghcr.io/projectcapsule/charts
version: 0.10.0
digest: sha256:b268fe0a87e4fa4d0196e5dac82c7e8ae20e96053f5ca860b1f7c44e3a357406
generated: "2025-12-09T15:58:45.796317945Z"
version: 0.11.2
digest: sha256:d4a756b7abb7ed928fd1d18e3870793bdc900eef81d2530a04e42606e273985c
generated: "2026-03-13T10:42:09.898265739Z"

View File

@@ -6,7 +6,7 @@ home: https://projectcapsule.dev/
icon: https://github.com/projectcapsule/capsule/raw/main/assets/logo/capsule_small.png
dependencies:
- name: capsule-proxy
version: 0.10.0
version: 0.11.2
repository: "oci://ghcr.io/projectcapsule/charts"
condition: proxy.enabled
alias: proxy

View File

@@ -51,86 +51,6 @@ spec:
enforce:
description: Enforcement Rules applied
properties:
gateways:
description: Define gateway rules for this namespace, restricting
which Gateway resources Routes may reference and optionally
injecting a default Gateway parentRef.
properties:
gateway:
description: Gateway restricts which Gateways Routes may
reference and optionally specifies a default Gateway injected
when no parentRef is provided.
properties:
allowed:
description: Allowed is an explicit list of Gateways
(by name and optional namespace) that Routes in this
namespace may reference.
items:
properties:
name:
description: Name of the Gateway.
type: string
namespace:
description: Namespace of the Gateway. When empty,
the Route's namespace is assumed.
type: string
required:
- name
type: object
type: array
default:
description: Default is the Gateway injected as parentRef
when no parentRef is specified in a Route resource.
properties:
name:
description: Name of the Gateway.
type: string
namespace:
description: Namespace of the Gateway. When empty,
the Route's namespace is assumed.
type: string
required:
- name
type: object
matchExpressions:
description: matchExpressions is a list of label selector
requirements. The requirements are ANDed.
items:
description: |-
A label selector requirement is a selector that contains values, a key, and an operator that
relates the key and values.
properties:
key:
description: key is the label key that the selector
applies to.
type: string
operator:
description: |-
operator represents a key's relationship to a set of values.
Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: |-
values is an array of string values. If the operator is In or NotIn,
the values array must be non-empty. If the operator is Exists or DoesNotExist,
the values array must be empty.
items:
type: string
type: array
x-kubernetes-list-type: atomic
required:
- key
- operator
type: object
type: array
x-kubernetes-list-type: atomic
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs.
type: object
type: object
type: object
registries:
description: |-
Define registries which are allowed to be used within this tenant

View File

@@ -2492,86 +2492,6 @@ spec:
enforce:
description: Enforcement Rules applied
properties:
gateways:
description: Define gateway rules for this namespace, restricting
which Gateway resources Routes may reference and optionally
injecting a default Gateway parentRef.
properties:
gateway:
description: Gateway restricts which Gateways Routes may
reference and optionally specifies a default Gateway
injected when no parentRef is provided.
properties:
allowed:
description: Allowed is an explicit list of Gateways
(by name and optional namespace) that Routes in this
namespace may reference.
items:
properties:
name:
description: Name of the Gateway.
type: string
namespace:
description: Namespace of the Gateway. When empty,
the Route's namespace is assumed.
type: string
required:
- name
type: object
type: array
default:
description: Default is the Gateway injected as parentRef
when no parentRef is specified in a Route resource.
properties:
name:
description: Name of the Gateway.
type: string
namespace:
description: Namespace of the Gateway. When empty,
the Route's namespace is assumed.
type: string
required:
- name
type: object
matchExpressions:
description: matchExpressions is a list of label selector
requirements. The requirements are ANDed.
items:
description: |-
A label selector requirement is a selector that contains values, a key, and an operator that
relates the key and values.
properties:
key:
description: key is the label key that the selector
applies to.
type: string
operator:
description: |-
operator represents a key's relationship to a set of values.
Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: |-
values is an array of string values. If the operator is In or NotIn,
the values array must be non-empty. If the operator is Exists or DoesNotExist,
the values array must be empty.
items:
type: string
type: array
x-kubernetes-list-type: atomic
required:
- key
- operator
type: object
type: array
x-kubernetes-list-type: atomic
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs.
type: object
type: object
type: object
registries:
description: |-
Define registries which are allowed to be used within this tenant

View File

@@ -157,43 +157,6 @@ webhooks:
timeoutSeconds: {{ $.Values.webhooks.mutatingWebhooksTimeoutSeconds }}
{{- end }}
{{- end }}
{{- with .Values.webhooks.hooks.httproutes }}
{{- if .enabled }}
- name: httproute.defaults.projectcapsule.dev
admissionReviewVersions:
- v1
clientConfig:
{{- include "capsule.webhooks.service" (dict "path" "/defaults" "ctx" $) | nindent 4 }}
failurePolicy: {{ .failurePolicy }}
matchPolicy: {{ .matchPolicy }}
reinvocationPolicy: {{ .reinvocationPolicy }}
{{- with .namespaceSelector }}
namespaceSelector:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .objectSelector }}
objectSelector:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .matchConditions }}
matchConditions:
{{- toYaml . | nindent 4 }}
{{- end }}
rules:
- apiGroups:
- gateway.networking.k8s.io
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- httproutes
scope: "Namespaced"
sideEffects: None
timeoutSeconds: {{ $.Values.webhooks.mutatingWebhooksTimeoutSeconds }}
{{- end }}
{{- end }}
{{- with (mergeOverwrite .Values.webhooks.hooks.namespaces .Values.webhooks.hooks.namespaceOwnerReference) }}
{{- if .enabled }}
- name: namespaces.tenants.projectcapsule.dev

View File

@@ -153,43 +153,6 @@ webhooks:
timeoutSeconds: {{ $.Values.webhooks.validatingWebhooksTimeoutSeconds }}
{{- end }}
{{- end }}
{{- with .Values.webhooks.hooks.httproutes }}
{{- if .enabled }}
- name: httproute.projectcapsule.dev
admissionReviewVersions:
- v1
- v1beta1
clientConfig:
{{- include "capsule.webhooks.service" (dict "path" "/httproutes/validating" "ctx" $) | nindent 4 }}
failurePolicy: {{ .failurePolicy }}
matchPolicy: {{ .matchPolicy }}
{{- with .namespaceSelector }}
namespaceSelector:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .objectSelector }}
objectSelector:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .matchConditions }}
matchConditions:
{{- toYaml . | nindent 4 }}
{{- end }}
rules:
- apiGroups:
- gateway.networking.k8s.io
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- httproutes
scope: Namespaced
sideEffects: None
timeoutSeconds: {{ $.Values.webhooks.validatingWebhooksTimeoutSeconds }}
{{- end }}
{{- end }}
{{- with .Values.webhooks.hooks.ingresses }}
{{- if .enabled }}
- name: ingress.projectcapsule.dev

View File

@@ -623,27 +623,6 @@ webhooks:
operator: Exists
# -- [MatchConditions](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-matchpolicy)
matchConditions: []
# -- [ReinvocationPolicy](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#reinvocation-policy)
reinvocationPolicy: Never
httproutes:
# -- Enable the Hook
enabled: true
# -- [FailurePolicy](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#failure-policy)
failurePolicy: Fail
# -- [MatchPolicy](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-matchpolicy)
matchPolicy: Equivalent
# -- [ObjectSelector](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-objectselector)
objectSelector: {}
# -- [NamespaceSelector](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-namespaceselector)
namespaceSelector:
matchExpressions:
- key: capsule.clastix.io/tenant
operator: Exists
# -- [MatchConditions](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-matchpolicy)
matchConditions: []
# -- [ReinvocationPolicy](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#reinvocation-policy)
reinvocationPolicy: Never
ingresses:
# -- Enable the Hook

View File

@@ -49,7 +49,6 @@ import (
"github.com/projectcapsule/capsule/internal/webhook/defaults"
"github.com/projectcapsule/capsule/internal/webhook/dra"
"github.com/projectcapsule/capsule/internal/webhook/gateway"
"github.com/projectcapsule/capsule/internal/webhook/httproute"
"github.com/projectcapsule/capsule/internal/webhook/ingress"
"github.com/projectcapsule/capsule/internal/webhook/misc"
namespacemutation "github.com/projectcapsule/capsule/internal/webhook/namespace/mutation"
@@ -281,11 +280,6 @@ func main() {
),
route.MiscCustomResources(misc.ResourceCounterHandler(manager.GetClient())),
route.Gateway(gateway.Class(cfg)),
route.HTTPRoute(
httproute.Handler(
httproute.GatewayValidator(),
),
),
route.DeviceClass(dra.DeviceClass()),
route.Defaults(defaults.Handler(cfg, kubeVersion)),
route.TenantMutation(

View File

@@ -58,8 +58,6 @@ func (h *handler) mutate(ctx context.Context, req admission.Request, c client.Cl
response = mutateIngressDefaults(ctx, req, h.version, c, decoder, req.Namespace)
case metav1.GroupVersionResource{Group: "gateway.networking.k8s.io", Version: "v1", Resource: "gateways"}:
response = mutateGatewayDefaults(ctx, req, c, decoder, req.Namespace)
case metav1.GroupVersionResource{Group: "gateway.networking.k8s.io", Version: "v1", Resource: "httproutes"}:
response = mutateHTTPRouteDefaults(ctx, req, c, decoder, req.Namespace)
}
if response == nil {

View File

@@ -1,102 +0,0 @@
// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package defaults
import (
"context"
"encoding/json"
"net/http"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
capsulehttproute "github.com/projectcapsule/capsule/internal/webhook/httproute"
"github.com/projectcapsule/capsule/internal/webhook/utils"
"github.com/projectcapsule/capsule/pkg/tenant"
)
func mutateHTTPRouteDefaults(ctx context.Context, req admission.Request, c client.Client, decoder admission.Decoder, namespace string) *admission.Response {
routeObj := &gatewayv1.HTTPRoute{}
if err := decoder.Decode(req, routeObj); err != nil {
return utils.ErroredResponse(err)
}
routeObj.SetNamespace(namespace)
tnt, err := capsulehttproute.TenantFromHTTPRoute(ctx, c, routeObj)
if err != nil {
return utils.ErroredResponse(err)
}
if tnt == nil {
return nil
}
// Resolve namespace-level rules to find the gateway default.
ns := &corev1.Namespace{}
if err = c.Get(ctx, types.NamespacedName{Name: namespace}, ns); err != nil {
return utils.ErroredResponse(err)
}
ruleBody, err := tenant.BuildNamespaceRuleBodyForNamespace(ns, tnt)
if err != nil {
return utils.ErroredResponse(err)
}
if ruleBody == nil || ruleBody.Enforce.Gateways == nil || ruleBody.Enforce.Gateways.Gateway == nil {
return nil
}
gwDefault := ruleBody.Enforce.Gateways.Gateway.Default
if gwDefault == nil {
return nil
}
// Only inject the default when the HTTPRoute has no parentRefs.
if len(routeObj.Spec.ParentRefs) > 0 {
return nil
}
defaultNamespace := gwDefault.Namespace
if defaultNamespace == "" {
defaultNamespace = namespace
}
ns16 := gatewayv1.Namespace(defaultNamespace)
routeObj.Spec.ParentRefs = []gatewayv1.ParentReference{
{
Group: groupPtr(gatewayv1.GroupName),
Kind: kindPtr("Gateway"),
Name: gatewayv1.ObjectName(gwDefault.Name),
Namespace: &ns16,
},
}
marshaled, err := json.Marshal(routeObj)
if err != nil {
response := admission.Errored(http.StatusInternalServerError, err)
return &response
}
response := admission.PatchResponseFromRaw(req.Object.Raw, marshaled)
return &response
}
func groupPtr(g string) *gatewayv1.Group {
v := gatewayv1.Group(g)
return &v
}
func kindPtr(k string) *gatewayv1.Kind {
v := gatewayv1.Kind(k)
return &v
}

View File

@@ -1,21 +0,0 @@
// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package httproute
import (
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
"github.com/projectcapsule/capsule/pkg/runtime/handlers"
)
// Handler builds a handlers.Handler that wraps all provided
// TypedHandlerWithTenantWithRuleset[*gatewayv1.HTTPRoute] implementations.
func Handler(handler ...handlers.TypedHandlerWithTenantWithRuleset[*gatewayv1.HTTPRoute]) handlers.Handler {
return &handlers.TypedTenantWithRulesetHandler[*gatewayv1.HTTPRoute]{
Factory: func() *gatewayv1.HTTPRoute {
return &gatewayv1.HTTPRoute{}
},
Handlers: handler,
}
}

View File

@@ -1,28 +0,0 @@
// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package httproute
import (
"context"
"sigs.k8s.io/controller-runtime/pkg/client"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
)
// TenantFromHTTPRoute returns the Tenant owning the namespace of the given HTTPRoute,
// or nil if the namespace does not belong to any Tenant.
func TenantFromHTTPRoute(ctx context.Context, c client.Client, route *gatewayv1.HTTPRoute) (*capsulev1beta2.Tenant, error) {
tenantList := &capsulev1beta2.TenantList{}
if err := c.List(ctx, tenantList, client.MatchingFields{".status.namespaces": route.Namespace}); err != nil {
return nil, err
}
if len(tenantList.Items) == 0 {
return nil, nil //nolint:nilnil
}
return &tenantList.Items[0], nil
}

View File

@@ -1,142 +0,0 @@
// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package httproute
import (
"context"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/events"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
caperrors "github.com/projectcapsule/capsule/pkg/api/errors"
evt "github.com/projectcapsule/capsule/pkg/runtime/events"
"github.com/projectcapsule/capsule/pkg/runtime/handlers"
)
type gatewayValidator struct{}
// GatewayValidator returns a TypedHandlerWithTenantWithRuleset that validates
// HTTPRoute parentRefs against the gateway rules configured in namespace rules.
func GatewayValidator() handlers.TypedHandlerWithTenantWithRuleset[*gatewayv1.HTTPRoute] {
return &gatewayValidator{}
}
func (h *gatewayValidator) OnCreate(
c client.Client,
obj *gatewayv1.HTTPRoute,
_ admission.Decoder,
recorder events.EventRecorder,
tnt *capsulev1beta2.Tenant,
rule *capsulev1beta2.NamespaceRuleBody,
) handlers.Func {
return func(ctx context.Context, req admission.Request) *admission.Response {
return h.validate(ctx, c, req, obj, tnt, recorder, rule)
}
}
func (h *gatewayValidator) OnUpdate(
c client.Client,
_ *gatewayv1.HTTPRoute,
obj *gatewayv1.HTTPRoute,
_ admission.Decoder,
recorder events.EventRecorder,
tnt *capsulev1beta2.Tenant,
rule *capsulev1beta2.NamespaceRuleBody,
) handlers.Func {
return func(ctx context.Context, req admission.Request) *admission.Response {
return h.validate(ctx, c, req, obj, tnt, recorder, rule)
}
}
func (h *gatewayValidator) OnDelete(
_ client.Client,
_ *gatewayv1.HTTPRoute,
_ admission.Decoder,
_ events.EventRecorder,
_ *capsulev1beta2.Tenant,
_ *capsulev1beta2.NamespaceRuleBody,
) handlers.Func {
return func(context.Context, admission.Request) *admission.Response {
return nil
}
}
func (h *gatewayValidator) validate(
ctx context.Context,
c client.Client,
req admission.Request,
route *gatewayv1.HTTPRoute,
tnt *capsulev1beta2.Tenant,
recorder events.EventRecorder,
rule *capsulev1beta2.NamespaceRuleBody,
) *admission.Response {
if rule == nil || rule.Enforce.Gateways == nil || rule.Enforce.Gateways.Gateway == nil {
return nil
}
allowed := rule.Enforce.Gateways.Gateway
for _, parentRef := range route.Spec.ParentRefs {
// Only validate parentRefs that point to a Gateway resource.
if parentRef.Kind != nil && *parentRef.Kind != gatewayv1.Kind("Gateway") {
continue
}
if parentRef.Group != nil && string(*parentRef.Group) != gatewayv1.GroupName {
continue
}
gwName := string(parentRef.Name)
gwNamespace := route.Namespace
if parentRef.Namespace != nil {
gwNamespace = string(*parentRef.Namespace)
}
// Try to fetch the Gateway object for label-selector matching.
gw := &gatewayv1.Gateway{}
var gwObj client.Object
if err := c.Get(ctx, types.NamespacedName{Namespace: gwNamespace, Name: gwName}, gw); err != nil {
if !k8serrors.IsNotFound(err) {
return errResponse(err)
}
} else {
gwObj = gw
}
if !allowed.MatchGateway(gwNamespace, gwName, gwObj) {
recorder.Eventf(
tnt,
nil,
corev1.EventTypeWarning,
evt.ReasonForbiddenGateway,
evt.ActionValidationDenied,
"HTTPRoute %s/%s references forbidden Gateway %s/%s",
req.Namespace, req.Name, gwNamespace, gwName,
)
response := admission.Denied(
caperrors.NewGatewayForbidden(gwName, gwNamespace, *allowed).Error(),
)
return &response
}
}
return nil
}
func errResponse(err error) *admission.Response {
resp := admission.Errored(500, err)
return &resp
}

View File

@@ -1,24 +0,0 @@
// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package route
import "github.com/projectcapsule/capsule/pkg/runtime/handlers"
type httproute struct {
handlers []handlers.Handler
}
// HTTPRoute returns a Webhook that handles admission requests for
// gateway.networking.k8s.io/v1 HTTPRoute resources.
func HTTPRoute(handler ...handlers.Handler) handlers.Webhook {
return &httproute{handlers: handler}
}
func (w *httproute) GetHandlers() []handlers.Handler {
return w.handlers
}
func (w *httproute) GetPath() string {
return "/httproutes/validating"
}

View File

@@ -6,7 +6,6 @@ package errors
import (
"fmt"
"reflect"
"strings"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
@@ -77,41 +76,3 @@ func NewGatewayClassUndefined(spec api.DefaultAllowedListSpec) error {
func (i GatewayClassUndefinedError) Error() string {
return utils.DefaultAllowedValuesErrorMessage(i.spec, "No gateway Class is forbidden for the current Tenant. Specify a gateway Class which is allowed within the Tenant: ")
}
// GatewayForbiddenError is returned when an HTTPRoute references a Gateway that
// is not in the allowed list for the Tenant's namespace rules.
type GatewayForbiddenError struct {
gatewayNamespace string
gatewayName string
spec api.AllowedGatewaySpec
}
func NewGatewayForbidden(name, namespace string, spec api.AllowedGatewaySpec) error {
return &GatewayForbiddenError{
gatewayNamespace: namespace,
gatewayName: name,
spec: spec,
}
}
func (e GatewayForbiddenError) Error() string {
msg := fmt.Sprintf("Gateway %s/%s is forbidden for the current Tenant: ", e.gatewayNamespace, e.gatewayName)
parts := []string{msg}
if e.spec.Default != nil {
parts = append(parts, fmt.Sprintf("default: %s/%s", e.spec.Default.Namespace, e.spec.Default.Name))
}
if len(e.spec.Allowed) > 0 {
names := make([]string, 0, len(e.spec.Allowed))
for _, a := range e.spec.Allowed {
names = append(names, fmt.Sprintf("%s/%s", a.Namespace, a.Name))
}
parts = append(parts, fmt.Sprintf("allowed: [%s]", strings.Join(names, ", ")))
}
return strings.Join(parts, " ")
}

View File

@@ -1,83 +0,0 @@
// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package api
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// GatewayNamespacedName is a namespaced reference to a Gateway resource.
// +kubebuilder:object:generate=true
type GatewayNamespacedName struct {
// Name of the Gateway.
Name string `json:"name"`
// Namespace of the Gateway. When empty, the Route's namespace is assumed.
// +optional
Namespace string `json:"namespace,omitempty"`
}
// AllowedGatewaySpec defines which Gateway resources are allowed to be
// referenced by Routes in a namespace, and an optional default Gateway.
// +kubebuilder:object:generate=true
type AllowedGatewaySpec struct {
// Default is the Gateway injected as parentRef when no parentRef is specified
// in a Route resource. The Gateway must also be allowed via Allowed or the
// LabelSelector.
// +optional
Default *GatewayNamespacedName `json:"default,omitempty"`
// LabelSelector matches Gateway resources by labels.
// +optional
metav1.LabelSelector `json:",inline"`
// Allowed is an explicit list of Gateways (by name and optional namespace)
// that Routes in this namespace may reference.
// +optional
Allowed []GatewayNamespacedName `json:"allowed,omitempty"`
}
// MatchDefault returns true when the given namespace/name matches the configured
// default Gateway.
func (in *AllowedGatewaySpec) MatchDefault(gwNamespace, gwName string) bool {
if in.Default == nil {
return false
}
return in.Default.Name == gwName && (in.Default.Namespace == "" || in.Default.Namespace == gwNamespace)
}
// MatchGateway returns true when the given Gateway is allowed by this spec.
// A Gateway is allowed if it is the default, appears in the Allowed list, or
// its labels match the LabelSelector.
func (in *AllowedGatewaySpec) MatchGateway(gwNamespace, gwName string, obj client.Object) bool {
if in.MatchDefault(gwNamespace, gwName) {
return true
}
for _, a := range in.Allowed {
if a.Name == gwName && (a.Namespace == "" || a.Namespace == gwNamespace) {
return true
}
}
if obj != nil && (len(in.MatchLabels) > 0 || len(in.MatchExpressions) > 0) {
selector, err := metav1.LabelSelectorAsSelector(&in.LabelSelector)
if err == nil && selector.Matches(labels.Set(obj.GetLabels())) {
return true
}
}
return false
}
// GatewayRuleSpec defines gateway-related enforcement rules for a namespace.
// +kubebuilder:object:generate=true
type GatewayRuleSpec struct {
// Gateway restricts which Gateways Routes may reference and optionally
// specifies a default Gateway injected when no parentRef is provided.
// +optional
Gateway *AllowedGatewaySpec `json:"gateway,omitempty"`
}

View File

@@ -111,32 +111,6 @@ func (in *AdditionalRoleBindingsSpec) DeepCopy() *AdditionalRoleBindingsSpec {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AllowedGatewaySpec) DeepCopyInto(out *AllowedGatewaySpec) {
*out = *in
if in.Default != nil {
in, out := &in.Default, &out.Default
*out = new(GatewayNamespacedName)
**out = **in
}
in.LabelSelector.DeepCopyInto(&out.LabelSelector)
if in.Allowed != nil {
in, out := &in.Allowed, &out.Allowed
*out = make([]GatewayNamespacedName, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedGatewaySpec.
func (in *AllowedGatewaySpec) DeepCopy() *AllowedGatewaySpec {
if in == nil {
return nil
}
out := new(AllowedGatewaySpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AllowedListSpec) DeepCopyInto(out *AllowedListSpec) {
*out = *in
@@ -264,41 +238,6 @@ func (in *ForbiddenListSpec) DeepCopy() *ForbiddenListSpec {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GatewayNamespacedName) DeepCopyInto(out *GatewayNamespacedName) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayNamespacedName.
func (in *GatewayNamespacedName) DeepCopy() *GatewayNamespacedName {
if in == nil {
return nil
}
out := new(GatewayNamespacedName)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GatewayRuleSpec) DeepCopyInto(out *GatewayRuleSpec) {
*out = *in
if in.Gateway != nil {
in, out := &in.Gateway, &out.Gateway
*out = new(AllowedGatewaySpec)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayRuleSpec.
func (in *GatewayRuleSpec) DeepCopy() *GatewayRuleSpec {
if in == nil {
return nil
}
out := new(GatewayRuleSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LimitRangesSpec) DeepCopyInto(out *LimitRangesSpec) {
*out = *in

View File

@@ -33,7 +33,6 @@ const (
ReasonMissingGatewayClass string = "MissingGatewayClass"
ReasonMissingDeviceClass string = "MissingDeviceClass"
ReasonForbiddenDeviceClass string = "ForbiddenDeviceClass"
ReasonForbiddenGateway string = "ForbiddenGateway"
// Pods.
ReasonMissingFQCI string = "MissingFQCI"

View File

@@ -58,11 +58,6 @@ func BuildNamespaceRuleBodyForNamespace(
if len(rule.Enforce.Registries) > 0 {
out.Enforce.Registries = append(out.Enforce.Registries, rule.Enforce.Registries...)
}
// Merge gateway rules: last non-nil gateway rule wins.
if rule.Enforce.Gateways != nil {
out.Enforce.Gateways = rule.Enforce.Gateways.DeepCopy()
}
}
return out, nil