Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🔥 [OIDC] - Do not fail if IDP is not reachable at startup #443

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app/api/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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));
Expand Down
89 changes: 56 additions & 33 deletions app/authentications/providers/oidc/Oidc.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,34 +37,40 @@ 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})`);
}
}

/**
* 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',
},
Expand All @@ -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,
{
Expand Down Expand Up @@ -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;
4 changes: 2 additions & 2 deletions app/authentications/providers/oidc/Oidc.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});

Expand Down
1 change: 1 addition & 0 deletions docs/changelog/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down
4 changes: 2 additions & 2 deletions e2e/features/api-container.feature
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions e2e/features/prometheus.feature
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand All @@ -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 |