Skip to content

feat(mcp): add OAuth 2.1 authorization with JWT validation and RFC 9728 metadata #9818

feat(mcp): add OAuth 2.1 authorization with JWT validation and RFC 9728 metadata

feat(mcp): add OAuth 2.1 authorization with JWT validation and RFC 9728 metadata #9818

Workflow file for this run

name: Router CI
on:
pull_request:
paths:
- 'pnpm-lock.yaml'
- 'composition-go/**/*'
- 'demo/**/*'
- 'router/**/*'
- 'router-plugin/**/*'
- 'router-tests/**/*'
- 'connect/**/*'
- '.github/workflows/router-ci.yaml'
concurrency:
group: ${{github.workflow}}-${{github.head_ref}}
cancel-in-progress: true
permissions:
contents: read # for actions/checkout to fetch code
pull-requests: write # required for adding pull request comments
packages: write # required for publishing packages
env:
CI: true
ROUTER_REGISTRATION: false
DO_NOT_TRACK: '1'
# Both jobs need to kept in sync. We need to distinguish between forks and people with write access to the repository.
jobs:
# Runs for forks without access to repository secrets
build_test_fork:
if: github.event.pull_request.head.repo.full_name != github.repository
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/go
with:
cache-dependency-path: |
router/go.sum
router-tests/go.sum
demo/go.sum
- uses: ./.github/actions/go-mod-tidy
with:
working-directory: ./router
- name: Install tools
run: make setup-build-tools
- uses: ./.github/actions/node
- name: Install Cli Node Dependencies
run: |
pnpm --filter ./cli --filter ./connect --filter ./shared --filter ./composition --filter ./protographic install --frozen-lockfile
pnpm buf generate --template buf.ts.gen.yaml
pnpm --filter ./cli --filter ./connect --filter ./shared --filter ./composition --filter ./protographic run build
- name: Install Bun For Plugin Building
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.15
- name: Generate code
run: make generate-go
- name: Check if git is not dirty after generating files
run: git diff --no-ext-diff --exit-code
- name: Install dependencies
working-directory: ./router
run: go mod download
- name: Run linters on router-tests
uses: ./.github/actions/go-linter
with:
working-directory: ./router-tests
- name: Run linters on router
uses: ./.github/actions/go-linter
with:
working-directory: ./router
- name: Test
working-directory: ./router
run: make test
- name: Build
working-directory: ./router
run: make build
# Runs for forks to test docker image is building
build_image_fork:
if: github.event.pull_request.head.repo.full_name != github.repository
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
matrix:
docker_build_target: [ '', 'nonroot' ]
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/build-push-image
with:
docker_context: router
dockerfile: router/Dockerfile
docker_build_target: ${{ matrix.docker_build_target }}
token: ${{secrets.GITHUB_TOKEN}}
image_name: router
image_description: 'Cosmo Router'
push: 'false'
# Runs for people with write access to the repository
# that have access to the secrets
build_test:
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- name: Log in to Docker Container registry (With write access)
uses: docker/login-action@v3
with:
registry: docker.io
username: ${{secrets.DOCKER_USERNAME}}
password: ${{secrets.DOCKER_PASSWORD}}
- uses: ./.github/actions/go
with:
cache-dependency-path: |
router/go.sum
router-tests/go.sum
demo/go.sum
- uses: ./.github/actions/go-mod-tidy
with:
working-directory: ./router
- name: Install tools
run: make setup-build-tools
- uses: ./.github/actions/node
- name: Install Cli Node Dependencies
run: |
pnpm --filter ./cli --filter ./connect --filter ./shared --filter ./composition --filter ./protographic install --frozen-lockfile
pnpm buf generate --template buf.ts.gen.yaml
pnpm --filter ./cli --filter ./connect --filter ./shared --filter ./composition --filter ./protographic run build
- name: Install Bun For Plugin Building
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.15
- name: Generate code
run: make generate-go
- name: Check if git is not dirty after generating files
run: git diff --no-ext-diff --exit-code
- name: Install dependencies
working-directory: ./router
run: go mod download
- name: Run linters on router-tests
uses: ./.github/actions/go-linter
with:
working-directory: ./router-tests
- name: Run linters on router
uses: ./.github/actions/go-linter
with:
working-directory: ./router
- name: Test
working-directory: ./router
run: make test-coverage
- name: Build
working-directory: ./router
run: make build
- name: Upload integration results to Codecov
uses: ./.github/actions/codecov-upload-pr
with:
artifact-name: router-tests-coverage
coverage-path: router/coverage.out
retention-days: 14
codecov-token: ${{ secrets.CODECOV_TOKEN }}
integration_test:
runs-on: ubuntu-latest-l
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
test_target:
[
'./. ./fuzzquery ./lifecycle ./modules',
'./telemetry',
'./events',
]
services:
nats:
image: ghcr.io/wundergraph/cosmo/nats:2.11.0-alpine
options: >-
--health-cmd "curl -f http://localhost:8222/healthz"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 4222:4222
- 8222:8222
- 6222:6222
redis:
image: redis:7
# Set health checks to wait until redis has started
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
redis-0:
image: bitnamilegacy/redis-cluster:7.2
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
ALLOW_EMPTY_PASSWORD: "yes"
REDIS_NODES: "redis-0 redis-1 redis-2"
REDIS_CLUSTER_REPLICAS: "0"
REDIS_CLUSTER_CREATOR: "no"
ports:
- 7001:6379
redis-1:
image: bitnamilegacy/redis-cluster:7.2
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
ALLOW_EMPTY_PASSWORD: "yes"
REDIS_NODES: "redis-0 redis-1 redis-2"
REDIS_CLUSTER_REPLICAS: "0"
REDIS_CLUSTER_CREATOR: "no"
ports:
- 7002:6379
redis-2:
image: bitnamilegacy/redis-cluster:7.2
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
ALLOW_EMPTY_PASSWORD: "yes"
REDIS_NODES: "redis-0 redis-1 redis-2"
REDIS_CLUSTER_REPLICAS: "0"
REDIS_CLUSTER_CREATOR: "yes"
ports:
- 7003:6379
kafka:
image: bitnamilegacy/kafka:3.7.0
options: >-
--health-cmd "kafka-broker-api-versions.sh --version"
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
KAFKA_ENABLE_KRAFT: yes
KAFKA_CFG_PROCESS_ROLES: controller,broker
KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_CFG_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093
KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 1@localhost:9093
KAFKA_CFG_TRANSACTION_PARTITION_VERIFICATION_ENABLE: false
KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
KAFKA_CFG_NODE_ID: 1
ALLOW_PLAINTEXT_LISTENER: yes
KAFKA_KRAFT_CLUSTER_ID: XkpGZQ27R3eTl3OdTm2LYA # 16 byte base64-encoded UUID
ports:
- '9092:9092'
steps:
- name: Wait for Redis Cluster
run: |
echo "[CHECK] Waiting for Redis Cluster to become healthy..."
cluster_containers=$(docker ps --quiet --filter "ancestor=bitnamilegacy/redis-cluster:7.2")
success=0
for i in {1..30}; do
if [ $i -eq 1 ]; then
echo "[INIT] Forcing cluster creation..."
# pick one container as the "creator"
creator=$(echo $cluster_containers | awk '{print $1}')
# run the cluster create command inside it
docker exec "$creator" redis-cli --cluster create redis-0:6379 redis-1:6379 redis-2:6379 --cluster-replicas 0 --cluster-yes || true
fi
for cid in $cluster_containers; do
docker exec "$cid" redis-cli -p 6379 cluster info
if docker exec "$cid" redis-cli -p 6379 cluster info 2>/dev/null | grep -q "cluster_state:ok"; then
echo "[SUCCESS] Redis Cluster is ready (reported by $cid)"
success=1
break 2
fi
done
echo "[WAITING] Cluster not ready yet (attempt $i)..."
sleep 2
done
if [ $success -eq 0 ]; then
echo "[ERROR] Redis Cluster did not become healthy in time"
for cid in $cluster_containers; do
echo "--- Cluster info for $cid ---"
docker exec "$cid" redis-cli -p 6379 cluster info || true
docker exec "$cid" redis-cli -p 6379 cluster nodes || true
echo "--- Logs for $cid ---"
docker logs "$cid" | tail -n 100
done
exit 1
fi
- uses: actions/checkout@v4
- uses: ./.github/actions/go
with:
cache-dependency-path: |
router-tests/go.sum
- name: Install tools
run: make setup-build-tools
- uses: ./.github/actions/node
- name: Install Cli Node Dependencies
run: |
pnpm --filter ./cli --filter ./connect --filter ./shared --filter ./composition --filter ./protographic install --frozen-lockfile
pnpm buf generate --template buf.ts.gen.yaml
pnpm --filter ./cli --filter ./connect --filter ./shared --filter ./composition --filter ./protographic run build
- name: Install Bun For Plugin Building
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.15
- name: Install dependencies
working-directory: ./router-tests
run: go mod download
- name: Configure Redis Authentication & ACL
run: |
docker ps -a
# Set a password for each master node
for cid in $(docker ps --format "{{.ID}} {{.Image}}" | grep "redis-cluster" | awk '{print $1}'); do
echo "Configuring ACLs in container $cid"
docker exec "$cid" redis-cli -p 6379 ACL SETUSER cosmo on ">test" "~*" "+@all"
docker exec "$cid" redis-cli -p 6379 ping
done
cid=$(docker ps --format "{{.ID}} {{.Image}}" | grep "redis:7" | awk '{print $1}')
# Sanity checks
docker exec "$cid" redis-cli -p 6379 ping
docker exec "$cid" redis-cli -u "redis://cosmo:test@redis-0:6379" ping
docker exec "$cid" redis-cli -u "redis://cosmo:test@redis-0:6379" cluster nodes
- name: Run Integration tests ${{ matrix.test_target }}
working-directory: ./router-tests
run: make test-coverage test_retry_count=0 test_params="-run '^Test[^(Flaky)]' --timeout=5m -p 1 --parallel 10" test_target="${{ matrix.test_target }}"
- name: Compute artifact name for codecov upload
id: artifact_name
run: |
# Sanitize test target: remove './', replace spaces and special chars with underscores
target="${{ matrix.test_target }}"
sanitized=$(echo "$target" | sed 's|\.\/||g' | sed 's|[^a-zA-Z0-9]|_|g' | sed 's|_\+|_|g' | sed 's|^_||' | sed 's|_$||')
echo "sanitized=$sanitized" >> $GITHUB_OUTPUT
- name: Upload integration results to Codecov
uses: ./.github/actions/codecov-upload-pr
with:
artifact-name: router-${{ steps.artifact_name.outputs.sanitized }}-nonFlaky
coverage-path: router-tests/coverage.out
retention-days: 14
codecov-token: ${{ secrets.CODECOV_TOKEN }}
- name: Run Flaky Integration tests ${{ matrix.test_target }}
working-directory: ./router-tests
run: make test-coverage test_retry_count=3 test_params="-run '^TestFlaky' --timeout=5m -p 1 --parallel 10" test_target="${{ matrix.test_target }}"
- name: Upload flaky integration results to Codecov
uses: ./.github/actions/codecov-upload-pr
with:
artifact-name: router-${{ steps.artifact_name.outputs.sanitized }}-flaky
coverage-path: router-tests/coverage.out
retention-days: 14
codecov-token: ${{ secrets.CODECOV_TOKEN }}
image_scan:
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
matrix:
docker_build_target: [ '', 'nonroot' ]
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/build-push-image
id: docker_build
with:
docker_username: ${{secrets.DOCKER_USERNAME}}
docker_password: ${{secrets.DOCKER_PASSWORD}}
docker_context: router
dockerfile: router/Dockerfile
docker_build_target: ${{ matrix.docker_build_target }}
token: ${{secrets.GITHUB_TOKEN}}
image_name: router
image_description: 'Cosmo Router'
image_platforms: 'linux/amd64'
load_Image: 'true'
push: 'false'
- uses: ./.github/actions/image-scan
with:
name: ${{ matrix.docker_build_target != '' && format('Router-{0}', matrix.docker_build_target) || 'Router' }}
github_token: ${{secrets.GITHUB_TOKEN}}
image_ref: ${{ steps.docker_build.outputs.image_ref }}${{ matrix.docker_build_target != '' && format('-{0}', matrix.docker_build_target) || '' }}
build_push_image:
# This is a limitation of GitHub. Only organization members can push to GitHub Container Registry
# For now, we will disable the push to the GitHub Container Registry for external contributors
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
matrix:
docker_build_target: [ '', 'nonroot' ]
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/build-push-image
with:
docker_username: ${{secrets.DOCKER_USERNAME}}
docker_password: ${{secrets.DOCKER_PASSWORD}}
docker_context: router
dockerfile: router/Dockerfile
docker_build_target: ${{ matrix.docker_build_target }}
token: ${{secrets.GITHUB_TOKEN}}
image_name: router
image_description: 'Cosmo Router'