CRD reference (pattern resources)

⏱ ~15 min

This module is optional read-only reference — you do not need it to complete the workshop flow. It summarizes Kubernetes custom resources (CRs) used in the AI Computer Vision at the Edge Validated Pattern. Helm templates under charts/all/ are the source of truth; examples below are simplified for workshop reading.

Use it when you want a single place to look up CR names, relationships, and chart locations after completing Module 07 — Review.

How a self-service OIDC client becomes a rate-limit tier

When a workshop user runs the OIDC credentials self-service scaffolder template in Developer Hub, the chosen plan tier flows through the gateway stack in five steps:

  1. The user selects planTier (free or gold) on the template form.

  2. The template creates a Keycloak client with an oidc-hardcoded-claim-mapper protocol mapper that embeds plan: <tier> in every JWT.

  3. The client obtains a token from realm cv (client_credentials or Bearer flow).

  4. Kuadrant AuthPolicy (authpolicy-cv on neuroface-cv-lb) validates the JWT issuer (keycloak.<hub-domain>/realms/cv) and admits the request.

  5. RateLimitPolicy and PlanPolicy on neuroface-cv-lb match auth.identity.plan == "free" or "gold" and apply /v1/predict limits (100/h or 500/h).

The MaaS route (ai-maas) uses the same dual-auth pattern (APIKEY or OIDC JWT) with PlanPolicy and TokenRateLimitPolicy tiers on /v1/chat/completions instead of per-request limits.

Resource relationship map

