DoodleScheduling / keycloak-controller

Keycloak realm reconciliation for kubernetes
Apache License 2.0
5 stars 0 forks source link

Client Authentication cannot be enabled for OIDC client through KeycloakClient custom resource #162

Closed bvegso closed 9 months ago

bvegso commented 9 months ago

Describe the bug

When I deploy the controller and create a KeycloakRealm and a KeycloakClient for that realm, if Client Authentication needs to be enabled, I have to specify client.spec.publicClient: false. This gets translated into a REST API request containing "publicClient":true in the end and Client Authentication is not enabled. I found the reason to be that in keycloak-controller, publicClient is omitted from the generated realm.json because it is boolean false, and the property has omitempty specified: https://github.com/DoodleScheduling/keycloak-controller/blob/0eec656cc41362d6e026622515604de54ef7a546/api/v1beta1/keycloakclient_types.go#L133C4-L133C4

Note: latest tag is used for adorsys/keycloak-config-cli bacause latest-23.0.3 does not exist. At the time of writing, latest has digest 73a3369546ac (which is latest-23.0.1)

Note: the client was created on the Keycloak 23.0.3 UI and exported as JSON, converted to YAML and added to the KeyclockClient resource descriptor.

The custom resources:

apiVersion: keycloak.infra.doodle.com/v1beta1
kind: KeycloakRealm
metadata:
  name: test-realm
spec:
  address: http://example-kc-service.idp:8080 # example on website includes /auth postfix which is not needed as of keycloak 19.x
  authSecret:
    name: keycloak-controller-user
  interval: 10m
  reconcilerTemplate:
    spec:
      containers:
      - name: keycloak-config-cli
        image: adorsys/keycloak-config-cli:latest # there is no 23.0.3 image
  resourceSelector:
    matchLabels:
      realm: test-realm
  realm:
    id: "test-realm"
    realm: "test-realm"
    enabled: true
    displayName: "Test Realm"
---
apiVersion: keycloak.infra.doodle.com/v1beta1
kind: KeycloakClient
metadata:
  name: kc-client-example
  labels:
    realm: test-realm
spec:
  client:
    clientId: test2-client-secret
    name: test4-client-secret
    description: ""
    rootUrl: ""
    adminUrl: ""
    baseUrl: ""
    surrogateAuthRequired: false
    enabled: true
    alwaysDisplayInConsole: true
    clientAuthenticatorType: client-secret
    secret: "${secret:keycloak-client-secret-test2-client-secret:secret}" # from a secret
    redirectUris: []
    webOrigins: []
    notBefore: 0
    bearerOnly: false
    consentRequired: false
    standardFlowEnabled: true
    implicitFlowEnabled: false
    directAccessGrantsEnabled: true
    serviceAccountsEnabled: false
    publicClient: false                          # <--------- here
    frontchannelLogout: false
    protocol: openid-connect
    attributes:
      oauth2.device.authorization.grant.enabled: "false"
      backchannel.logout.revoke.offline.tokens: "false"
      use.refresh.tokens: "true"
      oidc.ciba.grant.enabled: "false"
      backchannel.logout.session.required: "true"
      client_credentials.use_refresh_token: "false"
      acr.loa.map: "{}"
      require.pushed.authorization.requests: "false"
      tls.client.certificate.bound.access.tokens: "false"
      display.on.consent.screen: "false"
      token.response.type.bearer.lower-case: "false"
    authenticationFlowBindingOverrides: {}
    fullScopeAllowed: true
    nodeReRegistrationTimeout: -1
    defaultClientScopes:
      - web-origins
      - acr
      - profile
      - roles
      - email
    optionalClientScopes:
      - address
      - phone
      - offline_access
      - microprofile-jwt
    access:
      view: true
      configure: true
      manage: true

In the reconciler pod, the generated realm.json does not have the publicClient property, as expected, in line with omitempty:

{
  "id": "test-realm",
  "realm": "test-realm",
  "enabled": true,
  "displayName": "Test Realm",
  "clients": [
    {
      "clientId": "test2-client-secret",
      "name": "test4-client-secret",
      "enabled": true,
      "clientAuthenticatorType": "client-secret",
      "secret": "verysecret",
      "standardFlowEnabled": true,
      "directAccessGrantsEnabled": true,
      "protocol": "openid-connect",
      "attributes": {
        "acr.loa.map": "{}",
        "backchannel.logout.revoke.offline.tokens": "false",
        "backchannel.logout.session.required": "true",
        "client_credentials.use_refresh_token": "false",
        "display.on.consent.screen": "false",
        "oauth2.device.authorization.grant.enabled": "false",
        "oidc.ciba.grant.enabled": "false",
        "require.pushed.authorization.requests": "false",
        "tls.client.certificate.bound.access.tokens": "false",
        "token.response.type.bearer.lower-case": "false",
        "use.refresh.tokens": "true"
      },
      "fullScopeAllowed": true,
      "nodeReRegistrationTimeout": -1,
      "access": {
        "configure": true,
        "manage": true,
        "view": true
      },
      "optionalClientScopes": [
        "address",
        "phone",
        "offline_access",
        "microprofile-jwt"
      ],
      "defaultClientScopes": [
        "web-origins",
        "acr",
        "profile",
        "roles",
        "email"
      ],
      "alwaysDisplayInConsole": true
    }
  ],
  "components": null,
  "requiredActions": null
}

In keycloak-config-cli, the request contains a true value for publicClient:

# 2024-01-09 01:37:03.772 DEBUG 1 --- [           main] o.a.http.impl.execchain.MainClientExec   : Executing request PUT /admin/realms/test-realm/clients/649ad426-bda4-4393-b148-7fc16c90bd2a HTTP/1.1
# 2024-01-09 01:37:03.772 DEBUG 1 --- [           main] o.a.http.impl.execchain.MainClientExec   : Proxy auth state: UNCHALLENGED
# 2024-01-09 01:37:03.772 DEBUG 1 --- [           main] org.apache.http.headers                  : http-outgoing-0 >> PUT /admin/realms/test-realm/clients/649ad426-bda4-4393-b148-7fc16c90bd2a HTTP/1.1
# 2024-01-09 01:37:03.772 DEBUG 1 --- [           main] org.apache.http.headers                  : http-outgoing-0 >> Authorization: Bearer ey...redacted...Cn_Q
# 2024-01-09 01:37:03.772 DEBUG 1 --- [           main] org.apache.http.headers                  : http-outgoing-0 >> Content-Type: application/json
# 2024-01-09 01:37:03.772 DEBUG 1 --- [           main] org.apache.http.headers                  : http-outgoing-0 >> Content-Length: 1659
# 2024-01-09 01:37:03.772 DEBUG 1 --- [           main] org.apache.http.headers                  : http-outgoing-0 >> Host: example-kc-service.idp:8080
# 2024-01-09 01:37:03.772 DEBUG 1 --- [           main] org.apache.http.headers                  : http-outgoing-0 >> Connection: Keep-Alive
# 2024-01-09 01:37:03.772 DEBUG 1 --- [           main] org.apache.http.headers                  : http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.9)
# 2024-01-09 01:37:03.773 DEBUG 1 --- [           main] org.apache.http.wire                     : http-outgoing-0 >> "PUT /admin/realms/test-realm/clients/649ad426-bda4-4393-b148-7fc16c90bd2a HTTP/1.1[\r][\n]"
# 2024-01-09 01:37:03.773 DEBUG 1 --- [           main] org.apache.http.wire                     : http-outgoing-0 >> "Authorization: Bearer ey...redacted...n_Q[\r][\n]"
# 2024-01-09 01:37:03.773 DEBUG 1 --- [           main] org.apache.http.wire                     : http-outgoing-0 >> "Content-Type: application/json[\r][\n]"
# 2024-01-09 01:37:03.773 DEBUG 1 --- [           main] org.apache.http.wire                     : http-outgoing-0 >> "Content-Length: 1659[\r][\n]"
# 2024-01-09 01:37:03.773 DEBUG 1 --- [           main] org.apache.http.wire                     : http-outgoing-0 >> "Host: example-kc-service.idp:8080[\r][\n]"
# 2024-01-09 01:37:03.773 DEBUG 1 --- [           main] org.apache.http.wire                     : http-outgoing-0 >> "Connection: Keep-Alive[\r][\n]"
# 2024-01-09 01:37:03.773 DEBUG 1 --- [           main] org.apache.http.wire                     : http-outgoing-0 >> "User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.9)[\r][\n]"
# 2024-01-09 01:37:03.773 DEBUG 1 --- [           main] org.apache.http.wire                     : http-outgoing-0 >> "[\r][\n]"
# 2024-01-09 01:37:03.773 DEBUG 1 --- [           main] org.apache.http.wire                     : http-outgoing-0 >> "{"id":"649ad426-bda4-4393-b148-7fc16c90bd2a","clientId":"test2-client-secret","name":"test4-client-secret","description":null,"rootUrl":null,"adminUrl":null,"baseUrl":null,"surrogateAuthRequired":false,"enabled":true,"alwaysDisplayInConsole":true,"clientAuthenticatorType":"client-secret","secret":null,"registrationAccessToken":null,"defaultRoles":null,"redirectUris":[],"webOrigins":[],"notBefore":0,"bearerOnly":false,"consentRequired":false,"standardFlowEnabled":true,"implicitFlowEnabled":false,"directAccessGrantsEnabled":true,"serviceAccountsEnabled":false,"authorizationServicesEnabled":null,"directGrantsOnly":null,"publicClient":true,"frontchannelLogout":false,"protocol":"openid-connect","attributes":{"oidc.ciba.grant.enabled":"false","backchannel.logout.session.required":"true","client_credentials.use_refresh_token":"false","acr.loa.map":"{}","require.pushed.authorization.requests":"false","tls.client.certificate.bound.access.tokens":"false","display.on.consent.screen":"false","oauth2.device.authorization.grant.enabled":"false","backchannel.logout.revoke.offline.tokens":"false","token.response.type.bearer.lower-case":"false","use.refresh.tokens":"true"},"authenticationFlowBindingOverrides":{},"fullScopeAllowed":true,"nodeReRegistrationTimeout":-1,"registeredNodes":null,"protocolMappers":null,"clientTemplate":null,"useTemplateConfig":null,"useTemplateScope":null,"useTemplateMappers":null,"defaultClientScopes":["web-origins","acr","roles","profile","email"],"optionalClientScopes":["address","phone","offline_access","microprofile-jwt"],"authorizationSettings":null,"access":{"view":true,"configure":true,"manage":true},"origin":null}"

