1
0
mirror of https://github.com/quay/quay.git synced 2026-01-26 06:21:37 +03:00
Files
quay/plans/oidc_pkce_validation.md
Dave O'Connor 5b7ba94bc1 docs(oidc): add PKCE implementation and validation documentation (PROJQUAY-9281) (#4258)
docs(oidc): add PKCE implementation and validation documentation

- Add plans/oidc_pkcd.md with phased implementation plan
- Add plans/oidc_pkce_validation.md with QA step-by-step local validation (Keycloak + Quay)

Documentation includes:
- Implementation phases and considerations
- Local development setup with Keycloak
- Step-by-step validation procedures
- Configuration examples and troubleshooting

Topic: pkce-docs
2025-09-29 13:12:42 -04:00

6.5 KiB
Raw Permalink Blame History

OIDC + PKCE Validation Guide (Local QA)

This guide validates Quays OIDC login both without PKCE and with PKCE enforced.

Prereqs

  • Podman installed
  • Quay repo checked out and local-dev stack available

Conventions

  • Quay UI: http://localhost:8080
  • Keycloak: http://localhost:8081
  • Podman host alias used by Quay container: http://host.containers.internal:8081

1) Start services

  1. Start Quay local-dev (uses podman):
    DOCKER=podman make local-dev-up-static
    
  2. Start Keycloak (dev mode):
    podman rm -f quay-keycloak >/dev/null 2>&1 || true
    podman run -d --name quay-keycloak \
      -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin \
      -p 8081:8080 quay.io/keycloak/keycloak:latest start-dev
    # Wait until ready
    until curl -sSf http://localhost:8081/realms/master/.well-known/openid-configuration >/dev/null; do sleep 2; done
    

2) Configure Keycloak realm and client (no PKCE yet)

  1. Create realm quay and OIDC client quay-ui (confidential, standard flow):
    KC_TOKEN=$(curl -s -X POST -d 'client_id=admin-cli' -d 'username=admin' -d 'password=admin' -d 'grant_type=password' \
      http://localhost:8081/realms/master/protocol/openid-connect/token | jq -r .access_token)
    
    # Realm
    curl -s -X POST -H "Authorization: Bearer $KC_TOKEN" -H 'Content-Type: application/json' \
      http://localhost:8081/admin/realms -d '{"realm":"quay","enabled":true}'
    
    # Client (allow redirect back to Quay)
    curl -s -X POST -H "Authorization: Bearer $KC_TOKEN" -H 'Content-Type: application/json' \
      http://localhost:8081/admin/realms/quay/clients -d '{
        "clientId":"quay-ui","protocol":"openid-connect","publicClient":false,
        "standardFlowEnabled":true,
        "redirectUris":["http://localhost:8080/oauth2/someoidc/callback*"],
        "webOrigins":["+"]
      }'
    
    CID=$(curl -s -H "Authorization: Bearer $KC_TOKEN" 'http://localhost:8081/admin/realms/quay/clients?clientId=quay-ui' | jq -r '.[0].id')
    CLIENT_SECRET=$(curl -s -H "Authorization: Bearer $KC_TOKEN" http://localhost:8081/admin/realms/quay/clients/$CID/client-secret | jq -r .value)
    echo "CLIENT_SECRET=$CLIENT_SECRET"
    
  2. Create a test user:
    # Create user if not present
    curl -s -X POST -H "Authorization: Bearer $KC_TOKEN" -H 'Content-Type: application/json' \
      http://localhost:8081/admin/realms/quay/users -d '{"username":"quayuser","enabled":true,"email":"quayuser@example.com","emailVerified":true}'
    KC_UID=$(curl -s -H "Authorization: Bearer $KC_TOKEN" 'http://localhost:8081/admin/realms/quay/users?username=quayuser' | jq -r '.[0].id')
    curl -s -X PUT -H "Authorization: Bearer $KC_TOKEN" -H 'Content-Type: application/json' \
      http://localhost:8081/admin/realms/quay/users/$KC_UID/reset-password -d '{"type":"password","value":"password","temporary":false}'
    

3) Configure Quay OIDC provider (no PKCE yet)

Edit local-dev/stack/config.yaml and add:

