Make Kerberos authentication provider work with Elastic anonymous access. (#40994)

This commit is contained in:
Aleh Zasypkin 2019-07-15 17:30:58 +02:00 committed by GitHub
parent 7731e5ba94
commit 6a29ef4105
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 99 additions and 16 deletions

View file

@ -36,13 +36,28 @@ exports.NativeRealm = class NativeRealm {
`setting ${chalk.bold(username)} password to ${chalk.bold(password)}`
);
await this._client.security.changePassword({
username,
refresh: 'wait_for',
body: {
password,
},
});
try {
await this._client.security.changePassword({
username,
refresh: 'wait_for',
body: {
password,
},
});
} catch (err) {
const isAnonymousUserPasswordChangeError =
err.statusCode === 400 &&
err.body &&
err.body.error &&
err.body.error.reason.startsWith(`user [${username}] is anonymous`);
if (!isAnonymousUserPasswordChangeError) {
throw err;
} else {
this._log.info(
`cannot set password for anonymous user ${chalk.bold(username)}, skipping`
);
}
}
});
}

View file

@ -69,7 +69,14 @@ describe('KerberosAuthenticationProvider', () => {
it('does not handle requests that can be authenticated without `Negotiate` header.', async () => {
const request = requestFixture();
callWithRequest.withArgs(request, 'shield.authenticate').resolves({});
callWithRequest
.withArgs(
sinon.match({
headers: { authorization: `Negotiate ${Buffer.from('__fake__').toString('base64')}` },
}),
'shield.authenticate'
)
.resolves({});
const authenticationResult = await provider.authenticate(request, null);
@ -78,7 +85,14 @@ describe('KerberosAuthenticationProvider', () => {
it('does not handle requests if backend does not support Kerberos.', async () => {
const request = requestFixture();
callWithRequest.withArgs(request, 'shield.authenticate').rejects(Boom.unauthorized());
callWithRequest
.withArgs(
sinon.match({
headers: { authorization: `Negotiate ${Buffer.from('__fake__').toString('base64')}` },
}),
'shield.authenticate'
)
.rejects(Boom.unauthorized());
let authenticationResult = await provider.authenticate(request, null);
expect(authenticationResult.notHandled()).toBe(true);
@ -93,7 +107,7 @@ describe('KerberosAuthenticationProvider', () => {
const request = requestFixture();
const tokenPair = { accessToken: 'token', refreshToken: 'refresh-token' };
callWithRequest.withArgs(request, 'shield.authenticate').rejects(Boom.unauthorized());
callWithRequest.withArgs(sinon.match.any, 'shield.authenticate').rejects(Boom.unauthorized());
tokens.refresh.withArgs(tokenPair.refreshToken).resolves(null);
let authenticationResult = await provider.authenticate(request, tokenPair);
@ -102,7 +116,7 @@ describe('KerberosAuthenticationProvider', () => {
expect(authenticationResult.challenges).toBeUndefined();
callWithRequest
.withArgs(request, 'shield.authenticate')
.withArgs(sinon.match.any, 'shield.authenticate')
.rejects(Boom.unauthorized(null, 'Basic'));
authenticationResult = await provider.authenticate(request, tokenPair);
@ -114,7 +128,12 @@ describe('KerberosAuthenticationProvider', () => {
it('fails with `Negotiate` challenge if backend supports Kerberos.', async () => {
const request = requestFixture();
callWithRequest
.withArgs(request, 'shield.authenticate')
.withArgs(
sinon.match({
headers: { authorization: `Negotiate ${Buffer.from('__fake__').toString('base64')}` },
}),
'shield.authenticate'
)
.rejects(Boom.unauthorized(null, 'Negotiate'));
const authenticationResult = await provider.authenticate(request, null);
@ -126,7 +145,9 @@ describe('KerberosAuthenticationProvider', () => {
it('fails if request authentication is failed with non-401 error.', async () => {
const request = requestFixture();
callWithRequest.withArgs(request, 'shield.authenticate').rejects(Boom.serverUnavailable());
callWithRequest
.withArgs(sinon.match.any, 'shield.authenticate')
.rejects(Boom.serverUnavailable());
const authenticationResult = await provider.authenticate(request, null);
@ -135,7 +156,7 @@ describe('KerberosAuthenticationProvider', () => {
expect(authenticationResult.challenges).toBeUndefined();
});
it('gets an token pair in exchange to SPNEGO one and stores it in the state.', async () => {
it('gets a token pair in exchange to SPNEGO one and stores it in the state.', async () => {
const user = { username: 'user' };
const request = requestFixture({ headers: { authorization: 'negotiate spnego' } });
@ -307,7 +328,12 @@ describe('KerberosAuthenticationProvider', () => {
statusCode: 500,
body: { error: { reason: 'token document is missing and must be present' } },
})
.withArgs(sinon.match({ headers: {} }), 'shield.authenticate')
.withArgs(
sinon.match({
headers: { authorization: `Negotiate ${Buffer.from('__fake__').toString('base64')}` },
}),
'shield.authenticate'
)
.rejects(Boom.unauthorized(null, 'Negotiate'));
tokens.refresh.withArgs(tokenPair.refreshToken).resolves(null);

View file

@ -276,7 +276,19 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider {
// Try to authenticate current request with Elasticsearch to see whether it supports SPNEGO.
let authenticationError: Error;
try {
await this.options.client.callWithRequest(request, 'shield.authenticate');
await this.options.client.callWithRequest(
{
headers: {
...request.headers,
// We should send a fake SPNEGO token to Elasticsearch to make sure Kerberos realm is included
// into authentication chain and adds a `WWW-Authenticate: Negotiate` header to the error
// response. Otherwise it may not be even consulted if request can be authenticated by other
// means (e.g. when anonymous access is enabled in Elasticsearch).
authorization: `Negotiate ${Buffer.from('__fake__').toString('base64')}`,
},
},
'shield.authenticate'
);
this.debug('Request was not supposed to be authenticated, ignoring result.');
return AuthenticationResult.notHandled();
} catch (err) {

View file

@ -14,6 +14,7 @@ require('@kbn/test').runTestsCli([
require.resolve('../test/api_integration/config.js'),
require.resolve('../test/plugin_api_integration/config.js'),
require.resolve('../test/kerberos_api_integration/config'),
require.resolve('../test/kerberos_api_integration/anonymous_access.config'),
require.resolve('../test/saml_api_integration/config.js'),
require.resolve('../test/token_api_integration/config.js'),
// require.resolve('../test/oidc_api_integration/config.js'),

View file

@ -0,0 +1,29 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { KibanaFunctionalTestDefaultProviders } from '../types/providers';
// eslint-disable-next-line import/no-default-export
export default async function({ readConfigFile }: KibanaFunctionalTestDefaultProviders) {
const kerberosAPITestsConfig = await readConfigFile(require.resolve('./config.ts'));
return {
...kerberosAPITestsConfig.getAll(),
junit: {
reportName: 'X-Pack Kerberos API with Anonymous Access Integration Tests',
},
esTestCluster: {
...kerberosAPITestsConfig.get('esTestCluster'),
serverArgs: [
...kerberosAPITestsConfig.get('esTestCluster.serverArgs'),
'xpack.security.authc.anonymous.username=anonymous_user',
'xpack.security.authc.anonymous.roles=superuser',
],
},
};
}