diff --git a/packages/kbn-es/src/settings.ts b/packages/kbn-es/src/settings.ts index a5919dccebd5a7..c9ff5e44296a12 100644 --- a/packages/kbn-es/src/settings.ts +++ b/packages/kbn-es/src/settings.ts @@ -11,6 +11,8 @@ */ const SECURE_SETTINGS_LIST = [ /^xpack\.security\.authc\.realms\.oidc\.[a-zA-Z0-9_]+\.rp\.client_secret$/, + /^xpack\.security\.authc\.realms\.jwt\.[a-zA-Z0-9_]+\.client_authentication\.shared_secret$/, + /^xpack\.security\.authc\.realms\.jwt\.[a-zA-Z0-9_]+\.hmac_jwkset$/, ]; function isSecureSetting(settingName: string) { diff --git a/x-pack/plugins/security/server/authentication/authenticator.ts b/x-pack/plugins/security/server/authentication/authenticator.ts index 8599eb287989ed..06ff153d37f612 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.ts @@ -647,7 +647,13 @@ export class Authenticator { throw new Error(`Provider name "${options.name}" is reserved.`); } - this.providers.set(options.name, new HTTPAuthenticationProvider(options, { supportedSchemes })); + this.providers.set( + options.name, + new HTTPAuthenticationProvider(options, { + supportedSchemes, + jwt: this.options.config.authc.http.jwt, + }) + ); } /** diff --git a/x-pack/plugins/security/server/authentication/providers/http.ts b/x-pack/plugins/security/server/authentication/providers/http.ts index 958b125a75c173..6a853b7b96eecc 100644 --- a/x-pack/plugins/security/server/authentication/providers/http.ts +++ b/x-pack/plugins/security/server/authentication/providers/http.ts @@ -5,6 +5,8 @@ * 2.0. */ +import Boom from '@hapi/boom'; + import type { KibanaRequest } from '@kbn/core/server'; import { AuthenticationResult } from '../authentication_result'; @@ -13,8 +15,14 @@ import { HTTPAuthorizationHeader } from '../http_authentication'; import type { AuthenticationProviderOptions } from './base'; import { BaseAuthenticationProvider } from './base'; +/** + * A a type-string of the Elasticsearch JWT realm. + */ +const JWT_REALM_TYPE = 'jwt'; + interface HTTPAuthenticationProviderOptions { supportedSchemes: Set; + jwt?: { restrictToPaths?: string[] }; } /** @@ -32,6 +40,17 @@ export class HTTPAuthenticationProvider extends BaseAuthenticationProvider { */ private readonly supportedSchemes: Set; + /** + * An optional JWT bearer credentials configuration. + */ + private readonly jwt?: { + /** + * A list of the Kibana internal URL paths that are allowed to be accessed with a valid JWT credentials. If not + * defined, no restriction is imposed + */ + restrictToPaths?: Set; + }; + constructor( protected readonly options: Readonly, httpOptions: Readonly @@ -44,6 +63,13 @@ export class HTTPAuthenticationProvider extends BaseAuthenticationProvider { this.supportedSchemes = new Set( [...httpOptions.supportedSchemes].map((scheme) => scheme.toLowerCase()) ); + this.jwt = httpOptions.jwt + ? { + restrictToPaths: httpOptions.jwt.restrictToPaths + ? new Set(httpOptions.jwt.restrictToPaths.map((path) => path.toLowerCase())) + : undefined, + } + : httpOptions.jwt; } /** @@ -79,6 +105,20 @@ export class HTTPAuthenticationProvider extends BaseAuthenticationProvider { this.logger.debug( `Request to ${request.url.pathname}${request.url.search} has been authenticated via authorization header with "${authorizationHeader.scheme}" scheme.` ); + + // Respect the JWT bearer path restriction, if set. JWT authentication metadata also includes fields that we might + // want to take into account as well: jwt_claim_iss, jwt_claim_name, jwt_claim_aud and jwt_claim_sub. + if ( + user.authentication_realm.type === JWT_REALM_TYPE && + this.jwt?.restrictToPaths && + !this.jwt.restrictToPaths.has(request.url.pathname) + ) { + this.logger.error( + `Attempted to authenticate with JWT credentials against ${request.url.pathname}${request.url.search}, but it's not allowed.` + ); + return AuthenticationResult.failed(Boom.unauthorized()); + } + return AuthenticationResult.succeeded(user); } catch (err) { this.logger.debug( diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index 2ce87e8d3d8e94..c1be96bd09db40 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -279,6 +279,21 @@ export const ConfigSchema = schema.object({ enabled: schema.boolean({ defaultValue: true }), autoSchemesEnabled: schema.boolean({ defaultValue: true }), schemes: schema.arrayOf(schema.string(), { defaultValue: ['apikey', 'bearer'] }), + jwt: schema.conditional( + schema.contextRef('serverless'), + true, + schema.object({ + restrictToPaths: schema.maybe( + schema.arrayOf( + schema.string({ + validate: (pathToValidate) => + /^\//.test(pathToValidate) ? undefined : 'must start with a slash', + }) + ) + ), + }), + schema.never() + ), }), }), audit: schema.object({