SOMEOIDC_LOGIN_CONFIG:
  SERVICE_NAME: "Keycloak"
  OIDC_SERVER: "http://host.containers.internal:8081/realms/quay/"  # note trailing slash
  CLIENT_ID: "quay-ui"
  CLIENT_SECRET: "<CLIENT_SECRET_FROM_ABOVE>"
  LOGIN_SCOPES: ["openid", "profile", "email"]
  DEBUGGING: true

Restart Quay:

podman restart quay-quay
sleep 8

Validate baseline (no PKCE):

  • Browser: go to http://localhost:8080, click “Sign in with Keycloak”, log in as quayuser / password.
  • Expected: Login succeeds; user is logged into Quay.

Optional API check to retrieve auth URL:

CSRF=$(curl -s -c cookies.txt -b cookies.txt http://localhost:8080/csrf_token | jq -r .csrf_token)
curl -s -b cookies.txt -c cookies.txt -H "X-Requested-With: XMLHttpRequest" -H "X-CSRF-Token: $CSRF" \
  -H "Content-Type: application/json" -X POST -d '{"kind":"login"}' \
  http://localhost:8080/api/v1/externallogin/someoidc | jq -r .auth_url

4) Enforce PKCE on the IdP and confirm failure

  1. Require PKCE (S256) on Keycloak client:
    KC_TOKEN=$(curl -s -X POST -d 'client_id=admin-cli' -d 'username=admin' -d 'password=admin' -d 'grant_type=password' \
      http://localhost:8081/realms/master/protocol/openid-connect/token | jq -r .access_token)
    CID=$(curl -s -H "Authorization: Bearer $KC_TOKEN" 'http://localhost:8081/admin/realms/quay/clients?clientId=quay-ui' | jq -r '.[0].id')
    curl -s -H "Authorization: Bearer $KC_TOKEN" http://localhost:8081/admin/realms/quay/clients/$CID > /tmp/client.json
    cat /tmp/client.json | jq '.attributes["pkce.code.challenge.method"]="S256" | .attributes["oauth.pkce.required"]="true"' > /tmp/client-upd.json
    curl -s -X PUT -H "Authorization: Bearer $KC_TOKEN" -H 'Content-Type: application/json' \
      --data-binary @/tmp/client-upd.json http://localhost:8081/admin/realms/quay/clients/$CID
    
  2. Browser: log out of Quay if logged in, then try “Sign in with Keycloak”.
    • Expected: Login fails with PKCE error on Keycloak (e.g., missing code_challenge_method).

5) Enable PKCE in Quay and confirm success

  1. Edit local-dev/stack/config.yaml to enable PKCE:
    SOMEOIDC_LOGIN_CONFIG:
      ...
      USE_PKCE: true
      PKCE_METHOD: "S256"
    
  2. Restart Quay:
    podman restart quay-quay
    sleep 8
    
  3. Confirm the generated auth URL includes PKCE parameters:
    CSRF=$(curl -s -c cookies.txt -b cookies.txt http://localhost:8080/csrf_token | jq -r .csrf_token)
    AUTHURL=$(curl -s -b cookies.txt -c cookies.txt -H "X-Requested-With: XMLHttpRequest" -H "X-CSRF-Token: $CSRF" \
      -H "Content-Type: application/json" -X POST -d '{"kind":"login"}' http://localhost:8080/api/v1/externallogin/someoidc | jq -r .auth_url)
    echo "$AUTHURL" | grep -E 'code_challenge=.*&code_challenge_method=S256'
    
  4. Browser: “Sign in with Keycloak” again, log in as quayuser / password.
    • Expected: Login succeeds (PKCE working end-to-end).

Expected outcomes summary

  • No PKCE: login succeeds.
  • IdP PKCE required, Quay PKCE disabled: login fails with PKCE error.
  • IdP PKCE required, Quay PKCE enabled: auth URL has code_challenge + code_challenge_method=S256, login succeeds.

Troubleshooting notes

  • Ensure OIDC_SERVER ends with a trailing / (required by Quay).
  • With podman, Quay must reach Keycloak via host.containers.internal:8081 (not localhost).
  • Initial 502s from Quay are normal while warming up; wait ~510 seconds.
  • Local HTTP causes Keycloak non-secure cookie warnings; safe to ignore during dev.
  • Ignore unrelated securityworker log errors in Quay during this validation.