Skip to content

Identity and authorization

kcp is the enforcement point for Platform Mesh. Every API request flows through the front proxy, an authentication chain (global + per-workspace), and an authorization chain that ends with the rebac-authz-webhook calling OpenFGA.

See Identity and authorization for the conceptual model.

Primitives

PrimitivePlatform Mesh roleUpstream
kcp-front-proxySingle TLS-terminating ingress; all clients connect here.Front proxy
--authentication-configCluster-wide OIDC config (kcp flag, takes an AuthenticationConfiguration).Authentication
WorkspaceAuthenticationConfigurationPer-workspace OIDC config referenced by a WorkspaceType. Platform Mesh uses this to give each org its own Keycloak realm.Workspace authentication
--authorization-webhook-config-fileWires a SubjectAccessReview webhook into the kcp authorizer chain.Authorization
Built-in authorizer chainRBAC → required-groups → workspace-content → max-permission-policy → local/global/bootstrap. Webhook layers after.Authorization

Per-workspace OIDC

The WorkspaceAuthenticationConfiguration referenced by the orgs WorkspaceType (see Workspaces) defines the JWT issuer, audience, and claim mapping for a workspace subtree. Platform Mesh ships one per shared realm and one per organization (the security-operator provisions per-org configs dynamically):

yaml
# platform-mesh-operator/manifests/kcp/workspace-authentication-configuration.yaml
apiVersion: tenancy.kcp.io/v1alpha1
kind: WorkspaceAuthenticationConfiguration
metadata:
  name: orgs-authentication
spec:
  jwt:
    - issuer:
        url: https://{{ .baseDomainPort }}/keycloak/realms/welcome
        audiences:
        {{- range .welcomeAudiences }}
        - {{ . }}
        {{- end }}
        audienceMatchPolicy: MatchAny
        certificateAuthority: |
{{ .domainCADec | indent 10 }}
      claimMappings:
        groups:
          claim: groups
          prefix: ""
        username:
          claim: email
          prefix: ""

The security-operator composes per-org issuers from AccountInfo.Spec.OIDC.Clients:

go
// security-operator/internal/subroutine/workspace_authorization.go
audiences := make([]string, 0, len(accountInfo.Spec.OIDC.Clients)+len(r.cfg.AdditionalAudiences))
for clientName, clientInfo := range accountInfo.Spec.OIDC.Clients {
    audiences = append(audiences, clientInfo.ClientID)
}
audiences = append(audiences, r.cfg.AdditionalAudiences...)

jwtAuth := kcptenancyv1alphav1.JWTAuthenticator{
    Issuer: kcptenancyv1alphav1.Issuer{
        URL:       fmt.Sprintf("https://%s/keycloak/realms/%s", r.cfg.BaseDomain, workspaceName),
        Audiences: audiences,
    },
}

Authorizer chain

Platform Mesh ships an AuthorizationConfiguration that runs RBACNode → a webhook into rebac-authz-webhook. The match conditions skip RBAC group requests so the webhook does not have to re-authorize standard Kubernetes RBAC traffic:

yaml
# helm-charts/local-setup/webhook-config/authorization-webhook-config.yaml
apiVersion: apiserver.config.k8s.io/v1beta1
kind: AuthorizationConfiguration
authorizers:
  - { type: RBAC, name: rbac }
  - { type: Node, name: node }
  - type: Webhook
    name: authz-webhook
    webhook:
      authorizedTTL: 30s
      unauthorizedTTL: 30s
      timeout: 3s
      subjectAccessReviewVersion: v1
      matchConditionSubjectAccessReviewVersion: v1
      failurePolicy: NoOpinion
      matchConditions:
        - expression: has(request.resourceAttributes)
        - expression: request.resourceAttributes.group != "rbac.authorization.k8s.io"
      connectionInfo:
        type: KubeConfigFile
        kubeConfigFile: /config/authz-webhook-kubeconfig.yaml

rebac-authz-webhook wiring

The webhook runs as its own deployment. It connects to OpenFGA and watches a specific APIExportEndpointSlice so it can resolve workspace identity for incoming SubjectAccessReviews:

yaml
# helm-charts/charts/rebac-authz-webhook/templates/deployment.yaml (excerpt)
args:
  - "serve"
  - "--openfga-addr={{ .Values.openfga.url }}"
  - "--kcp-api-export-endpoint-slice-name={{ .Values.kcp.apiExportEndpointSliceName }}"
  - "--health-probe-bind-address={{ .Values.healthProbeBindAddress }}"
  - "--webhook-cache-miss-max-retries={{ .Values.webhook.cacheMissMaxRetries }}"
  - "--webhook-cache-miss-ttl={{ .Values.webhook.cacheMissTTL }}"
ports:
  - containerPort: 9443  # webhook server (SubjectAccessReview)
  - containerPort: 8080  # metrics

Maximal Permission Policy

APIExport.spec.maximalPermissionPolicy puts an upper bound on what consumers can do with the export's resources, even via the apis.kcp.io:binding: group prefix. Platform Mesh exposes this to providers but does not centrally drive it.

EU and German government funding logos

Funded by the European Union – NextGenerationEU.

The views and opinions expressed are solely those of the author(s) and do not necessarily reflect the views of the European Union or the European Commission. Neither the European Union nor the European Commission can be held responsible for them.