Skip to content

Conversation

@didierofrivia
Copy link
Member

@didierofrivia didierofrivia commented Jan 13, 2026

Closes #14

This PR makes the Developer Portal APIKey controller to automatically propagate AuthPolicy APIKey selector labels to the generated Secrets. This enables Authorino to correctly identify and validate API keys (Secrets) based on the labels defined in the AuthPolicy's authentication scheme. It also corrects the label key from app to use the constant apiKeySecretLabelDevPortalKey (devportal.kuadrant.io/apiproduct). Besides, it updates its resource Status displaying the associated information for the APIKey auth method and credential location

@didierofrivia didierofrivia force-pushed the apikey-secret-labels branch 2 times, most recently from 6b6e41e to 9d0a939 Compare January 14, 2026 10:36
@didierofrivia didierofrivia self-assigned this Jan 14, 2026
@didierofrivia didierofrivia moved this to In Progress in Kuadrant Jan 14, 2026
@didierofrivia didierofrivia changed the title [WIP] APIKey secret labels APIKey secret labels Jan 14, 2026
@didierofrivia didierofrivia marked this pull request as ready for review January 14, 2026 16:38
@didierofrivia didierofrivia requested a review from eguzki January 14, 2026 16:38
@didierofrivia didierofrivia moved this from In Progress to Ready For Review in Kuadrant Jan 14, 2026
Copy link
Contributor

@eguzki eguzki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Codewise looks good. Just one question regarding the approach for computing the authpolicy.

I have run some verification steps and it's working as expected.

When the authpolicy is missing, the controller successfully creates the secret, without reporting error logs 👍

make kind-create-cluster
make install
make gateway-api-install
make kuadrant-core-install

make local-deploy

kubectl create ns gateway-system

kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: my-gateway
  namespace: gateway-system
spec:
  gatewayClassName: istio
  listeners:
  - name: http
    protocol: HTTP
    port: 80
    hostname: "example.com"
    allowedRoutes:
      namespaces:
        from: All
EOF

kubectl create ns toystore

kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: my-route
  namespace: toystore
spec:
  hostnames:
    - example.com
  parentRefs:
    - name: my-gateway
      namespace: gateway-system
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: "/"
      backendRefs:
        - name: toystore
          port: 80
EOF

cat <<EOF >route-status-patch.yaml
status:
  parents:
    - controllerName: istio.io/gateway-controller
      conditions:
        - lastTransitionTime: "2025-09-03T19:16:16Z"
          message: Route was valid
          observedGeneration: 1
          reason: Accepted
          status: "True"
          type: Accepted
      parentRef:
        group: gateway.networking.k8s.io
        kind: Gateway
        name: my-gateway
        namespace: gateway-system
EOF

kubectl patch httproute my-route --type=merge --patch "$(cat route-status-patch.yaml)" --subresource status -n toystore
rm route-status-patch.yaml

kubectl apply -f - <<EOF
apiVersion: kuadrant.io/v1
kind: AuthPolicy
metadata:
  name: toystore-auth
  namespace: toystore
spec:
  targetRef:
    group: gateway.networking.k8s.io
    kind: HTTPRoute
    name: my-route
  rules:
    authentication:
      "api-key-users":
        apiKey:
          selector:
            matchLabels:
              app: toystore
        credentials:
          authorizationHeader:
            prefix: APIKEY
EOF

cat <<EOF >authpolicy-status-patch.yaml
status:
  conditions:
    - lastTransitionTime: "2025-09-03T19:16:16Z"
      message: Route was valid
      reason: Accepted
      status: "True"
      type: Accepted
    - lastTransitionTime: "2025-09-03T19:16:16Z"
      message: Route was valid
      reason: Enforced
      status: "True"
      type: Enforced
EOF

kubectl patch authpolicy toystore-auth --type=merge --patch "$(cat authpolicy-status-patch.yaml)" --subresource status -n toystore
rm authpolicy-status-patch.yaml

kubectl apply -f - <<EOF
apiVersion: devportal.kuadrant.io/v1alpha1
kind: APIProduct
metadata:
  name: toystore-api
  namespace: toystore
spec:
  targetRef:
    group: gateway.networking.k8s.io
    kind: HTTPRoute
    name: my-route
  displayName: Toystore API
  description: Manage toy inventory
  approvalMode: manual
  publishStatus: Published
EOF

kubectl apply -f - <<EOF
apiVersion: extensions.kuadrant.io/v1alpha1
kind: PlanPolicy
metadata:
  name: toystore-plans
  namespace: toystore
spec:
  targetRef:
    group: gateway.networking.k8s.io
    kind: HTTPRoute
    name: my-route
  plans:
    - tier: gold
      predicate: |
        has(auth.identity) && auth.identity.metadata.annotations["secret.kuadrant.io/plan-id"] == "gold"
      limits:
        daily: 100
    - tier: silver
      predicate: |
        has(auth.identity) && auth.identity.metadata.annotations["secret.kuadrant.io/plan-id"] == "silver"
      limits:
        daily: 50
    - tier: premium
      predicate: |
        has(auth.identity) && auth.identity.metadata.annotations["secret.kuadrant.io/plan-id"] == "premium"
      limits:
        daily: 10
EOF

