Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 18 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ traefik LoadBalancer 10.96.251.221 172.18.0.3 80:31415/TCP,443:31650/TC

#### 2.1 Option 1: Use pre-built images

There are pre-built images for all JAD apps available from [GHCR](https://github.com/eclipse-dataspace-hub/jad/packages) and the
There are pre-built images for all JAD apps available from [GHCR](https://github.com/eclipse-dataspace-hub/jad/packages)
and the
Connector Fabric Manager images are available from
the [CFM GitHub Repository](https://github.com/eclipse-cfm/cfm/packages). Those are tested and we
strongly recommend using them.
Expand Down Expand Up @@ -306,6 +307,11 @@ be put in place by running the REST requests in the `CFM - Provision Consumer` f
in the [Bruno collection](./requests/EDC-V%20Onboarding). Be sure to select the `"KinD Local"` environment in
Bruno.

Since we switched to token exchange for authentication, the requests in the Bruno collection have been updated to use
the `Authorization` with a fixed Bearer token. A token can be obtained by creating a service account token with
`kubectl` and running the token exchange flow. The script `scripts/token.sh` does this
automatically and print the access token. Run it and copy the token into the Bruno environment at collection level.

![bruno.png](docs/images/bruno.png)

Those requests can be run manually, one after the other, or via Bruno's "Run" feature. It may be necessary to manually
Expand Down Expand Up @@ -425,20 +431,20 @@ unauthenticated at the gateway level.

### Application routes (`jad.localhost`)

| Service | Exposed path | Rewrites to | Backend port | Auth middleware |
|---------------------|---------------------|-------------------------|--------------|----------------------------------------|
| Control Plane | `/api/management` | `/api/mgmt` | `8081` | `jwt-auth-management-api` |
| Service | Exposed path | Rewrites to | Backend port | Auth middleware |
|---------------------|---------------------|------------------------|--------------|----------------------------------------|
| Control Plane | `/api/management` | `/api/mgmt` | `8081` | `jwt-auth-management-api` |
| Identity Hub | `/api/identity` | `/api/identity/v1beta` | `7081` | `jwt-auth-identity-api` |
| Issuer Service | `/api/issuer/admin` | `/api/admin/v1beta` | `10013` | `jwt-auth-issuer-admin-api` |
| Provision Manager | `/api/pm` | `/api/v1beta` | `8080` | `jwt-auth-provision-manager-api` |
| Tenant Manager | `/api/tm` | `/api/v1alpha1` | `8080` | `jwt-auth-tenant-manager-api` |
| Dataplane (public) | `/api/dp/public` | `/` | `11002` | — |
| Dataplane (control) | `/api/dp/control` | `/` | `8083` | — |
| Dataplane (certs) | `/api/dp/certs` | `/` | `8186` | — |
| Siglet | `/api/siglet` | `/` | `8080` | — |
| Redline | `/redline` | `/` | `8081` | — |
| Keycloak | `/auth` | `/` | `8080` | — (is the auth server) |
| Web UI | `/ui` | `/` | `80` | — (obtains its own token via Keycloak) |
| Tenant Manager | `/api/tm` | `/api/v1alpha1` | `8080` | `jwt-auth-tenant-manager-api` |
| Dataplane (public) | `/api/dp/public` | `/` | `11002` | — |
| Dataplane (control) | `/api/dp/control` | `/` | `8083` | — |
| Dataplane (certs) | `/api/dp/certs` | `/` | `8186` | — |
| Siglet | `/api/siglet` | `/` | `8080` | — |
| Redline | `/redline` | `/` | `8081` | — |
| Keycloak | `/auth` | `/` | `8080` | — (is the auth server) |
| Web UI | `/ui` | `/` | `80` | — (obtains its own token via Keycloak) |

### Auth middleware scopes

Expand Down
72 changes: 72 additions & 0 deletions scripts/token.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
#
# Copyright (c) 2026 Metaform Systems, Inc.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License, Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
#
# Contributors:
# Metaform Systems, Inc. - initial API and implementation
#
#

#
# Mint a Kubernetes SA token, exchange it (RFC 8693) at the IDP, print the
# resulting access token to stdout.
#
# Usage:
# KUBE_SA=my-sa KUBE_NS=my-ns KUBE_AUDIENCE=<idp-subject-audience> \
# IDP_TOKEN_ENDPOINT=https://idp.example.com/oauth2/token \
# IDP_AUDIENCE=<audience-the-ingress-expects> \
# ./get-access-token.sh
#
# # capture it:
# TOKEN="$(./get-access-token.sh)"
# curl -H "Authorization: Bearer $TOKEN" https://your-ingress/...
#
set -euo pipefail

# ---- config (override via env) -------------------------------------------
: "${KUBE_SA:=redline}"
: "${KUBE_NS:=edc-v}"
: "${KUBE_AUDIENCE:=https://kubernetes.default.svc.cluster.local}" # audience the IDP expects on the subject token
: "${KUBE_TOKEN_DURATION:=1800s}"

: "${IDP_TOKEN_ENDPOINT:=http://jad.localhost/api/auth/token}"
: "${IDP_AUDIENCE:=edcv}" # audience the ingress expects
: "${IDP_SCOPE:=admin cfm-write cfm-read read write}"

# ---- 1. mint the SA subject token ----------------------------------------
kubectl_args=(create token "$KUBE_SA" -n "$KUBE_NS" --duration "$KUBE_TOKEN_DURATION")
if [ -n "$KUBE_AUDIENCE" ]; then kubectl_args+=(--audience "$KUBE_AUDIENCE"); fi

SUBJECT_TOKEN="$(kubectl "${kubectl_args[@]}")"

# ---- 2. RFC 8693 token exchange ------------------------------------------
curl_args=(
--silent --show-error --fail-with-body
--request POST "$IDP_TOKEN_ENDPOINT"
--header "Content-Type: application/x-www-form-urlencoded"
--data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange"
--data-urlencode "subject_token=${SUBJECT_TOKEN}"
--data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:jwt"
--data-urlencode "resource=redline"
--data-urlencode "audience=${IDP_AUDIENCE}"
Comment thread
wolf4ood marked this conversation as resolved.
--data-urlencode "scope=${IDP_SCOPE}"
)

RESPONSE="$(curl "${curl_args[@]}")"

# ---- 3. extract + print access_token -------------------------------------
ACCESS_TOKEN="$(printf '%s' "$RESPONSE" | jq -r '.access_token // empty')"

if [ -z "$ACCESS_TOKEN" ]; then
echo "error: no access_token in IDP response:" >&2
printf '%s\n' "$RESPONSE" >&2
exit 1
fi

printf '%s\n' "$ACCESS_TOKEN"
Loading