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. (#38796)

This commit is contained in:
Aleh Zasypkin 2019-06-14 18:54:44 +02:00 committed by GitHub
parent 156467e535
commit 7f3f579894
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 94 additions and 13 deletions

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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', () => {

View file

@ -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.

View file

@ -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

View file

@ -125,6 +125,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

View file

@ -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);