feat: Add optional audience for http-monitors via the oauth2 client credentials flow (#5950)
Some checks failed
Auto Test / armv7-simple-test (18, ARMv7) (push) Has been cancelled
Auto Test / armv7-simple-test (20, ARMv7) (push) Has been cancelled
Auto Test / check-linters (push) Has been cancelled
Auto Test / e2e-test (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
Merge Conflict Labeler / Labeling (push) Has been cancelled
validate / json-yaml-validate (push) Has been cancelled
validate / validate (push) Has been cancelled
Auto Test / auto-test (18, ARM64) (push) Has been cancelled
Auto Test / auto-test (18, macos-latest) (push) Has been cancelled
Auto Test / auto-test (18, ubuntu-latest) (push) Has been cancelled
Auto Test / auto-test (18, windows-latest) (push) Has been cancelled
Auto Test / auto-test (20, ARM64) (push) Has been cancelled
Auto Test / auto-test (20, macos-latest) (push) Has been cancelled
Auto Test / auto-test (20, ubuntu-latest) (push) Has been cancelled
Auto Test / auto-test (20, windows-latest) (push) Has been cancelled

Co-authored-by: Frank Elsinga <frank@elsinga.de>
This commit is contained in:
Ryan Hartje 2025-06-29 19:37:41 -05:00 committed by GitHub
parent 5336b05a7f
commit 9506b3a16b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 32 additions and 7 deletions

View file

@ -0,0 +1,12 @@
exports.up = function (knex) {
return knex.schema
.alterTable("monitor", function (table) {
table.string("oauth_audience").nullable().defaultTo(null);
});
};
exports.down = function (knex) {
return knex.schema.alterTable("monitor", function (table) {
table.string("oauth_audience").alter();
});
};

View file

@ -181,6 +181,7 @@ class Monitor extends BeanModel {
oauth_client_secret: this.oauth_client_secret, oauth_client_secret: this.oauth_client_secret,
oauth_token_url: this.oauth_token_url, oauth_token_url: this.oauth_token_url,
oauth_scopes: this.oauth_scopes, oauth_scopes: this.oauth_scopes,
oauth_audience: this.oauth_audience,
oauth_auth_method: this.oauth_auth_method, oauth_auth_method: this.oauth_auth_method,
pushToken: this.pushToken, pushToken: this.pushToken,
databaseConnectionString: this.databaseConnectionString, databaseConnectionString: this.databaseConnectionString,
@ -1746,7 +1747,7 @@ class Monitor extends BeanModel {
*/ */
async makeOidcTokenClientCredentialsRequest() { async makeOidcTokenClientCredentialsRequest() {
log.debug("monitor", `[${this.name}] The oauth access-token undefined or expired. Requesting a new token`); log.debug("monitor", `[${this.name}] The oauth access-token undefined or expired. Requesting a new token`);
const oAuthAccessToken = await getOidcTokenClientCredentials(this.oauth_token_url, this.oauth_client_id, this.oauth_client_secret, this.oauth_scopes, this.oauth_auth_method); const oAuthAccessToken = await getOidcTokenClientCredentials(this.oauth_token_url, this.oauth_client_id, this.oauth_client_secret, this.oauth_scopes, this.oauth_audience, this.oauth_auth_method);
if (this.oauthAccessToken?.expires_at) { if (this.oauthAccessToken?.expires_at) {
log.debug("monitor", `[${this.name}] Obtained oauth access-token. Expires at ${new Date(this.oauthAccessToken?.expires_at * 1000)}`); log.debug("monitor", `[${this.name}] Obtained oauth access-token. Expires at ${new Date(this.oauthAccessToken?.expires_at * 1000)}`);
} else { } else {

View file

@ -802,6 +802,7 @@ let needSetup = false;
bean.oauth_auth_method = monitor.oauth_auth_method; bean.oauth_auth_method = monitor.oauth_auth_method;
bean.oauth_token_url = monitor.oauth_token_url; bean.oauth_token_url = monitor.oauth_token_url;
bean.oauth_scopes = monitor.oauth_scopes; bean.oauth_scopes = monitor.oauth_scopes;
bean.oauth_audience = monitor.oauth_audience;
bean.tlsCa = monitor.tlsCa; bean.tlsCa = monitor.tlsCa;
bean.tlsCert = monitor.tlsCert; bean.tlsCert = monitor.tlsCert;
bean.tlsKey = monitor.tlsKey; bean.tlsKey = monitor.tlsKey;

View file

@ -58,7 +58,7 @@ exports.initJWTSecret = async () => {
}; };
/** /**
* Decodes a jwt and returns the payload portion without verifying the jqt. * Decodes a jwt and returns the payload portion without verifying the jwt.
* @param {string} jwt The input jwt as a string * @param {string} jwt The input jwt as a string
* @returns {object} Decoded jwt payload object * @returns {object} Decoded jwt payload object
*/ */
@ -67,15 +67,16 @@ exports.decodeJwt = (jwt) => {
}; };
/** /**
* Gets a Access Token form a oidc/oauth2 provider * Gets an Access Token from an oidc/oauth2 provider
* @param {string} tokenEndpoint The token URI form the auth service provider * @param {string} tokenEndpoint The token URI from the auth service provider
* @param {string} clientId The oidc/oauth application client id * @param {string} clientId The oidc/oauth application client id
* @param {string} clientSecret The oidc/oauth application client secret * @param {string} clientSecret The oidc/oauth application client secret
* @param {string} scope The scope the for which the token should be issued for * @param {string} scope The scope(s) for which the token should be issued for
* @param {string} authMethod The method on how to sent the credentials. Default client_secret_basic * @param {string} audience The audience for which the token should be issued for
* @param {string} authMethod The method used to send the credentials. Default client_secret_basic
* @returns {Promise<oidc.TokenSet>} TokenSet promise if the token request was successful * @returns {Promise<oidc.TokenSet>} TokenSet promise if the token request was successful
*/ */
exports.getOidcTokenClientCredentials = async (tokenEndpoint, clientId, clientSecret, scope, authMethod = "client_secret_basic") => { exports.getOidcTokenClientCredentials = async (tokenEndpoint, clientId, clientSecret, scope, audience, authMethod = "client_secret_basic") => {
const oauthProvider = new oidc.Issuer({ token_endpoint: tokenEndpoint }); const oauthProvider = new oidc.Issuer({ token_endpoint: tokenEndpoint });
let client = new oauthProvider.Client({ let client = new oauthProvider.Client({
client_id: clientId, client_id: clientId,
@ -91,6 +92,10 @@ exports.getOidcTokenClientCredentials = async (tokenEndpoint, clientId, clientSe
if (scope) { if (scope) {
grantParams.scope = scope; grantParams.scope = scope;
} }
if (audience) {
grantParams.audience = audience;
}
return await client.grant(grantParams); return await client.grant(grantParams);
}; };

View file

@ -1022,6 +1022,8 @@
"Client ID": "Client ID", "Client ID": "Client ID",
"Client Secret": "Client Secret", "Client Secret": "Client Secret",
"OAuth Scope": "OAuth Scope", "OAuth Scope": "OAuth Scope",
"OAuth Audience": "OAuth Audience",
"Optional: The audience to request the JWT for": "Optional: The audience to request the JWT for",
"Optional: Space separated list of scopes": "Optional: Space separated list of scopes", "Optional: Space separated list of scopes": "Optional: Space separated list of scopes",
"Go back to home page.": "Go back to home page.", "Go back to home page.": "Go back to home page.",
"No tags found.": "No tags found.", "No tags found.": "No tags found.",

View file

@ -1025,6 +1025,10 @@
<label for="oauth_scopes" class="form-label">{{ $t("OAuth Scope") }}</label> <label for="oauth_scopes" class="form-label">{{ $t("OAuth Scope") }}</label>
<input id="oauth_scopes" v-model="monitor.oauth_scopes" type="text" class="form-control" :placeholder="$t('Optional: Space separated list of scopes')"> <input id="oauth_scopes" v-model="monitor.oauth_scopes" type="text" class="form-control" :placeholder="$t('Optional: Space separated list of scopes')">
</div> </div>
<div class="my-3">
<label for="oauth_audience" class="form-label">{{ $t("OAuth Audience") }}</label>
<input id="oauth_audience" v-model="monitor.oauth_audience" type="text" class="form-control" :placeholder="$t('Optional: The audience to request the JWT for')">
</div>
</template> </template>
</template> </template>
<template v-else> <template v-else>