kubectl apply -f - <<EOF
apiVersion: devportal.kuadrant.io/v1alpha1
kind: APIKey
metadata:
  name: my-app-apikey
  namespace: toystore
spec:
  # Reference to the APIProduct this key is for
  apiProductRef:
    name: toystore-api
  # Plan tier for rate limiting and feature access
  planTier: premium
  # Use case description for approval review
  useCase: Mobile application for pet store management
  requestedBy:
    userId: user-12345
    email: developer@example.com
EOF

kubectl patch apikey my-app-apikey -n toystore --subresource=status --type='merge' -p '{"status":{"phase":"Approved"}}'

@didierofrivia didierofrivia requested a review from eguzki January 16, 2026 13:35
Copy link
Contributor

@eguzki eguzki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good to me.

Just one issue found, but it is based on an edge case: approving an apikey for authpolicy that does not have apikey auth method.

return v.GetMethod() == v1beta3.ApiKeyAuthentication
})

if apiKeyAuthMethods != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

an slice can be not nil and empty!

Tried with an authpolicy only with jwt auth method, and when creating a apikey and approving it, the controller panic'ed

026-01-19T17:44:16Z	ERROR	Observed a panic	{"controller": "apikey", "controllerGroup": "devportal.kuadrant.io", "controllerKind": "APIKey", "APIKey": {"name":"my-app-apikey","namespace":"toystore"}, "namespace": "toystore", "name": "my-app-apikey", "reconcileID": "6ff38d4c-a37d-484f-9ae8-e0c29efd7623", "panic": "runtime error: index out of range [0] with length 0", "panicGoValue": "runtime.boundsError{x:0, y:0, signed:true, code:0x0}", "stacktrace": "goroutine 299 [running]:\nk8s.io/apimachinery/pkg/util/runtime.logPanic({0x2543448, 0xc000526a20}, {0x2059200, 0xc000ba20c0})\n\t/go/pkg/mod/k8s.io/apimachinery@v0.33.3/pkg/util/runtime/runtime.go:132 +0xbc\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).Reconcile.func1()\n\t/go/pkg/mod/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:108 +0x112\npanic({0x2059200?, 0xc000ba20c0?})\n\t/usr/local/go/src/runtime/panic.go:792 +0x132\ngithub.com/kuadrant/developer-portal-controller/internal/controller.getAPIKeyAuthScheme(0xc000526bd0)\n\t/workspace/internal/controller/apikey_controller.go:293 +0x33d\ngithub.com/kuadrant/developer-portal-controller/internal/controller.(*APIKeyReconciler).reconcileApproved(0xc000791560, {0x2543448, 0xc000526a20}, 0xc000f00700)\n\t/workspace/internal/controller/apikey_controller.go:225 +0x185\ngithub.com/kuadrant/developer-portal-controller/internal/controller.(*APIKeyReconciler).Reconcile(0xc000791560, {0x2543448, 0xc000526a20}, {{{0xc000b981d8?, 0x21d3593?}, {0xc000b981e0?, 0x100?}}})\n\t/workspace/internal/controller/apikey_controller.go:100 +0x21e\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).Reconcile(0xc000526990?, {0x2543448?, 0xc000526a20?}, {{{0xc000b981d8?, 0x0?}, {0xc000b981e0?, 0x0?}}})\n\t/go/pkg/mod/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:119 +0xbf\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).reconcileHandler(0x256cb80, {0x2543480, 0xc000532dc0}, {{{0xc000b981d8, 0x8}, {0xc000b981e0, 0xd}}}, 0x0)\n\t/go/pkg/mod/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:340 +0x3ad\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).processNextWorkItem(0x256cb80, {0x2543480, 0xc000532dc0})\n\t/go/pkg/mod/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:300 +0x21b\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).Start.func2.1()\n\t/go/pkg/mod/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:202 +0x85\ncreated by sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).Start.func2 in goroutine 88\n\t/go/pkg/mod/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:198 +0x28f\n"}
k8s.io/apimachinery/pkg/util/runtime.logPanic
	/go/pkg/mod/k8s.io/apimachinery@v0.33.3/pkg/util/runtime/runtime.go:142
sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).Reconcile.func1
	/go/pkg/mod/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:108

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oopsie! that true, better to check the length

Signed-off-by: dd <4183971+didierofrivia@users.noreply.github.com>
Signed-off-by: dd <4183971+didierofrivia@users.noreply.github.com>
Signed-off-by: dd <4183971+didierofrivia@users.noreply.github.com>
Signed-off-by: dd <4183971+didierofrivia@users.noreply.github.com>
Signed-off-by: dd <4183971+didierofrivia@users.noreply.github.com>
Signed-off-by: dd di cesare <4183971+didierofrivia@users.noreply.github.com>
Signed-off-by: dd <4183971+didierofrivia@users.noreply.github.com>
Signed-off-by: dd <4183971+didierofrivia@users.noreply.github.com>
@didierofrivia didierofrivia merged commit e98457e into main Jan 20, 2026
19 checks passed
@didierofrivia didierofrivia deleted the apikey-secret-labels branch January 20, 2026 11:05
@github-project-automation github-project-automation bot moved this from Ready For Review to Done in Kuadrant Jan 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

API key secret labels are hardcoded

3 participants