From a667cf035992809daf6ed78c1c60c667eda1c295 Mon Sep 17 00:00:00 2001 From: Manfred Martin Date: Sun, 29 Sep 2024 18:20:35 +0200 Subject: [PATCH] :fire: [OIDC] - Do not fail if IDP is not reachable at startup --- app/api/auth.js | 6 +- app/authentications/providers/oidc/Oidc.js | 89 ++++++++++++------- .../providers/oidc/Oidc.test.js | 4 +- docs/changelog/README.md | 1 + e2e/features/api-container.feature | 4 +- e2e/features/prometheus.feature | 4 +- 6 files changed, 66 insertions(+), 42 deletions(-) diff --git a/app/api/auth.js b/app/api/auth.js index ff084945..43efca36 100644 --- a/app/api/auth.js +++ b/app/api/auth.js @@ -48,9 +48,9 @@ function getSessionSecretKey() { * @param authentication * @param app */ -function useStrategy(authentication, app) { +async function useStrategy(authentication, app) { try { - const strategy = authentication.getStrategy(app); + const strategy = await authentication.getStrategy(app); passport.use(authentication.getId(), strategy); STRATEGY_IDS.push(authentication.getId()); } catch (e) { @@ -148,7 +148,7 @@ function init(app) { // Register all authentications Object.values(registry.getState().authentication) - .forEach((authentication) => useStrategy(authentication, app)); + .forEach(async (authentication) => useStrategy(authentication, app)); passport.serializeUser((user, done) => { done(null, JSON.stringify(user)); diff --git a/app/authentications/providers/oidc/Oidc.js b/app/authentications/providers/oidc/Oidc.js index cbe2bd3e..5f29fae9 100644 --- a/app/authentications/providers/oidc/Oidc.js +++ b/app/authentications/providers/oidc/Oidc.js @@ -37,21 +37,25 @@ class Oidc extends Authentication { }; } - async initAuthentication() { + async initClient() { this.log.debug(`Discovering configuration from ${this.configuration.discovery}`); custom.setHttpOptionsDefaults({ timeout: this.configuration.timeout, }); - const issuer = await Issuer.discover(this.configuration.discovery); - this.client = new issuer.Client({ - client_id: this.configuration.clientid, - client_secret: this.configuration.clientsecret, - response_types: ['code'], - }); try { - this.logoutUrl = this.client.endSessionUrl(); - } catch (e) { - this.log.warn('End session url is not supported'); + const issuer = await Issuer.discover(this.configuration.discovery); + this.client = new issuer.Client({ + client_id: this.configuration.clientid, + client_secret: this.configuration.clientsecret, + response_types: ['code'], + }); + try { + this.logoutUrl = this.client.endSessionUrl(); + } catch (e) { + this.log.warn('End session url is not supported'); + } + } catch (err) { + this.log.warn(` OIDC IDP discovery failed (${err.message})`); } } @@ -59,12 +63,14 @@ class Oidc extends Authentication { * Return passport strategy. * @param app */ - getStrategy(app) { + async getStrategy(app) { app.get(`/auth/oidc/${this.name}/redirect`, async (req, res) => this.redirect(req, res)); app.get(`/auth/oidc/${this.name}/cb`, async (req, res) => this.callback(req, res)); + + const client = await this.getClient(); const strategy = new OidcStrategy( { - client: this.client, + client, params: { scope: 'openid email profile', }, @@ -86,34 +92,40 @@ class Oidc extends Authentication { } async redirect(req, res) { - const codeVerifier = generators.codeVerifier(); - const codeChallenge = generators.codeChallenge(codeVerifier); - const state = uuid(); + try { + const client = await this.getClient(); + const codeVerifier = generators.codeVerifier(); + const codeChallenge = generators.codeChallenge(codeVerifier); + const state = uuid(); - req.session.oidc = { - codeVerifier, - state, - }; - const authUrl = `${this.client.authorizationUrl({ - redirect_uri: `${getPublicUrl(req)}/auth/oidc/${this.name}/cb`, - scope: 'openid email profile', - code_challenge_method: 'S256', - code_challenge: codeChallenge, - state, - })}`; - this.log.debug(`Build redirection url [${authUrl}]`); - res.json({ - url: authUrl, - }); + req.session.oidc = { + codeVerifier, + state, + }; + const authUrl = `${client.authorizationUrl({ + redirect_uri: `${getPublicUrl(req)}/auth/oidc/${this.name}/cb`, + scope: 'openid email profile', + code_challenge_method: 'S256', + code_challenge: codeChallenge, + state, + })}`; + this.log.debug(`Build redirection url [${authUrl}]`); + res.json({ + url: authUrl, + }); + } catch (err) { + res.status(500).json('OIDC provider error'); + } } async callback(req, res) { + const client = await this.getClient(); try { this.log.debug('Validate callback data'); - const params = this.client.callbackParams(req); + const params = client.callbackParams(req); const oidcChecks = req.session.oidc; - const tokenSet = await this.client.callback( + const tokenSet = await client.callback( `${getPublicUrl(req)}/auth/oidc/${this.name}/cb`, params, { @@ -151,11 +163,22 @@ class Oidc extends Authentication { } async getUserFromAccessToken(accessToken) { - const userInfo = await this.client.userinfo(accessToken); + const client = await this.getClient(); + const userInfo = await client.userinfo(accessToken); return { username: userInfo.email || 'unknown', }; } + + async getClient() { + if (!this.client) { + await this.initClient(); + } + if (!this.client) { + throw new Error('OIDC provider is not initialized'); + } + return this.client; + } } module.exports = Oidc; diff --git a/app/authentications/providers/oidc/Oidc.test.js b/app/authentications/providers/oidc/Oidc.test.js index fda1b41d..f5fb85e0 100644 --- a/app/authentications/providers/oidc/Oidc.test.js +++ b/app/authentications/providers/oidc/Oidc.test.js @@ -37,8 +37,8 @@ test('validateConfiguration should throw error when invalid', () => { }).toThrowError(ValidationError); }); -test('getStrategy should return an Authentication strategy', () => { - const strategy = oidc.getStrategy(app); +test('getStrategy should return an Authentication strategy', async () => { + const strategy = await oidc.getStrategy(app); expect(strategy.name).toEqual('oidc'); }); diff --git a/docs/changelog/README.md b/docs/changelog/README.md index 045854e1..156f7428 100644 --- a/docs/changelog/README.md +++ b/docs/changelog/README.md @@ -4,6 +4,7 @@ - :star: [UI] - Make watcher and registry names visible when container box is collapsed - :fire: Fix edge case where comparing different tags with identical digests (e.g. `mongo:8` = `mongo:8.0.0`) - :fire: [UI] - Fix sporadic 401 error when loading backend info from the UI +- :fire: [OIDC] - Do not fail if IDP is not reachable at startup # 6.5.0 - :star: [API/UI] - Add a feature to allow/disallow delete operations (`WUD_SERVER_FEATURE_DELETE`) diff --git a/e2e/features/api-container.feature b/e2e/features/api-container.feature index 49d72b13..cca7b7b6 100644 --- a/e2e/features/api-container.feature +++ b/e2e/features/api-container.feature @@ -21,7 +21,7 @@ Feature: WUD Container API Exposure | 0 | ecr_sub_sub_test | ecr | https://229211676173.dkr.ecr.eu-west-1.amazonaws.com/v2 | sub/sub/test | 1.0.0 | 2.0.0 | true | | 1 | ecr_sub_test | ecr | https://229211676173.dkr.ecr.eu-west-1.amazonaws.com/v2 | sub/test | 1.0.0 | 2.0.0 | true | | 2 | ecr_test | ecr | https://229211676173.dkr.ecr.eu-west-1.amazonaws.com/v2 | test | 1.0.0 | 2.0.0 | true | - | 3 | ghcr_radarr | ghcr | https://ghcr.io/v2 | linuxserver/radarr | 3.2.1.5070-ls105 | 5.11.0.9244-ls240 | true | + | 3 | ghcr_radarr | ghcr | https://ghcr.io/v2 | linuxserver/radarr | 3.2.1.5070-ls105 | 5.11.0.9244-ls241 | true | | 4 | gitlab_test | gitlab | https://registry.gitlab.com/v2 | manfred-martin/docker-registry-test | 1.0.0 | 2.0.0 | true | | 5 | hub_homeassistant_202161 | hub | https://registry-1.docker.io/v2 | homeassistant/home-assistant | 2021.6.1 | 2024.9.3 | true | | 6 | hub_homeassistant_latest | hub | https://registry-1.docker.io/v2 | homeassistant/home-assistant | latest | latest | false | @@ -36,7 +36,7 @@ Feature: WUD Container API Exposure | 15 | hub_vaultwarden_1222 | hub | https://registry-1.docker.io/v2 | vaultwarden/server | 1.32.0-alpine | 1.32.0-alpine | false | | 16 | hub_vaultwarden_latest | hub | https://registry-1.docker.io/v2 | vaultwarden/server | latest | latest | false | | 17 | hub_youtubedb_latest | hub | https://registry-1.docker.io/v2 | jeeaaasustest/youtube-dl | latest | latest | false | - | 18 | lscr_radarr | lscr | https://lscr.io/v2 | linuxserver/radarr | 3.2.1.5070-ls105 | 5.11.0.9244-ls240 | true | + | 18 | lscr_radarr | lscr | https://lscr.io/v2 | linuxserver/radarr | 3.2.1.5070-ls105 | 5.11.0.9244-ls241 | true | | 19 | quay_prometheus | quay | https://quay.io/v2 | prometheus/prometheus | v2.52.0 | v2.54.1 | true | Scenario: WUD must allow to get a container with semver diff --git a/e2e/features/prometheus.feature b/e2e/features/prometheus.feature index 989ce84b..80cc68ef 100644 --- a/e2e/features/prometheus.feature +++ b/e2e/features/prometheus.feature @@ -25,7 +25,7 @@ Feature: Prometheus exposure | ecr_sub_sub_test | ecr | https://229211676173.dkr.ecr.eu-west-1.amazonaws.com/v2 | sub/sub/test | 1.0.0 | 2.0.0 | true | | ecr_sub_test | ecr | https://229211676173.dkr.ecr.eu-west-1.amazonaws.com/v2 | sub/test | 1.0.0 | 2.0.0 | true | | ecr_test | ecr | https://229211676173.dkr.ecr.eu-west-1.amazonaws.com/v2 | test | 1.0.0 | 2.0.0 | true | - | ghcr_radarr | ghcr | https://ghcr.io/v2 | linuxserver/radarr | 3.2.1.5070-ls105 |5.11.0.9244-ls240 | true | + | ghcr_radarr | ghcr | https://ghcr.io/v2 | linuxserver/radarr | 3.2.1.5070-ls105 |5.11.0.9244-ls241 | true | | gitlab_test | gitlab | https://registry.gitlab.com/v2 | manfred-martin/docker-registry-test | 1.0.0 | 2.0.0 | true | | hub_homeassistant_202161 | hub | https://registry-1.docker.io/v2 | homeassistant/home-assistant | 2021.6.1 | 2024.9.3 | true | | hub_homeassistant_latest | hub | https://registry-1.docker.io/v2 | homeassistant/home-assistant | latest | latest | false | @@ -40,5 +40,5 @@ Feature: Prometheus exposure | hub_vaultwarden_1222 | hub | https://registry-1.docker.io/v2 | vaultwarden/server | 1.32.0-alpine | 1.32.0-alpine | false | | hub_vaultwarden_latest | hub | https://registry-1.docker.io/v2 | vaultwarden/server | latest | latest | false | | hub_youtubedb_latest | hub | https://registry-1.docker.io/v2 | jeeaaasustest/youtube-dl | latest | latest | false | - | lscr_radarr | lscr | https://lscr.io/v2 | linuxserver/radarr | 3.2.1.5070-ls105 |5.11.0.9244-ls240 | true | + | lscr_radarr | lscr | https://lscr.io/v2 | linuxserver/radarr | 3.2.1.5070-ls105 |5.11.0.9244-ls241 | true | | quay_prometheus | quay | https://quay.io/v2 | prometheus/prometheus | v2.52.0 |v2.54.1 | true |