refactor(apigateway): configure api & dashboard listeneres individually
Some checks failed
Lint / Run on Ubuntu (push) Has been cancelled
E2E Tests / Run on Ubuntu (push) Has been cancelled
Tests / Run on Ubuntu (push) Has been cancelled
Docs / deploy (push) Successful in 1m54s
release / release (push) Successful in 7m41s

This commit is contained in:
Peter 2025-01-24 09:16:35 +01:00
parent 0014927ca9
commit c0cbd22bb0
Signed by: prskr
GPG key ID: F56BED6903BC5E37
7 changed files with 143 additions and 44 deletions

View file

@ -48,12 +48,23 @@ type EnvoySpec struct {
WorkloadTemplate *WorkloadTemplate `json:"workloadTemplate,omitempty"`
}
type ApiEndpointSpec struct {
// JWKSSelector - selector where the JWKS can be retrieved from to enable the API gateway to validate JWTs
JWKSSelector *corev1.SecretKeySelector `json:"jwks"`
}
type DashboardEndpointSpec struct{}
// APIGatewaySpec defines the desired state of APIGateway.
type APIGatewaySpec struct {
// Envoy - configure the envoy instance and most importantly the control-plane
Envoy *EnvoySpec `json:"envoy"`
// JWKSSelector - selector where the JWKS can be retrieved from to enable the API gateway to validate JWTs
JWKSSelector *corev1.SecretKeySelector `json:"jwks"`
// ApiEndpoint - Configure the endpoint for all API routes
// this includes the JWT configuration
ApiEndpoint *ApiEndpointSpec `json:"apiEndpoint,omitempty"`
// DashboardEndpoint - Configure the endpoint for the Supabase dashboard (studio)
// this includes optional authentication (basic or Oauth2) for the dashboard
DashboardEndpoint *DashboardEndpointSpec `json:"dashboardEndpoint,omitempty"`
// ServiceSelector - selector to match all Supabase services (or in fact EndpointSlices) that should be considered for this APIGateway
// +kubebuilder:default={"matchExpressions":{{"key": "app.kubernetes.io/part-of", "operator":"In", "values":{"supabase"}},{"key":"supabase.k8s.icb4dc0.de/api-gateway-target","operator":"Exists"}}}
ServiceSelector *metav1.LabelSelector `json:"serviceSelector"`
@ -88,7 +99,7 @@ type APIGateway struct {
func (g APIGateway) JwksSecretMeta() metav1.ObjectMeta {
return metav1.ObjectMeta{
Name: g.Spec.JWKSSelector.Name,
Name: g.Spec.ApiEndpoint.JWKSSelector.Name,
Namespace: g.Namespace,
Labels: maps.Clone(g.Labels),
}

View file

@ -93,11 +93,16 @@ func (in *APIGatewaySpec) DeepCopyInto(out *APIGatewaySpec) {
*out = new(EnvoySpec)
(*in).DeepCopyInto(*out)
}
if in.JWKSSelector != nil {
in, out := &in.JWKSSelector, &out.JWKSSelector
*out = new(v1.SecretKeySelector)
if in.ApiEndpoint != nil {
in, out := &in.ApiEndpoint, &out.ApiEndpoint
*out = new(ApiEndpointSpec)
(*in).DeepCopyInto(*out)
}
if in.DashboardEndpoint != nil {
in, out := &in.DashboardEndpoint, &out.DashboardEndpoint
*out = new(DashboardEndpointSpec)
**out = **in
}
if in.ServiceSelector != nil {
in, out := &in.ServiceSelector, &out.ServiceSelector
*out = new(metav1.LabelSelector)
@ -147,6 +152,26 @@ func (in *APIGatewayStatus) DeepCopy() *APIGatewayStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApiEndpointSpec) DeepCopyInto(out *ApiEndpointSpec) {
*out = *in
if in.JWKSSelector != nil {
in, out := &in.JWKSSelector, &out.JWKSSelector
*out = new(v1.SecretKeySelector)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApiEndpointSpec.
func (in *ApiEndpointSpec) DeepCopy() *ApiEndpointSpec {
if in == nil {
return nil
}
out := new(ApiEndpointSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AuthProviderMeta) DeepCopyInto(out *AuthProviderMeta) {
*out = *in
@ -485,6 +510,21 @@ func (in *DashboardDbSpec) DeepCopy() *DashboardDbSpec {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DashboardEndpointSpec) DeepCopyInto(out *DashboardEndpointSpec) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DashboardEndpointSpec.
func (in *DashboardEndpointSpec) DeepCopy() *DashboardEndpointSpec {
if in == nil {
return nil
}
out := new(DashboardEndpointSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DashboardList) DeepCopyInto(out *DashboardList) {
*out = *in

View file

@ -43,11 +43,49 @@ spec:
spec:
description: APIGatewaySpec defines the desired state of APIGateway.
properties:
apiEndpoint:
description: |-
ApiEndpoint - Configure the endpoint for all API routes
this includes the JWT configuration
properties:
jwks:
description: JWKSSelector - selector where the JWKS can be retrieved
from to enable the API gateway to validate JWTs
properties:
key:
description: The key of the secret to select from. Must be
a valid secret key.
type: string
name:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
optional:
description: Specify whether the Secret or its key must be
defined
type: boolean
required:
- key
type: object
x-kubernetes-map-type: atomic
required:
- jwks
type: object
componentTypeLabel:
default: app.kubernetes.io/name
description: ComponentTypeLabel - Label to identify which Supabase
component a Service represents (e.g. auth, postgrest, ...)
type: string
dashboardEndpoint:
description: |-
DashboardEndpoint - Configure the endpoint for the Supabase dashboard (studio)
this includes optional authentication (basic or Oauth2) for the dashboard
type: object
envoy:
description: Envoy - configure the envoy instance and most importantly
the control-plane
@ -2593,30 +2631,6 @@ spec:
required:
- controlPlane
type: object
jwks:
description: JWKSSelector - selector where the JWKS can be retrieved
from to enable the API gateway to validate JWTs
properties:
key:
description: The key of the secret to select from. Must be a
valid secret key.
type: string
name:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
optional:
description: Specify whether the Secret or its key must be defined
type: boolean
required:
- key
type: object
x-kubernetes-map-type: atomic
serviceSelector:
default:
matchExpressions:
@ -2674,7 +2688,6 @@ spec:
x-kubernetes-map-type: atomic
required:
- envoy
- jwks
- serviceSelector
type: object
status:

View file

@ -6,6 +6,7 @@ metadata:
app.kubernetes.io/managed-by: kustomize
name: gateway-sample
spec:
apiEndpoint:
jwks:
# will be created by Core resource operator if not present
# just make sure the secret name is either based on the name of the core resource or explicitly set

View file

@ -71,13 +71,30 @@ _Appears in:_
| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `envoy` _[EnvoySpec](#envoyspec)_ | Envoy - configure the envoy instance and most importantly the control-plane | | |
| `jwks` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#secretkeyselector-v1-core)_ | JWKSSelector - selector where the JWKS can be retrieved from to enable the API gateway to validate JWTs | | |
| `apiEndpoint` _[ApiEndpointSpec](#apiendpointspec)_ | ApiEndpoint - Configure the endpoint for all API routes<br />this includes the JWT configuration | | |
| `dashboardEndpoint` _[DashboardEndpointSpec](#dashboardendpointspec)_ | DashboardEndpoint - Configure the endpoint for the Supabase dashboard (studio)<br />this includes optional authentication (basic or Oauth2) for the dashboard | | |
| `serviceSelector` _[LabelSelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#labelselector-v1-meta)_ | ServiceSelector - selector to match all Supabase services (or in fact EndpointSlices) that should be considered for this APIGateway | \{ matchExpressions:[map[key:app.kubernetes.io/part-of operator:In values:[supabase]] map[key:supabase.k8s.icb4dc0.de/api-gateway-target operator:Exists]] \} | |
| `componentTypeLabel` _string_ | ComponentTypeLabel - Label to identify which Supabase component a Service represents (e.g. auth, postgrest, ...) | app.kubernetes.io/name | |
#### ApiEndpointSpec
_Appears in:_
- [APIGatewaySpec](#apigatewayspec)
| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `jwks` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#secretkeyselector-v1-core)_ | JWKSSelector - selector where the JWKS can be retrieved from to enable the API gateway to validate JWTs | | |
#### AuthProviderMeta
@ -319,6 +336,19 @@ _Appears in:_
| `dbCredentialsRef` _[DbCredentialsReference](#dbcredentialsreference)_ | DBCredentialsRef - reference to a Secret key where the DB credentials can be retrieved from<br />Credentials need to be stored in basic auth form | | |
#### DashboardEndpointSpec
_Appears in:_
- [APIGatewaySpec](#apigatewayspec)
#### DashboardList

View file

@ -53,7 +53,7 @@ var (
)
const (
jwksSecretNameField = ".spec.jwks.name"
jwksSecretNameField = ".spec.apiEndpoint.jwks.name"
)
func init() {
@ -116,7 +116,7 @@ func (r *APIGatewayReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Ma
return nil
}
return []string{gw.Spec.JWKSSelector.Name}
return []string{gw.Spec.ApiEndpoint.JWKSSelector.Name}
})
if err != nil {
return fmt.Errorf("setting up field index for JWKS secret name: %w", err)
@ -211,7 +211,7 @@ func (r *APIGatewayReconciler) reconcileJwksSecret(
return "", err
}
jwksRaw, ok := jwksSecret.Data[gateway.Spec.JWKSSelector.Key]
jwksRaw, ok := jwksSecret.Data[gateway.Spec.ApiEndpoint.JWKSSelector.Key]
if !ok {
return "", fmt.Errorf("%w in secret %s", ErrNoJwksConfigured, jwksSecret.Name)
}
@ -401,10 +401,10 @@ func (r *APIGatewayReconciler) reconileEnvoyDeployment(
{
Secret: &corev1.SecretProjection{
LocalObjectReference: corev1.LocalObjectReference{
Name: gateway.Spec.JWKSSelector.Name,
Name: gateway.Spec.ApiEndpoint.JWKSSelector.Name,
},
Items: []corev1.KeyToPath{{
Key: gateway.Spec.JWKSSelector.Key,
Key: gateway.Spec.ApiEndpoint.JWKSSelector.Key,
Path: "jwks.json",
}},
},

View file

@ -56,8 +56,12 @@ func (d *APIGatewayCustomDefaulter) Default(ctx context.Context, obj runtime.Obj
}
apigatewaylog.Info("Defaulting for APIGateway", "name", apiGateway.GetName())
if apiGateway.Spec.JWKSSelector == nil {
apiGateway.Spec.JWKSSelector = &corev1.SecretKeySelector{
if apiGateway.Spec.ApiEndpoint == nil {
apiGateway.Spec.ApiEndpoint = new(supabasev1alpha1.ApiEndpointSpec)
}
if apiGateway.Spec.ApiEndpoint.JWKSSelector == nil {
apiGateway.Spec.ApiEndpoint.JWKSSelector = &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: supabase.ServiceConfig.JWT.ObjectName(apiGateway),
},