mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[7.x] Make Kerberos authentication provider to accept requests with Authorization: Bearer xxx
header to support reporting use case. Disable Kerberos support for the Windows Chromium build. (#38998)
This commit is contained in:
parent
93dc567a86
commit
329dff61c2
9 changed files with 94 additions and 13 deletions
|
@ -27,6 +27,8 @@ The various build flags are not well documented. Some are documented [here](http
|
|||
|
||||
As of this writing, there is an officially supported headless Chromium build args file for Linux: `build/args/headless.gn`. This does not work on Windows or Mac, so we have taken that as our starting point, and modified it until the Windows / Mac builds succeeded.
|
||||
|
||||
**NOTE:** Please, make sure you consult @elastic/kibana-security before you change, remove or add any of the build flags.
|
||||
|
||||
## VMs
|
||||
|
||||
I ran Linux and Windows VMs in GCP with the following specs:
|
||||
|
|
|
@ -19,6 +19,7 @@ use_alsa = false
|
|||
use_cups = false
|
||||
use_dbus = false
|
||||
use_gio = false
|
||||
# Please, consult @elastic/kibana-security before changing/removing this option.
|
||||
use_kerberos = false
|
||||
use_libpci = false
|
||||
use_pulseaudio = false
|
||||
|
|
|
@ -4,3 +4,5 @@ symbol_level = 0
|
|||
is_component_build = false
|
||||
remove_webcore_debug_symbols = true
|
||||
enable_nacl = false
|
||||
# Please, consult @elastic/kibana-security before changing/removing this option.
|
||||
use_kerberos = false
|
||||
|
|
|
@ -18,6 +18,8 @@ use_gio = false
|
|||
use_libpci = false
|
||||
use_pulseaudio = false
|
||||
use_udev = false
|
||||
# Please, consult @elastic/kibana-security before changing/removing this option.
|
||||
use_kerberos = false
|
||||
|
||||
is_debug = false
|
||||
symbol_level = 0
|
||||
|
|
|
@ -266,6 +266,51 @@ describe('KerberosAuthenticationProvider', () => {
|
|||
expect(authenticationResult.error).toHaveProperty('output.statusCode', 401);
|
||||
expect(authenticationResult.challenges).toEqual(['Negotiate']);
|
||||
});
|
||||
|
||||
it('succeeds if `authorization` contains a valid token.', async () => {
|
||||
const user = { username: 'user' };
|
||||
const request = requestFixture({ headers: { authorization: 'Bearer some-valid-token' } });
|
||||
|
||||
callWithRequest.withArgs(request, 'shield.authenticate').resolves(user);
|
||||
|
||||
const authenticationResult = await provider.authenticate(request);
|
||||
|
||||
expect(request.headers.authorization).toBe('Bearer some-valid-token');
|
||||
expect(authenticationResult.succeeded()).toBe(true);
|
||||
expect(authenticationResult.user).toBe(user);
|
||||
expect(authenticationResult.state).toBeUndefined();
|
||||
});
|
||||
|
||||
it('fails if token from `authorization` header is rejected.', async () => {
|
||||
const request = requestFixture({ headers: { authorization: 'Bearer some-invalid-token' } });
|
||||
|
||||
const failureReason = { statusCode: 401 };
|
||||
callWithRequest.withArgs(request, 'shield.authenticate').rejects(failureReason);
|
||||
|
||||
const authenticationResult = await provider.authenticate(request);
|
||||
|
||||
expect(authenticationResult.failed()).toBe(true);
|
||||
expect(authenticationResult.error).toBe(failureReason);
|
||||
});
|
||||
|
||||
it('fails if token from `authorization` header is rejected even if state contains a valid one.', async () => {
|
||||
const user = { username: 'user' };
|
||||
const request = requestFixture({ headers: { authorization: 'Bearer some-invalid-token' } });
|
||||
|
||||
const failureReason = { statusCode: 401 };
|
||||
callWithRequest.withArgs(request, 'shield.authenticate').rejects(failureReason);
|
||||
|
||||
callWithRequest
|
||||
.withArgs(sinon.match({ headers: { authorization: 'Bearer some-valid-token' } }))
|
||||
.resolves(user);
|
||||
|
||||
const authenticationResult = await provider.authenticate(request, {
|
||||
accessToken: 'some-valid-token',
|
||||
});
|
||||
|
||||
expect(authenticationResult.failed()).toBe(true);
|
||||
expect(authenticationResult.error).toBe(failureReason);
|
||||
});
|
||||
});
|
||||
|
||||
describe('`deauthenticate` method', () => {
|
||||
|
|
|
@ -68,7 +68,10 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
this.debug(`Trying to authenticate user request to ${request.url.path}.`);
|
||||
|
||||
const authenticationScheme = getRequestAuthenticationScheme(request);
|
||||
if (authenticationScheme && authenticationScheme !== 'negotiate') {
|
||||
if (
|
||||
authenticationScheme &&
|
||||
(authenticationScheme !== 'negotiate' && authenticationScheme !== 'bearer')
|
||||
) {
|
||||
this.debug(`Unsupported authentication scheme: ${authenticationScheme}`);
|
||||
return AuthenticationResult.notHandled();
|
||||
}
|
||||
|
@ -78,7 +81,15 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
return AuthenticationResult.notHandled();
|
||||
}
|
||||
|
||||
let authenticationResult = await this.authenticateViaHeader(request);
|
||||
let authenticationResult = AuthenticationResult.notHandled();
|
||||
if (authenticationScheme) {
|
||||
// We should get rid of `Bearer` scheme support as soon as Reporting doesn't need it anymore.
|
||||
authenticationResult =
|
||||
authenticationScheme === 'bearer'
|
||||
? await this.authenticateWithBearerScheme(request)
|
||||
: await this.authenticateWithNegotiateScheme(request);
|
||||
}
|
||||
|
||||
if (state && authenticationResult.notHandled()) {
|
||||
authenticationResult = await this.authenticateViaState(request, state);
|
||||
if (authenticationResult.failed() && isAccessTokenExpiredError(authenticationResult.error)) {
|
||||
|
@ -131,18 +142,12 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
}
|
||||
|
||||
/**
|
||||
* Validates whether request contains `Negotiate ***` Authorization header and just passes it
|
||||
* forward to Elasticsearch backend.
|
||||
* Tries to authenticate request with `Negotiate ***` Authorization header by passing it to the Elasticsearch backend to
|
||||
* get an access token in exchange.
|
||||
* @param request Request instance.
|
||||
*/
|
||||
private async authenticateViaHeader(request: RequestWithLoginAttempt) {
|
||||
this.debug('Trying to authenticate via header.');
|
||||
|
||||
const authorization = request.headers.authorization;
|
||||
if (!authorization) {
|
||||
this.debug('Authorization header is not presented.');
|
||||
return AuthenticationResult.notHandled();
|
||||
}
|
||||
private async authenticateWithNegotiateScheme(request: RequestWithLoginAttempt) {
|
||||
this.debug('Trying to authenticate request using "Negotiate" authentication scheme.');
|
||||
|
||||
// First attempt to exchange SPNEGO token for an access token.
|
||||
let accessToken: string;
|
||||
|
@ -180,6 +185,28 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to authenticate request with `Bearer ***` Authorization header by passing it to the Elasticsearch backend.
|
||||
* @param request Request instance.
|
||||
*/
|
||||
private async authenticateWithBearerScheme(request: RequestWithLoginAttempt) {
|
||||
this.debug('Trying to authenticate request using "Bearer" authentication scheme.');
|
||||
|
||||
try {
|
||||
const user = await this.options.client.callWithRequest(request, 'shield.authenticate');
|
||||
|
||||
this.debug('Request has been authenticated using "Bearer" authentication scheme.');
|
||||
|
||||
return AuthenticationResult.succeeded(user);
|
||||
} catch (err) {
|
||||
this.debug(
|
||||
`Failed to authenticate request using "Bearer" authentication scheme: ${err.message}`
|
||||
);
|
||||
|
||||
return AuthenticationResult.failed(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to extract access token from state and adds it to the request before it's
|
||||
* forwarded to Elasticsearch backend.
|
||||
|
|
|
@ -138,6 +138,7 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
public async authenticate(request: RequestWithLoginAttempt, state?: ProviderState | null) {
|
||||
this.debug(`Trying to authenticate user request to ${request.url.path}.`);
|
||||
|
||||
// We should get rid of `Bearer` scheme support as soon as Reporting doesn't need it anymore.
|
||||
let {
|
||||
authenticationResult,
|
||||
headerNotRecognized, // eslint-disable-line prefer-const
|
||||
|
|
|
@ -121,6 +121,7 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
public async authenticate(request: RequestWithLoginAttempt, state?: ProviderState | null) {
|
||||
this.debug(`Trying to authenticate user request to ${request.url.path}.`);
|
||||
|
||||
// We should get rid of `Bearer` scheme support as soon as Reporting doesn't need it anymore.
|
||||
let {
|
||||
authenticationResult,
|
||||
// eslint-disable-next-line prefer-const
|
||||
|
|
|
@ -201,7 +201,7 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) {
|
|||
const apiResponse = await supertest
|
||||
.get('/api/security/v1/me')
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.set('Authorization', 'Bearer AbCdEf')
|
||||
.set('Authorization', 'Basic a3JiNTprcmI1')
|
||||
.set('Cookie', sessionCookie.cookieString())
|
||||
.expect(401);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue