diff --git a/api/v1alpha1/apigateway_types.go b/api/v1alpha1/apigateway_types.go
index e246c33..4c03dbb 100644
--- a/api/v1alpha1/apigateway_types.go
+++ b/api/v1alpha1/apigateway_types.go
@@ -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),
}
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index 57b98dd..6aa26ea 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -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
diff --git a/config/crd/bases/supabase.k8s.icb4dc0.de_apigateways.yaml b/config/crd/bases/supabase.k8s.icb4dc0.de_apigateways.yaml
index aff2a06..ba7c81b 100644
--- a/config/crd/bases/supabase.k8s.icb4dc0.de_apigateways.yaml
+++ b/config/crd/bases/supabase.k8s.icb4dc0.de_apigateways.yaml
@@ -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:
diff --git a/config/samples/supabase_v1alpha1_apigateway.yaml b/config/samples/supabase_v1alpha1_apigateway.yaml
index 711ca54..f5551a3 100644
--- a/config/samples/supabase_v1alpha1_apigateway.yaml
+++ b/config/samples/supabase_v1alpha1_apigateway.yaml
@@ -6,8 +6,9 @@ metadata:
app.kubernetes.io/managed-by: kustomize
name: gateway-sample
spec:
- 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
- name: core-sample-jwt
- key: jwks.json
+ 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
+ name: core-sample-jwt
+ key: jwks.json
diff --git a/docs/api/supabase.k8s.icb4dc0.de.md b/docs/api/supabase.k8s.icb4dc0.de.md
index 1f783f9..b81cd28 100644
--- a/docs/api/supabase.k8s.icb4dc0.de.md
+++ b/docs/api/supabase.k8s.icb4dc0.de.md
@@ -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
this includes the JWT configuration | | |
+| `dashboardEndpoint` _[DashboardEndpointSpec](#dashboardendpointspec)_ | DashboardEndpoint - Configure the endpoint for the Supabase dashboard (studio)
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
Credentials need to be stored in basic auth form | | |
+#### DashboardEndpointSpec
+
+
+
+
+
+
+
+_Appears in:_
+- [APIGatewaySpec](#apigatewayspec)
+
+
+
#### DashboardList
diff --git a/internal/controller/apigateway_controller.go b/internal/controller/apigateway_controller.go
index 0a112e7..d460d64 100644
--- a/internal/controller/apigateway_controller.go
+++ b/internal/controller/apigateway_controller.go
@@ -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",
}},
},
diff --git a/internal/webhook/v1alpha1/apigateway_webhook_defaulter.go b/internal/webhook/v1alpha1/apigateway_webhook_defaulter.go
index fd12df3..2d86b97 100644
--- a/internal/webhook/v1alpha1/apigateway_webhook_defaulter.go
+++ b/internal/webhook/v1alpha1/apigateway_webhook_defaulter.go
@@ -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),
},