Hub Cluster Gateway API + RHCL Gateway HTTPRoute OIDCPolicy AuthPolicy RateLimitPolicy APIProduct DevPortal plugin RHBK (Keycloak) KeycloakRealmImport (cv, maas, neuroface) Keycloak CR → StatefulSet External Secrets Operator ExternalSecret → Vault secrets Red Hat Developer Hub Backstage CR catalog ConfigMaps Software Templates (scaffolder) RBAC policy CSV dynamic-plugins-rhdh Keycloak OIDC (backstage realm) extraFiles.configMaps: users, neuroface-cv, neuroface, kuadrant-apis, iam-realms, public-apis, rhdh-plugins, software-templates Red Hat Service Interconnect Site AccessGrant/Token Listener Connector Service Mesh (Istio ambient) Istio CR Red Hat OpenShift AI InferenceService Spoke Clusters (east / west) Connector NeuroFace pods HashiCorp Vault secret/hub/keycloak/realms/* secret/hub/developer-hub-secrets ClusterSecretStore Legend: targetRef (policy → route) OIDC issuer secret sync (ESO/Vault) Skupper tunnel (hub ↔ spoke) catalog mount / plugin

Architecture map

Hub Cluster Developer Hub Catalog, Scaffolder, LS RHBK (Keycloak) OIDC Identity Provider Gateway API + RHCL Routing & Kuadrant Auth HashiCorp Vault / ESO Secrets Management GitLab Source Code / Configs OpenShift GitOps Argo CD ApplicationSets Red Hat OpenShift AI (Hub) AI Gateway / MaaS LLM Proxy OpenShift Pipelines Tekton CI/CD Image Builds Spoke Clusters (East / West) Service Mesh (Istio) Ambient Mode L4/L7 Secure Routing ztunnel / waypoint proxies OpenShift AI (Edge) ModelMesh / KServe InferenceServices YOLO PPE Detection / OpenVINO NeuroFace Application Vue Frontend & Python Backend Biometric 2FA & Camera stream processing OpenShift Virtualization KubeVirt VMs Legacy workloads on the edge Service Interconnect

Scaffolding sequence diagram

User Developer Hub GitLab Argo CD Spoke Cluster 1. Fill software template 2. Publish repo (scaffolder) 3. Return repo URL 4. Register in Catalog 5. AppSet detects new repo 6. Generate Application CR 7. Sync manifests (GitOps) 8. Tekton PipelineRun 9. App available via Gateway / Mesh

Documentation: Red Hat Connectivity Link

OIDCPolicy

Purpose: Enforce browser redirect OIDC login on Gateway API HTTPRoutes (interactive sessions).

Chart: charts/all/neuroface-gateway/templates/oidcpolicy-realms.yaml

neuroface-cv-lb deliberately does not use OIDCPolicy. That route serves API clients with Bearer tokens, not browser logins. An OIDCPolicy redirect breaks CORS preflight (OPTIONS requests cannot carry a token). Realm cv on neuroface-cv-lb uses AuthPolicy + jwt-users instead (see below).

apiVersion: extensions.kuadrant.io/v1alpha1
kind: OIDCPolicy
metadata:
  name: oidc-neuroface
  namespace: neuroface-gateway-system
spec:
  provider:
    issuerURL: https://sso.apps.cluster.example.com/realms/neuroface
    clientID: client-neuroface-user1
    authorizationEndpoint: https://sso.apps.cluster.example.com/realms/neuroface/protocol/openid-connect/auth
    tokenEndpoint: https://sso.apps.cluster.example.com/realms/neuroface/protocol/openid-connect/token
    redirectURI: https://neuroface.apps.cluster.example.com/auth/callback
  targetRef:
    group: gateway.networking.k8s.io
    kind: HTTPRoute
    name: neuroface-app-lb
  auth:
    tokenSource:
      authorizationHeader:
        prefix: Bearer
        name: Authorization
      cookie:
        name: jwt

Connection: OIDCPolicyHTTPRouteGateway → backend Service (via Skupper on spokes). Per-user scaffolded NeuroFace instances on spokes use the same redirect pattern against their local RHBK realm.

AuthPolicy

Purpose: API-key and/or JWT authentication on Gateway API HTTPRoutes. A single AuthPolicy can declare multiple identity sources; Authorino admits the request if any one succeeds ("dual auth").

apiVersion: kuadrant.io/v1
kind: AuthPolicy
metadata:
  name: authpolicy-cv
  namespace: neuroface-gateway-system
spec:
  targetRef:
    group: gateway.networking.k8s.io
    kind: HTTPRoute
    name: neuroface-cv-lb
  when:
    - predicate: request.method != "OPTIONS"
  rules:
    authentication:
      jwt-users:
        jwt:
          issuerUrl: https://keycloak.apps.cluster.example.com/realms/cv

Connection: AuthPolicyHTTPRoute → Keycloak JWT issuer. Workshop APIs (for example ai-maas) add an api-key-users source alongside jwt-users for dual auth.

Chart: charts/all/workshop-kuadrant-apis/templates/policies.yaml

RateLimitPolicy

Purpose: Per-route request limits keyed off authenticated identity (JWT claims or client IP).

apiVersion: kuadrant.io/v1
kind: RateLimitPolicy
metadata:
  name: neuroface-cv-ratelimit
  namespace: neuroface-gateway-system
spec:
  targetRef:
    group: gateway.networking.k8s.io
    kind: HTTPRoute
    name: neuroface-cv-lb
  limits:
    predict-free:
      when:
        - predicate: 'request.path.matches("^/v1/predict")'
        - predicate: 'has(auth.identity) && has(auth.identity.plan) && auth.identity.plan == "free"'
      counters:
        - expression: auth.identity.sub
      rates:
        - limit: 100
          window: 1h
    predict-gold:
      when:
        - predicate: 'request.path.matches("^/v1/predict")'
        - predicate: 'has(auth.identity) && has(auth.identity.plan) && auth.identity.plan == "gold"'
      counters:
        - expression: auth.identity.sub
      rates:
        - limit: 500
          window: 1h
    predict-default:
      when:
        - predicate: 'request.path.matches("^/v1/predict")'
        - predicate: '!(has(auth.identity) && has(auth.identity.plan))'
      counters:
        - expression: request.headers['x-forwarded-for']
      rates:
        - limit: 30
          window: 1m

Connection: RateLimitPolicyHTTPRoute. The plan JWT claim (from the self-service template’s planTier parameter) selects the predict-free or predict-gold tier.

Chart: charts/all/neuroface-gateway/templates/ratelimit-policy.yaml

PlanPolicy

Purpose: Publish discoverable rate-limit tiers in the Kuadrant DevPortal plugin (Developer Hub API entity Kuadrant tab).

apiVersion: extensions.kuadrant.io/v1alpha1
kind: PlanPolicy
metadata:
  name: neuroface-cv-plans
  namespace: neuroface-gateway-system
spec:
  targetRef:
    group: gateway.networking.k8s.io
    kind: HTTPRoute
    name: neuroface-cv-lb
  plans:
    - tier: free
      predicate: |
        has(auth.identity) && has(auth.identity.plan) && auth.identity.plan == "free"
      limits:
        custom:
          - limit: 100
            window: 1h
    - tier: gold
      predicate: |
        has(auth.identity) && has(auth.identity.plan) && auth.identity.plan == "gold"
      limits:
        custom:
          - limit: 500
            window: 1h

Connection: PlanPolicyHTTPRoute → DevPortal discovered-plans UI. Pairs with RateLimitPolicy on the same route.

Chart: charts/all/neuroface-gateway/templates/plan-policy.yaml

TokenRateLimitPolicy

Purpose: Token-budget rate limits for LLM endpoints (counts tokens, not requests).

apiVersion: kuadrant.io/v1alpha1
kind: TokenRateLimitPolicy
metadata:
  name: ai-maas-token-limits
  namespace: ai-gateway-system
spec:
  targetRef:
    group: gateway.networking.k8s.io
    kind: HTTPRoute
    name: ai-maas
  limits:
    free:
      rates:
        - limit: 50
          window: 1m
      when:
        - predicate: request.path == "/v1/chat/completions"
        - predicate: |
            auth.identity.metadata.annotations["secret.kuadrant.io/plan-id"] == "free" ||
            auth.identity.groups.split(",").exists(g, g == "free")
      counters:
        - expression: auth.identity.userid
    oidc:
      rates:
        - limit: 50
          window: 1m
      when:
        - predicate: request.path == "/v1/chat/completions"
        - predicate: has(auth.identity.iss)
      counters:
        - expression: auth.identity.sub

Connection: TokenRateLimitPolicyHTTPRoute (ai-maas). Works with dual-auth AuthPolicy and PlanPolicy tiers (free, gold, oidc).

Chart: charts/all/workshop-kuadrant-apis/templates/policies.yaml

APIProduct

Purpose: Expose an HTTPRoute as an API entry in Developer Hub (Kuadrant DevPortal plugin).

metadata.name must exactly match the Backstage API entity name. The Kuadrant frontend plugin builds "View in Catalog" links as /catalog/default/api/<metadata.name>, not from documentation.docsURL. Example: neuroface-cv-openapi (not neuroface-cv or neuroface-cv-oidc).

apiVersion: devportal.kuadrant.io/v1alpha1
kind: APIProduct
metadata:
  name: neuroface-cv-openapi
  namespace: neuroface-gateway-system
spec:
  displayName: Computer Vision API
  description: YOLO PPE detection API protected by realm-cv OIDC JWTs.
  publishStatus: Published
  approvalMode: manual
  version: v1
  tags: [neuroface, cv, oidc, ppe, edge, gateway-api]
  documentation:
    docsURL: https://developer-hub.apps.cluster.example.com/catalog/default/api/neuroface-cv-openapi
  contact:
    email: platform@example.com
  targetRef:
    group: gateway.networking.k8s.io
    kind: HTTPRoute
    name: neuroface-cv-lb

Chart: charts/all/neuroface-gateway/templates/apiproducts.yaml

Kubernetes Gateway API

Documentation: Gateway API specification

Gateway

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: neuroface-gateway
  namespace: neuroface-gateway-system
spec:
  gatewayClassName: istio
  listeners:
    - name: http
      port: 8080
      protocol: HTTP

HTTPRoute

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: neuroface-app-lb
spec:
  parentRefs:
    - name: neuroface-gateway
  rules:
    - backendRefs:
        - name: neuroface-app-east
          port: 8080
          weight: 50
        - name: neuroface-app-west
          port: 8080
          weight: 50

Red Hat Build of Keycloak (RHBK)

Documentation: Red Hat Build of Keycloak

KeycloakRealmImport

Purpose: Declaratively create realms (cv, maas, neuroface) with users, clients, roles, and groups. Client secrets are injected via spec.placeholders referencing Kubernetes Secrets (sourced from Vault through ESO).

Chart: charts/all/rhbk-iam/templates/realm-import.yaml

apiVersion: k8s.keycloak.org/v2alpha1
kind: KeycloakRealmImport
metadata:
  name: realm-cv
  namespace: keycloak-system
spec:
  keycloakCRName: keycloak
  placeholders:
    CLIENT_SECRET_USER1:
      secret:
        name: keycloak-client-cv-user1
        key: clientSecret
  realm:
    realm: cv
    enabled: true
    displayName: Computer Vision
    verifyEmail: false
    requiredActions:
      - alias: VERIFY_PROFILE
        enabled: false
    clients:
      - clientId: client-cv-user1
        secret: $(CLIENT_SECRET_USER1)
        serviceAccountsEnabled: true
    users:
      - username: user1
        firstName: Workshop
        lastName: user1
        credentials:
          - type: password
            value: Welcome123!
            temporary: false
        clientRoles:
          realm-management:
            - view-clients
            - manage-clients

Connection: KeycloakRealmImportKeycloak CR → ExternalSecret (Vault client secrets).

External Secrets Operator

Documentation: External Secrets Operator

Purpose: Sync secrets from HashiCorp Vault into Kubernetes Secrets. Each ExternalSecret maps a Vault path to a K8s Secret key.

Chart: charts/all/rhbk-iam/templates/realm-import.yaml (generates ExternalSecrets for each client)

apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: keycloak-client-cv-user1
  namespace: keycloak-system
spec:
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  refreshInterval: 1h
  data:
    - secretKey: clientSecret
      remoteRef:
        key: secret/data/hub/keycloak/realms/cv/user1
        property: clientSecret
  target:
    name: keycloak-client-cv-user1
    creationPolicy: Owner

Red Hat Service Interconnect (Skupper)

Site

Purpose: Declare a cluster as part of the Skupper network.

apiVersion: skupper.io/v2alpha1
kind: Site
metadata:
  name: hub-site
  namespace: service-interconnect

Listener

Purpose: Expose a remote service locally on the hub (receives traffic from spoke Connectors).

apiVersion: skupper.io/v2alpha1
kind: Listener
metadata:
  name: neuroface-app-east
spec:
  routingKey: neuroface-app-east
  port: 8080

Connector

Purpose: Forward traffic from a spoke pod to the hub Listener.

apiVersion: skupper.io/v2alpha1
kind: Connector
metadata:
  name: neuroface-app-east
spec:
  routingKey: neuroface-app-east
  selector: app=neuroface-backend
  port: 8080

OpenShift Service Mesh (Istio ambient)

Documentation: OpenShift Service Mesh

Istio

apiVersion: sailoperator.io/v1alpha1
kind: Istio
metadata:
  name: default
  namespace: istio-system
spec:
  profile: ambient

Connection: Istio control plane → Gateway controller → ZTunnel data plane (ambient L4 mTLS and mesh telemetry).

Chart: VP servicemesh-config application (servicemesh chart, profile: ambient)

ZTunnel

Purpose: Deploy the ambient mesh data plane (ztunnel DaemonSet). Required for L4 mTLS and Istio metrics (istio_requests_total, istio_tcp_*) consumed by Kiali and Grafana.

apiVersion: sailoperator.io/v1
kind: ZTunnel
metadata:
  name: default
  namespace: istio-system
spec:
  namespace: ztunnel
  version: v1.28.8

Connection: ZTunnelztunnel namespace DaemonSet on every node. Without this CR, oc get istio default -n istio-system reports ZTunnelNotFound and mesh telemetry panels show no data.

Chart: charts/all/istio-ztunnel/templates/ztunnel.yaml

Red Hat Developer Hub

Documentation: Red Hat Developer Hub

Backstage CR

Purpose: Deploy and configure the Developer Hub instance. Catalog entities are mounted as ConfigMaps via spec.application.extraFiles.configMaps and registered in catalog.locations.

Chart: charts/all/developer-hub/templates/backstage-developer-hub.yaml

apiVersion: rhdh.redhat.com/v1alpha5
kind: Backstage
metadata:
  name: developer-hub
  namespace: developer-hub
spec:
  application:
    appConfig:
      configMaps:
        - name: app-config-rhdh
        - name: app-config-auth-rhdh
    dynamicPluginsConfigMapName: dynamic-plugins-rhdh
    extraFiles:
      configMaps:
        - name: developer-hub-catalog-users
          key: users.yaml
          mountPath: /opt/app-root/src/catalog-data
        - name: developer-hub-catalog-neuroface-cv
          key: neuroface-cv-system.yaml
          mountPath: /opt/app-root/src/catalog-data/neuroface-cv
        - name: developer-hub-catalog-iam-realms
          key: iam-realms.yaml
          mountPath: /opt/app-root/src/catalog-data/iam-realms
        - name: developer-hub-catalog-kuadrant-apis
          key: workshop-kuadrant-apis.yaml
          mountPath: /opt/app-root/src/catalog-data/kuadrant-apis
    route:
      enabled: true
      host: developer-hub.apps.<hub-domain>
  deployment:
    patch:
      spec:
        template:
          spec:
            serviceAccountName: developer-hub
            containers:
              - name: backstage-backend
                envFrom:
                  - secretRef:
                      name: developer-hub-oidc-auth
                volumeMounts:
                  - name: rhdh-rbac-policy
                    mountPath: /opt/app-root/src/rbac-policy.csv
                    subPath: rbac-policy.csv

Catalog ConfigMaps

ConfigMap Content

developer-hub-catalog-users

users.yaml — User and Group entities (user1..user30, group developers)

developer-hub-catalog-neuroface-cv

neuroface-cv-system.yaml — NeuroFace CV System, Components, APIs

developer-hub-catalog-neuroface

neuroface-system.yaml — NeuroFace spoke System and Components

developer-hub-catalog-iam-realms

iam-realms.yaml — IAM / OIDC Realms System (cv, maas, neuroface APIs)

developer-hub-catalog-kuadrant-apis

workshop-kuadrant-apis.yaml — Kuadrant workshop APIs (httpbin, MaaS, MCP, NeuroFace)

developer-hub-catalog-public-apis

public-apis.yaml — Public REST API examples (Petstore, httpbin, etc.)

developer-hub-catalog-rhdh-plugins

rhdh-plugins.yaml — Installed dynamic plugins inventory

developer-hub-catalog-software-templates

software-templates.yaml — Template manifests registry

Software Templates (Scaffolder)

Purpose: Self-service workflows in Developer Hub — deploy personal NeuroFace instances, create/revoke OIDC clients, and register components in the catalog.

Chart: charts/all/developer-hub/files/software-templates/

Template Purpose

ai-computer-vision

Scaffold a personal NeuroFace instance on east or west spoke (GitLab publish + catalog register)

oidc-credentials-self-service

Create an OIDC client in Keycloak realm cv for API access. Select planTier (free/gold) to embed a plan JWT claim for rate limits on /v1/predict (secret shown once on result page)

oidc-credentials-revoke

Delete a client created by the self-service template (same API + client label)

apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: ai-computer-vision
  title: "AI Computer Vision at the Edge"
spec:
  owner: platform-engineering
  type: service
  parameters:
    - title: AI CV instance
      properties:
        owner:
          title: Owner
          ui:field: OwnerPicker
        spoke:
          title: Target spoke
          type: string
          enum: [east, west]
  steps:
    - id: fetch
      name: Fetch skeleton
      action: fetch:template
    - id: publish
      name: Publish to GitLab
      action: publish:gitlab
    - id: register
      name: Register in catalog
      action: catalog:register

Red Hat OpenShift AI

Documentation: Red Hat OpenShift AI

InferenceService

Purpose: Serve ML models (YOLO PPE detection) via KServe with OpenVINO runtime.

apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: yolo-ppe-serving
spec:
  predictor:
    model:
      modelFormat:
        name: openvino
      storageUri: s3://models/yolo-ppe

OpenShift GitOps (Argo CD)

Documentation: Red Hat OpenShift GitOps

ApplicationSet

Purpose: Automatically generate Argo CD Application resources based on discovered GitLab repositories. In this workshop, an ApplicationSet watches the ws-workshop GitLab group and automatically deploys any repository matching the ^neuroface- prefix to the appropriate spoke cluster.

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: user-neuroface-apps
  namespace: vp-gitops
spec:
  generators:
    - matrix:
        generators:
          - scmProvider:
              gitlab:
                group: "ws-workshop"
                api: https://gitlab.apps.<hub-domain>/api/v4
              filters:
                - repositoryMatch: "^neuroface-"
          - git:
              repoURL: "https://gitlab.apps.<hub-domain>/ws-workshop/{{ '{{ .repository }}' }}.git"
              revision: HEAD
              files:
                - path: k8s/target-cluster.yaml
  template:
    metadata:
      name: "{{ '{{ .repository }}' }}"
    spec:
      project: ai
      source:
        repoURL: "https://gitlab.apps.<hub-domain>/ws-workshop/{{ '{{ .repository }}' }}.git"
        targetRevision: HEAD
        path: k8s
      destination:
        name: "{{ '{{ .cluster }}' }}"
        namespace: "{{ '{{ .repository }}' }}"
      syncPolicy:
        automated:
          prune: true
          selfHeal: true

Application

Purpose: A generated resource that manages the deployment of manifests from a Git repository to a specific target cluster namespace.

OpenShift Pipelines (Tekton)

Pipeline

Purpose: Defines a reusable series of Task resources that execute in a specific order. The scaffolded NeuroFace repositories include a Tekton pipeline that builds the backend container image using buildah.

apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
  name: neuroface-build
spec:
  params:
    - name: image-tag
      type: string
      default: latest
  workspaces:
    - name: source
    - name: dockerconfig
  tasks:
    - name: build-backend
      taskRef:
        kind: ClusterTask
        name: buildah
      params:
        - name: IMAGE
          value: image-registry.openshift-image-registry.svc:5000/$(context.pipelineRun.namespace)/neuroface-backend:$(params.image-tag)

PipelineRun

Purpose: Instantiates a Pipeline execution, binding it to specific workspaces (like PersistentVolumeClaims) and parameters.

OpenShift Virtualization

Not included in this pattern installation. OpenShift Virtualization / KubeVirt is not deployed by the AI Computer Vision Validated Pattern, and there is no cnv-vm-workshop software template in Developer Hub. The diagram above shows a generic edge reference architecture only.

Documentation (for future extensions): OpenShift Virtualization

VirtualMachine (reference only)

Purpose: Run legacy VM-based workloads alongside containers. Not provisioned by this workshop pattern.

# Reference example — not deployed by ia-computer-vision
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  name: my-vm-lab
spec:
  running: true
  template:
    spec:
      domain:
        cpu:
          cores: 1
        memory:
          guest: 512Mi

HashiCorp Vault / External Secrets

ClusterSecretStore

Purpose: Provides a cluster-wide connection definition for the External Secrets Operator to securely authenticate and fetch secrets from Vault.

apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: vault-backend
spec:
  provider:
    vault:
      server: "http://vault.vault.svc.cluster.local:8200"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "validatedPatternDefaultPolicy"

Full pattern reference

See the pattern documentation: CRD reference on GitHub Pages.

End of the lab guide

You reached the last module in the canonical workshop order. Return to Welcome or use the sidebar to revisit any module.