To Reproduce

Steps to reproduce the behavior:

  1. Apply the above resource descriptors to a Keycloak instance.
  2. Wait for the resources to be reconciled by Keycloak Controller.
  3. Observe how Client Authentication is not enabled for client test4-client-secret in realm test-realm.

Expected behavior

Client Authentication should be enabled for client test4-client-secret in realm test-realm

Environment

Additional context

Minikube running on Docker Desktop 4.26.1 (131620) on MacOS 14.2.1 on Intel.

bvegso commented 9 months ago
  1. tried removing the omitempty from the PublicClient property in the API type KeycloakAPIClient, that broke all of the tests
  2. tried understanding how keycloak-config-cli works to see if it has a default for the setting in the client representation: it relies on org.keycloak.representations.idm.ClientRepresentation and i could not find any relevant code for setting a default for this value
raffis commented 9 months ago

Thanks for the detailed report. Interesting, this might require an api change to have these bools as pointers instead. I first have to figure out if there are other similar fields and if this is just since kc v23.

bvegso commented 9 months ago

so the suspicion is that this was working with Keycloak 22.x? i will try that

raffis commented 9 months ago

so the suspicion is that this was working with Keycloak 22.x? i will try that

I just verified this with v20.x and there is the same problem.

raffis commented 9 months ago

v2.1.0 was released with a fix for this. Thanks for reporting.

bvegso commented 9 months ago

verified, works, thanks heaps.

note: i had to uninstall the chart, remove the "latest" v2.0.7 ghcr.io/doodlescheduling/keycloak-controller image from minikube manually and re-deploy the chart for keycloak-controller to start using the v2.1.0 "latest" image.

UPDATE: not sure how this happened, but when i first applied the v2.1.0 chart, it had "latest" image reference in my pod:

% k describe pod -n idp keycloak-controller-69776bd655-qsg7w
Name:             keycloak-controller-69776bd655-qsg7w
...
Containers:
  keycloak-controller:
    Container ID:  docker://1dc581c5098e9aae333b6c8296f4b2138aaadb4b0aba05b7e0555adc79122f4d
    Image:         ghcr.io/doodlescheduling/keycloak-controller:latest
    Image ID:      docker-pullable://ghcr.io/doodlescheduling/keycloak-controller@sha256:696db80f336b481742557d20edd3945a7c4f618b46efe32addf5ad702c953411

but now after having removed the 2.1.0 chart and reinstalled 2.1.0, it looks as expected:

% k describe pod -n idp keycloak-controller-6b56cbf59-fms4c
Name:             keycloak-controller-6b56cbf59-fms4c
...
Containers:
  keycloak-controller:
    Container ID:  docker://afd6df7f4d08b4ab0ce90883e411d783010f28e0a94e1845088250dd2d44fe3e
    Image:         ghcr.io/doodlescheduling/keycloak-controller:v2.1.0
    Image ID:      docker-pullable://ghcr.io/doodlescheduling/keycloak-controller@sha256:b6d8f1fb937f9e5164b160b41abd7e8280c709329a73be00227a32a2f17416bd