dashboard icon indicating copy to clipboard operation
dashboard copied to clipboard

OpenID Connect Improvements

Open holgerkoser opened this issue 3 years ago • 3 comments

What this PR does / why we need it:

Refresh Token

OpenID Connect defines the offline_access scope value to request a refresh_token in addition to the id_token which allows to use OIDC provider with short id_token lifetimes. With this PR we support the usage of scope offline_access. Typically the refresh_token is an opaque token and not a JWT with exp claim. But the refresh_token has a limited lifetime. If the refresh_token is used to refresh an id_token a new refresh_token is returned and the old refresh_token can not be used once again. Therefore the client must check if an id_token will expire soon and refresh the id_token if necessary. Since the process can be repeated again and again we recommend to configure and absolute sessionLifetime that stops the automatic refresh process and forces a logout if the the lifetime of a session exceeds this limit.

To enable this feature the operator has to configure the scope in the chart values:

oidc:
  scope: "openid email groups profile offline_access"
  sessionLifetime: 86400 # 1 day
Authorization Code Flow + PKCE

Currently all downloaded user kubeconfigs contain a client_secret. With this PR we support the PKCE flow for the OIDC client used by the dashboard itself as well for the public client used for the downloaded kubeconfig. In order to remove the client_secret from the downloaded kubeconfig the operator needs to force the usage the PKCE flow for the public client. The PKCE flow will be enabled by default if no clientSecret is given in the values.

oidc:
  ...
  public:
    clientId: 'my-public-client-id'
    # clientSecret: 'my-public-client-secret'

It is also possible to force PKCE flow for the client used by the dashboard but the client_secret is still required.

oidc:
  clientId: 'my-internal-client-id'
  clientSecret: 'my-internal-client-secret'
  usePKCE: true
Replace socket.io with Server-Sent Events

Since it is not possible to refresh the JWT for an open socket.io connection we use Server-Sent Events to push new data to the client. The initial data fetch for a new topic subscription is done via HTTP.

Which issue(s) this PR fixes: Fixes #976 #984

Special notes for your reviewer:

Release note:

Added support for OIDC refresh tokens. This allows an operator to configure short `id_token` lifetimes. 
Added support for PKCE flow to the internal and the public OIDC client. This allows an operator to configure the the public client without a `client_secret`. 
Migrated from socket.io to Server-Sent Events to push new data to the client.

holgerkoser avatar Aug 11 '22 10:08 holgerkoser

@grolu You have pull request review open invite, please check

gardener-robot avatar Aug 14 '22 03:08 gardener-robot

the shootInfo is not fetched anymore https://github.com/gardener/dashboard/pull/1277/files#diff-0c36ce15651c51795b411359e8caaf5af7960c82674b4f2015b8dd5f480fde2cL1430-L1443 . The kubeconfig is then not displayed

  subscribeShootAcknowledgement ({ commit, dispatch, state, getters }, object) {
    if (object.kind === 'Shoot') {
      commit('shoots/HANDLE_EVENTS', {
        rootState: state,
        rootGetters: getters,
        events: [{
          type: 'ADDED',
          object
        }]
      })
      const fetchShootAndShootSeedInfo = async ({ metadata, spec }) => {
        const promises = [dispatch('getShootInfo', metadata)]
        const seedName = spec.seedName
        if (getters.isAdmin && !getters.isSeedUnreachableByName(seedName)) {
          promises.push(dispatch('getShootSeedInfo', metadata))
        }
        try {
          await Promise.all(promises)
        } catch (err) {
          console.error('Failed to fetch shoot or shootSeed info:', err.message)
        }
      }
      fetchShootAndShootSeedInfo(object)
    }

petersutter avatar Aug 15 '22 11:08 petersutter

not necessarily related to this PR, but is the clientSecret really required https://github.com/gardener/dashboard/blob/master/charts/gardener-dashboard/templates/secret-oidc.yaml#L19 ?

petersutter avatar Aug 17 '22 15:08 petersutter

not necessarily related to this PR, but is the clientSecret really required https://github.com/gardener/dashboard/blob/master/charts/gardener-dashboard/templates/secret-oidc.yaml#L19 ?

Since our backend application is not deployed publicly (on mobile device or a SPA or ...) the secret can be kept secure. It makes sense to require a secret I think even if it could be removed since we now have PKCE flow also in the backend.

holgerkoser avatar Aug 24 '22 12:08 holgerkoser

the shootInfo is not fetched anymore https://github.com/gardener/dashboard/pull/1277/files#diff-0c36ce15651c51795b411359e8caaf5af7960c82674b4f2015b8dd5f480fde2cL1430-L1443 . The kubeconfig is then not displayed

  subscribeShootAcknowledgement ({ commit, dispatch, state, getters }, object) {
    if (object.kind === 'Shoot') {
      commit('shoots/HANDLE_EVENTS', {
        rootState: state,
        rootGetters: getters,
        events: [{
          type: 'ADDED',
          object
        }]
      })
      const fetchShootAndShootSeedInfo = async ({ metadata, spec }) => {
        const promises = [dispatch('getShootInfo', metadata)]
        const seedName = spec.seedName
        if (getters.isAdmin && !getters.isSeedUnreachableByName(seedName)) {
          promises.push(dispatch('getShootSeedInfo', metadata))
        }
        try {
          await Promise.all(promises)
        } catch (err) {
          console.error('Failed to fetch shoot or shootSeed info:', err.message)
        }
      }
      fetchShootAndShootSeedInfo(object)
    }

I have fixed this with commit 7435245

holgerkoser avatar Aug 24 '22 12:08 holgerkoser

This pull request is replaced by #1297

holgerkoser avatar Sep 26 '22 09:09 holgerkoser