goharbor / harbor

An open source trusted cloud native registry project that stores, signs, and scans content.
https://goharbor.io
Apache License 2.0
24.3k stars 4.77k forks source link

OIDC - Keycloak - Login Error #21259

Open marcossilvestrini opened 4 days ago

marcossilvestrini commented 4 days ago

I'm having trouble using Keycloak as authentication in my Harbor. I deployed it with this values(chart release: 1.16.0):

# https://github.com/goharbor/harbor-helm/blob/main/values.yaml
harbor:
  expose:
    type: ingress
    tls:
      enabled: true
      certSource: secret
      auto:
        commonName: harbor.local
      secret:
        secretName: "harbor-tls"
    ingress:
      controller: default
      className: "nginx"
      hosts:
        core: harbor.local
      harbor:
        annotations: {}
        labels: {}
      annotations:
        cert-manager.io/cluster-issuer: letsencrypt-prod
        ingress.kubernetes.io/ssl-redirect: "true"
        ingress.kubernetes.io/proxy-body-size: "0"
        nginx.ingress.kubernetes.io/ssl-redirect: "true"
        nginx.ingress.kubernetes.io/proxy-body-size: "0"
        nginx.ingress.kubernetes.io/server-snippet: |
          if ($request_uri ~* ^/v2/([a-zA-Z0-9]+)/(manifests|blobs|tags)/?(.*)$) {
            set $dockerio_extra_project "/library";
          }

    loadBalancer:
      name: harbor
      IP: ""
      ports:
        httpPort: 80
        httpsPort: 443
      annotations: {}
      sourceRanges: []
  externalURL: https://harbor.local
  persistence:
    enabled: true
    resourcePolicy: "keep"
    persistentVolumeClaim:
      registry:
        storageClass: ""
        accessMode: ReadWriteOnce
        size: 50Gi
      jobservice:
        jobLog:
          storageClass: ""
          accessMode: ReadWriteOnce
          size: 10Gi
      database:
        storageClass: ""
        accessMode: ReadWriteOnce
        size: 10Gi
      redis:
        storageClass: ""
        accessMode: ReadWriteOnce
        size: 10Gi
      trivy:
        storageClass: ""
        accessMode: ReadWriteOnce
        size: 10Gi
    imageChartStorage:
      disableredirect: false
      type: filesystem
      filesystem:
        rootdirectory: /storage
  existingSecretAdminPasswordKey: HARBOR_ADMIN_PASSWORD
  harborAdminPassword: <path:harbor/data/cluster#HARBOR_ADMIN_PASS>
  ipFamily:
    ipv6:
      enabled: false
    ipv4:
      enabled: true
  updateStrategy:
    type: "Recreate"
  logLevel: debug
  secretKey: <path:harbor/data/cluster#HARBOR_ADMIN_PASS>
  existingSecretSecretKey: ""
  proxy:
    httpProxy:
    httpsProxy:
    noProxy: 127.0.0.1,localhost,.local,.internal
    components:
      - core
      - jobservice
      - trivy
  metrics:
    enabled: true
    core:
      path: /metrics
      port: 8001
    registry:
      path: /metrics
      port: 8001
    jobservice:
      path: /metrics
      port: 8001
    exporter:
      path: /metrics
      port: 8001

    serviceMonitor:
      enabled: true
      additionalLabels: {}
      interval: ""
      metricRelabelings: []
  cache:
    enabled: true
    expireHours: 730
  nginx:
    image:
      repository: goharbor/nginx-photon
      tag: v2.12.0
    replicas: 1

  portal:
    image:
      repository: goharbor/harbor-portal
      tag: v2.12.0
    replicas: 1

  core:
    image:
      repository: goharbor/harbor-core
      tag: v2.12.0
    replicas: 1

  jobservice:
    image:
      repository: goharbor/harbor-jobservice
      tag: v2.12.0
    replicas: 1

  registry:
    registry:
      image:
        repository: goharbor/registry-photon
        tag: v2.12.0
    controller:
      image:
        repository: goharbor/harbor-registryctl
        tag: v2.12.0
    replicas: 1

  trivy:
    enabled: true
    image:
      repository: goharbor/trivy-adapter-photon
      tag: v2.12.0
    replicas: 1
    debugMode: false
    vulnType: "os,library"
    severity: "UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL"
    ignoreUnfixed: false
    insecure: false
    securityCheck: "vuln"
    timeout: 5m0s

  database:
    type: internal
    internal:
      image:
        repository: goharbor/harbor-db
        tag: v2.12.0
      password: <path:harbor/data/cluster#HARBOR_POSTGRES_PASSWORD>
      shmSizeLimit: 512Mi
    external:
      host: "192.168.0.1"
      port: "5432"
      username: "user"
      password: "password"
      coreDatabase: "registry"
      existingSecret: ""
      sslmode: "disable"
    maxIdleConns: 100
    maxOpenConns: 900

  redis:
    type: internal
    internal:
      image:
        repository: goharbor/redis-photon
        tag: v2.12.0

  exporter:
    replicas: 1
    revisionHistoryLimit: 10
    image:
      repository: goharbor/harbor-exporter
      tag: v2.12.0
    cacheDuration: 23
    cacheCleanInterval: 14400
    priorityClassName:

My harbor oidc configurations:

OIDC Provider Name 
keycloak

OIDC Endpoint 
https://keycloak.local/realms/production

OIDC Client ID
harbor

OIDC Client Secret
foobarbeer

OIDC Scope 
openid,profile,email,offline_access

Verify Certificate 
true

My keycloak client:

{
  "clientId": "harbor",
  "name": "harbor",
  "description": "Harbor client",
  "rootUrl": "https://harbor.local",
  "adminUrl": "https://harbor.local",
  "baseUrl": "https://harbor.local",
  "surrogateAuthRequired": false,
  "enabled": true,
  "alwaysDisplayInConsole": false,
  "clientAuthenticatorType": "client-secret",
  "secret": "my-secret-here",
  "redirectUris": [
    "https://harbor.local/c/oidc/callback"
  ],
  "webOrigins": [
    "https://harbor.local"
  ],
  "notBefore": 0,
  "bearerOnly": false,
  "consentRequired": false,
  "standardFlowEnabled": true,
  "implicitFlowEnabled": false,
  "directAccessGrantsEnabled": true,
  "serviceAccountsEnabled": true,
  "publicClient": false,
  "frontchannelLogout": true,
  "protocol": "openid-connect",
  "attributes": {
    "oidc.ciba.grant.enabled": "false",
    "client.secret.creation.time": "1732804243",
    "backchannel.logout.session.required": "true",
    "oauth2.device.authorization.grant.enabled": "false",
    "backchannel.logout.revoke.offline.tokens": "false"
  },
  "authenticationFlowBindingOverrides": {},
  "fullScopeAllowed": true,
  "nodeReRegistrationTimeout": -1,
  "protocolMappers": [
    {
      "name": "Client Host",
      "protocol": "openid-connect",
      "protocolMapper": "oidc-usersessionmodel-note-mapper",
      "consentRequired": false,
      "config": {
        "user.session.note": "clientHost",
        "id.token.claim": "true",
        "introspection.token.claim": "true",
        "access.token.claim": "true",
        "claim.name": "clientHost",
        "jsonType.label": "String"
      }
    },
    {
      "name": "Client ID",
      "protocol": "openid-connect",
      "protocolMapper": "oidc-usersessionmodel-note-mapper",
      "consentRequired": false,
      "config": {
        "user.session.note": "client_id",
        "id.token.claim": "true",
        "introspection.token.claim": "true",
        "access.token.claim": "true",
        "claim.name": "client_id",
        "jsonType.label": "String"
      }
    },
    {
      "name": "Client IP Address",
      "protocol": "openid-connect",
      "protocolMapper": "oidc-usersessionmodel-note-mapper",
      "consentRequired": false,
      "config": {
        "user.session.note": "clientAddress",
        "id.token.claim": "true",
        "introspection.token.claim": "true",
        "access.token.claim": "true",
        "claim.name": "clientAddress",
        "jsonType.label": "String"
      }
    }
  ],
  "defaultClientScopes": [
    "web-origins",
    "acr",
    "address",
    "profile",
    "roles",
    "offline_access",
    "organization",
    "harbor",
    "groups",
    "microprofile-jwt",
    "basic",
    "email"
  ],
  "optionalClientScopes": [
    "phone"
  ],
  "access": {
    "view": true,
    "configure": true,
    "manage": true
  }
}

Firefox error 400 :

'oauth2: "unauthorized_client" "Invalid client or Invalid client credentials"'

url:

https://harbor.local/c/oidc/callback?state=VtwYOjAIc3UOoMys0dc27bvwu9d73j09&session_state=fab50f9b-d4af-4757-a1cf-2c24f92636b0&iss=https%3A%2F%2Fkeycloak.local%2Frealms%2Fproduction&code=foobarbeer

Test with curl(I modify keycloak client, parameter Use refresh tokens for this test):

curl -X POST \
  https://keycloak.local/realms/production/protocol/openid-connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=foobarbeer" \
  -d "redirect_uri=https://harbor.local/c/oidc/callback" \
  -d "client_id=harbor" \
  -d "client_secret=foobarbeer"

Payload result:

{
    "access_token": "foobarbeer",
    "expires_in": 300,
    "refresh_expires_in": 0,
    "token_type": "Bearer",
    "id_token": "foobarbeer",
    "not-before-policy": 0,
    "session_state": "fab50f9b-d4af-4757-a1cf-2c24f92636b0",
    "scope": "openid address profile email microprofile-jwt groups organization"
}

access_token decrypted:

{
  "exp": 1732807056,
  "iat": 1732806756,
  "auth_time": 1732806690,
  "jti": "fooobarbeer",
  "iss": "https://keycloak.local/realms/production",
  "aud": [
    "realm-management",
    "broker",
    "account"
  ],
  "sub": "foobarberr",
  "typ": "Bearer",
  "azp": "harbor",
  "sid": "fooooooo-d4af-4757-a1cf-2c24f92636b0",
  "acr": "1",
  "allowed-origins": [
    "https://harbor.local"
  ],
  "realm_access": {
    "roles": [
      "default-roles-production"
    ]
  },
  "resource_access": {
    "realm-management": {
      "roles": [
        "view-identity-providers",
        "view-realm",
        "manage-identity-providers",
        "impersonation",
        "realm-admin",
        "create-client",
        "manage-users",
        "query-realms",
        "view-authorization",
        "query-clients",
        "query-users",
        "manage-events",
        "manage-realm",
        "view-events",
        "view-users",
        "view-clients",
        "manage-authorization",
        "manage-clients",
        "query-groups"
      ]
    },
    "broker": {
      "roles": [
        "read-token"
      ]
    },
    "account": {
      "roles": [
        "manage-account",
        "view-applications",
        "view-consent",
        "view-groups",
        "manage-account-links",
        "manage-consent",
        "delete-account",
        "view-profile"
      ]
    }
  },
  "scope": "openid address profile email microprofile-jwt groups organization",
  "group_membership": [
    "admin",
    "architects"
  ],
  "upn": "marcos_roberto",
  "email_verified": false,
  "address": {},
  "name": "Marcos Roberto",
  "groups": [
    "default-roles-production",
    "default-roles-production",
    "default-roles-production"
  ],
  "preferred_username": "marcos_roberto",
  "given_name": "Marcos",
  "family_name": "Roberto"
}

harbor-core logs:

harbor-core-f7bcf4cb7-6vj7p core 2024-11-28T14:59:21Z [DEBUG] [/server/middleware/artifactinfo/artifact_info.go:55]: In artifact info middleware, url: /c/oidc/callback?state=J7LMWgW0NzZyoCz5BYwovOTZmCBH79t7&session_state=87ef2719-8876-4fa2-9993-906964fd6ce3&iss=https%3A%2F%2Fkeycloak.local%2Frealms%2Fproduction&code=foobarbeer
harbor-core-f7bcf4cb7-6vj7p core 2024-11-28T14:59:21Z [DEBUG] [/server/middleware/security/unauthorized.go:28][requestID="a93fa8bb010492c8d07ecf59309292a2"]: an unauthorized security context generated for request GET /c/oidc/callback
harbor-core-f7bcf4cb7-6vj7p core 2024-11-28T14:59:21Z [ERROR] [/core/controllers/oidc.go:118]: Failed to exchange token, error: oauth2: "unauthorized_client" "Invalid client or Invalid client credentials"
harbor-core-f7bcf4cb7-6vj7p core 2024-11-28T14:59:21Z [DEBUG] [/lib/http/error.go:62]: {"errors":[{"code":"BAD_REQUEST","message":"oauth2: \"unauthorized_client\" \"Invalid client or Invalid client credentials\""}]}

keycloak logs:

2024-11-28 14:59:21,746 WARN  [org.keycloak.events] (executor-thread-11) type="CODE_TO_TOKEN_ERROR", realmId="f3e2fad5-4b31-4689-a860-9d20847dd772", realmName="production", clientId="harbor", userId="null", ipAddress="192.168.4.240", error="invalid_client_credentials", grant_type="authorization_code"
2024-11-28 14:59:21,779 WARN  [org.keycloak.events] (executor-thread-10) type="CODE_TO_TOKEN_ERROR", realmId="f3e2fad5-4b31-4689-a860-9d20847dd772", realmName="production", clientId="harbor", userId="null", ipAddress="192.168.4.240", error="invalid_client_credentials", grant_type="authorization_code"

I've tried everything, but I can't get OIDC to work on my Harbor. I use Keycloak with all other apps like ArgoCD, Vault, Grafana, etc., but it doesn't work with Harbor.

reasonerjt commented 1 day ago

@marcossilvestrini I think the error message is clear Invalid client or Invalid client credentials Please double-check the client ID and client secret is correct.

I've tried everything, but I can't get OIDC to work on my Harbor. I use Keycloak with all other apps like ArgoCD, Vault, Grafana, etc., but it doesn't work with Harbor.

This is likely b/c they are using different clients and you misconfigured in Harbor.