mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Prevent Kerberos and PKI providers from initiating a new session for unauthenticated XHR/API requests. (#82817)
* Prevent Kerberos and PKI providers from initiating a new session for unauthenticated XHR requests. * Review#1: fix comment.
This commit is contained in:
parent
55cf3bd0a6
commit
45ddd69ca2
49 changed files with 271 additions and 290 deletions
3
.github/CODEOWNERS
vendored
3
.github/CODEOWNERS
vendored
|
@ -254,9 +254,6 @@
|
|||
/x-pack/test/ui_capabilities/ @elastic/kibana-security
|
||||
/x-pack/test/encrypted_saved_objects_api_integration/ @elastic/kibana-security
|
||||
/x-pack/test/functional/apps/security/ @elastic/kibana-security
|
||||
/x-pack/test/kerberos_api_integration/ @elastic/kibana-security
|
||||
/x-pack/test/oidc_api_integration/ @elastic/kibana-security
|
||||
/x-pack/test/pki_api_integration/ @elastic/kibana-security
|
||||
/x-pack/test/security_api_integration/ @elastic/kibana-security
|
||||
/x-pack/test/security_functional/ @elastic/kibana-security
|
||||
/x-pack/test/spaces_api_integration/ @elastic/kibana-security
|
||||
|
|
|
@ -346,6 +346,16 @@ describe('KerberosAuthenticationProvider', () => {
|
|||
expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not start SPNEGO for Ajax requests.', async () => {
|
||||
const request = httpServerMock.createKibanaRequest({ headers: { 'kbn-xsrf': 'xsrf' } });
|
||||
await expect(provider.authenticate(request)).resolves.toEqual(
|
||||
AuthenticationResult.notHandled()
|
||||
);
|
||||
|
||||
expect(mockOptions.client.asScoped).not.toHaveBeenCalled();
|
||||
expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('succeeds if state contains a valid token.', async () => {
|
||||
const user = mockAuthenticatedUser();
|
||||
const request = httpServerMock.createKibanaRequest({ headers: {} });
|
||||
|
@ -442,9 +452,6 @@ describe('KerberosAuthenticationProvider', () => {
|
|||
});
|
||||
|
||||
it('fails with `Negotiate` challenge if both access and refresh tokens from the state are expired and backend supports Kerberos.', async () => {
|
||||
const request = httpServerMock.createKibanaRequest();
|
||||
const tokenPair = { accessToken: 'expired-token', refreshToken: 'some-valid-refresh-token' };
|
||||
|
||||
const failureReason = LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(
|
||||
new (errors.AuthenticationException as any)('Unauthorized', {
|
||||
body: { error: { header: { 'WWW-Authenticate': 'Negotiate' } } },
|
||||
|
@ -456,37 +463,45 @@ describe('KerberosAuthenticationProvider', () => {
|
|||
|
||||
mockOptions.tokens.refresh.mockResolvedValue(null);
|
||||
|
||||
await expect(provider.authenticate(request, tokenPair)).resolves.toEqual(
|
||||
const nonAjaxRequest = httpServerMock.createKibanaRequest();
|
||||
const nonAjaxTokenPair = {
|
||||
accessToken: 'expired-token',
|
||||
refreshToken: 'some-valid-refresh-token',
|
||||
};
|
||||
await expect(provider.authenticate(nonAjaxRequest, nonAjaxTokenPair)).resolves.toEqual(
|
||||
AuthenticationResult.failed(failureReason, {
|
||||
authResponseHeaders: { 'WWW-Authenticate': 'Negotiate' },
|
||||
})
|
||||
);
|
||||
|
||||
expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1);
|
||||
expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(tokenPair.refreshToken);
|
||||
});
|
||||
|
||||
it('does not re-start SPNEGO if both access and refresh tokens from the state are expired.', async () => {
|
||||
const request = httpServerMock.createKibanaRequest({ routeAuthRequired: false });
|
||||
const tokenPair = { accessToken: 'expired-token', refreshToken: 'some-valid-refresh-token' };
|
||||
|
||||
const failureReason = LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(
|
||||
new (errors.AuthenticationException as any)('Unauthorized', {
|
||||
body: { error: { header: { 'WWW-Authenticate': 'Negotiate' } } },
|
||||
const ajaxRequest = httpServerMock.createKibanaRequest({ headers: { 'kbn-xsrf': 'xsrf' } });
|
||||
const ajaxTokenPair = {
|
||||
accessToken: 'expired-token',
|
||||
refreshToken: 'ajax-some-valid-refresh-token',
|
||||
};
|
||||
await expect(provider.authenticate(ajaxRequest, ajaxTokenPair)).resolves.toEqual(
|
||||
AuthenticationResult.failed(failureReason, {
|
||||
authResponseHeaders: { 'WWW-Authenticate': 'Negotiate' },
|
||||
})
|
||||
);
|
||||
const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient();
|
||||
mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason);
|
||||
mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
|
||||
|
||||
mockOptions.tokens.refresh.mockResolvedValue(null);
|
||||
|
||||
await expect(provider.authenticate(request, tokenPair)).resolves.toEqual(
|
||||
AuthenticationResult.notHandled()
|
||||
const optionalAuthRequest = httpServerMock.createKibanaRequest({ routeAuthRequired: false });
|
||||
const optionalAuthTokenPair = {
|
||||
accessToken: 'expired-token',
|
||||
refreshToken: 'optional-some-valid-refresh-token',
|
||||
};
|
||||
await expect(
|
||||
provider.authenticate(optionalAuthRequest, optionalAuthTokenPair)
|
||||
).resolves.toEqual(
|
||||
AuthenticationResult.failed(failureReason, {
|
||||
authResponseHeaders: { 'WWW-Authenticate': 'Negotiate' },
|
||||
})
|
||||
);
|
||||
|
||||
expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1);
|
||||
expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(tokenPair.refreshToken);
|
||||
expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(3);
|
||||
expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(nonAjaxTokenPair.refreshToken);
|
||||
expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(ajaxTokenPair.refreshToken);
|
||||
expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(optionalAuthTokenPair.refreshToken);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
import { AuthenticationResult } from '../authentication_result';
|
||||
import { DeauthenticationResult } from '../deauthentication_result';
|
||||
import { HTTPAuthorizationHeader } from '../http_authentication';
|
||||
import { canRedirectRequest } from '../can_redirect_request';
|
||||
import { Tokens, TokenPair } from '../tokens';
|
||||
import { BaseAuthenticationProvider } from './base';
|
||||
|
||||
|
@ -32,8 +33,9 @@ const WWWAuthenticateHeaderName = 'WWW-Authenticate';
|
|||
* @param request Request instance.
|
||||
*/
|
||||
function canStartNewSession(request: KibanaRequest) {
|
||||
// We should try to establish new session only if request requires authentication.
|
||||
return request.route.options.authRequired === true;
|
||||
// We should try to establish new session only if request requires authentication and it's not an XHR request.
|
||||
// Technically we can authenticate XHR requests too, but we don't want these to create a new session unintentionally.
|
||||
return canRedirectRequest(request) && request.route.options.authRequired === true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -75,11 +77,8 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
return AuthenticationResult.notHandled();
|
||||
}
|
||||
|
||||
let authenticationResult = authorizationHeader
|
||||
? await this.authenticateWithNegotiateScheme(request)
|
||||
: AuthenticationResult.notHandled();
|
||||
|
||||
if (state && authenticationResult.notHandled()) {
|
||||
let authenticationResult = AuthenticationResult.notHandled();
|
||||
if (state) {
|
||||
authenticationResult = await this.authenticateViaState(request, state);
|
||||
if (
|
||||
authenticationResult.failed() &&
|
||||
|
@ -89,11 +88,15 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
}
|
||||
}
|
||||
|
||||
// If we couldn't authenticate by means of all methods above, let's try to check if Elasticsearch can
|
||||
// start authentication mechanism negotiation, otherwise just return authentication result we have.
|
||||
return authenticationResult.notHandled() && canStartNewSession(request)
|
||||
? await this.authenticateViaSPNEGO(request, state)
|
||||
: authenticationResult;
|
||||
if (!authenticationResult.notHandled() || !canStartNewSession(request)) {
|
||||
return authenticationResult;
|
||||
}
|
||||
|
||||
// If we couldn't authenticate by means of all methods above, let's check if we're already at the authentication
|
||||
// mechanism negotiation stage, otherwise check with Elasticsearch if we can start it.
|
||||
return authorizationHeader
|
||||
? await this.authenticateWithNegotiateScheme(request)
|
||||
: await this.authenticateViaSPNEGO(request, state);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -264,12 +267,12 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
return AuthenticationResult.failed(err);
|
||||
}
|
||||
|
||||
// If refresh token is no longer valid, then we should clear session and renegotiate using SPNEGO.
|
||||
// If refresh token is no longer valid, let's try to renegotiate new tokens using SPNEGO. We
|
||||
// allow this because expired underlying token is an implementation detail and Kibana user
|
||||
// facing session is still valid.
|
||||
if (refreshedTokenPair === null) {
|
||||
this.logger.debug('Both access and refresh tokens are expired.');
|
||||
return canStartNewSession(request)
|
||||
? this.authenticateViaSPNEGO(request, state)
|
||||
: AuthenticationResult.notHandled();
|
||||
this.logger.debug('Both access and refresh tokens are expired. Re-authenticating...');
|
||||
return this.authenticateViaSPNEGO(request, state);
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
|
@ -295,6 +295,22 @@ describe('PKIAuthenticationProvider', () => {
|
|||
expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not exchange peer certificate to access token for Ajax requests.', async () => {
|
||||
const request = httpServerMock.createKibanaRequest({
|
||||
headers: { 'kbn-xsrf': 'xsrf' },
|
||||
socket: getMockSocket({
|
||||
authorized: true,
|
||||
peerCertificate: getMockPeerCertificate(['2A:7A:C2:DD', '3B:8B:D3:EE']),
|
||||
}),
|
||||
});
|
||||
await expect(provider.authenticate(request)).resolves.toEqual(
|
||||
AuthenticationResult.notHandled()
|
||||
);
|
||||
|
||||
expect(mockOptions.client.asScoped).not.toHaveBeenCalled();
|
||||
expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('fails with non-401 error if state is available, peer is authorized, but certificate is not available.', async () => {
|
||||
const request = httpServerMock.createKibanaRequest({
|
||||
socket: getMockSocket({ authorized: true }),
|
||||
|
@ -383,14 +399,7 @@ describe('PKIAuthenticationProvider', () => {
|
|||
});
|
||||
|
||||
it('gets a new access token even if existing token is expired.', async () => {
|
||||
const user = mockAuthenticatedUser();
|
||||
const request = httpServerMock.createKibanaRequest({
|
||||
socket: getMockSocket({
|
||||
authorized: true,
|
||||
peerCertificate: getMockPeerCertificate(['2A:7A:C2:DD', '3B:8B:D3:EE']),
|
||||
}),
|
||||
});
|
||||
const state = { accessToken: 'existing-token', peerCertificateFingerprint256: '2A:7A:C2:DD' };
|
||||
const user = mockAuthenticatedUser({ authentication_provider: { type: 'pki', name: 'pki' } });
|
||||
|
||||
const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient();
|
||||
mockScopedClusterClient.callAsCurrentUser
|
||||
|
@ -399,21 +408,74 @@ describe('PKIAuthenticationProvider', () => {
|
|||
LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error())
|
||||
)
|
||||
// In response to a call with a new token.
|
||||
.mockResolvedValueOnce(user) // In response to call with an expired token.
|
||||
.mockRejectedValueOnce(
|
||||
LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error())
|
||||
)
|
||||
// In response to a call with a new token.
|
||||
.mockResolvedValueOnce(user) // In response to call with an expired token.
|
||||
.mockRejectedValueOnce(
|
||||
LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error())
|
||||
)
|
||||
// In response to a call with a new token.
|
||||
.mockResolvedValueOnce(user);
|
||||
mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
|
||||
mockOptions.client.callAsInternalUser.mockResolvedValue({ access_token: 'access-token' });
|
||||
|
||||
await expect(provider.authenticate(request, state)).resolves.toEqual(
|
||||
AuthenticationResult.succeeded(
|
||||
{ ...user, authentication_provider: { type: 'pki', name: 'pki' } },
|
||||
{
|
||||
authHeaders: { authorization: 'Bearer access-token' },
|
||||
state: { accessToken: 'access-token', peerCertificateFingerprint256: '2A:7A:C2:DD' },
|
||||
}
|
||||
)
|
||||
const nonAjaxRequest = httpServerMock.createKibanaRequest({
|
||||
socket: getMockSocket({
|
||||
authorized: true,
|
||||
peerCertificate: getMockPeerCertificate(['2A:7A:C2:DD', '3B:8B:D3:EE']),
|
||||
}),
|
||||
});
|
||||
const nonAjaxState = {
|
||||
accessToken: 'existing-token',
|
||||
peerCertificateFingerprint256: '2A:7A:C2:DD',
|
||||
};
|
||||
await expect(provider.authenticate(nonAjaxRequest, nonAjaxState)).resolves.toEqual(
|
||||
AuthenticationResult.succeeded(user, {
|
||||
authHeaders: { authorization: 'Bearer access-token' },
|
||||
state: { accessToken: 'access-token', peerCertificateFingerprint256: '2A:7A:C2:DD' },
|
||||
})
|
||||
);
|
||||
|
||||
expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1);
|
||||
const ajaxRequest = httpServerMock.createKibanaRequest({
|
||||
headers: { 'kbn-xsrf': 'xsrf' },
|
||||
socket: getMockSocket({
|
||||
authorized: true,
|
||||
peerCertificate: getMockPeerCertificate(['3A:7A:C2:DD', '3B:8B:D3:EE']),
|
||||
}),
|
||||
});
|
||||
const ajaxState = {
|
||||
accessToken: 'existing-token',
|
||||
peerCertificateFingerprint256: '3A:7A:C2:DD',
|
||||
};
|
||||
await expect(provider.authenticate(ajaxRequest, ajaxState)).resolves.toEqual(
|
||||
AuthenticationResult.succeeded(user, {
|
||||
authHeaders: { authorization: 'Bearer access-token' },
|
||||
state: { accessToken: 'access-token', peerCertificateFingerprint256: '3A:7A:C2:DD' },
|
||||
})
|
||||
);
|
||||
|
||||
const optionalAuthRequest = httpServerMock.createKibanaRequest({
|
||||
routeAuthRequired: false,
|
||||
socket: getMockSocket({
|
||||
authorized: true,
|
||||
peerCertificate: getMockPeerCertificate(['4A:7A:C2:DD', '3B:8B:D3:EE']),
|
||||
}),
|
||||
});
|
||||
const optionalAuthState = {
|
||||
accessToken: 'existing-token',
|
||||
peerCertificateFingerprint256: '4A:7A:C2:DD',
|
||||
};
|
||||
await expect(provider.authenticate(optionalAuthRequest, optionalAuthState)).resolves.toEqual(
|
||||
AuthenticationResult.succeeded(user, {
|
||||
authHeaders: { authorization: 'Bearer access-token' },
|
||||
state: { accessToken: 'access-token', peerCertificateFingerprint256: '4A:7A:C2:DD' },
|
||||
})
|
||||
);
|
||||
|
||||
expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(3);
|
||||
expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', {
|
||||
body: {
|
||||
x509_certificate_chain: [
|
||||
|
@ -422,32 +484,26 @@ describe('PKIAuthenticationProvider', () => {
|
|||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(request.headers).not.toHaveProperty('authorization');
|
||||
});
|
||||
|
||||
it('does not exchange peer certificate to a new access token even if existing token is expired and request does not require authentication.', async () => {
|
||||
const request = httpServerMock.createKibanaRequest({
|
||||
routeAuthRequired: false,
|
||||
socket: getMockSocket({
|
||||
authorized: true,
|
||||
peerCertificate: getMockPeerCertificate(['2A:7A:C2:DD', '3B:8B:D3:EE']),
|
||||
}),
|
||||
expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', {
|
||||
body: {
|
||||
x509_certificate_chain: [
|
||||
'fingerprint:3A:7A:C2:DD:base64',
|
||||
'fingerprint:3B:8B:D3:EE:base64',
|
||||
],
|
||||
},
|
||||
});
|
||||
expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', {
|
||||
body: {
|
||||
x509_certificate_chain: [
|
||||
'fingerprint:4A:7A:C2:DD:base64',
|
||||
'fingerprint:3B:8B:D3:EE:base64',
|
||||
],
|
||||
},
|
||||
});
|
||||
const state = { accessToken: 'existing-token', peerCertificateFingerprint256: '2A:7A:C2:DD' };
|
||||
|
||||
const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient();
|
||||
mockScopedClusterClient.callAsCurrentUser.mockRejectedValueOnce(
|
||||
LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error())
|
||||
);
|
||||
mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
|
||||
|
||||
await expect(provider.authenticate(request, state)).resolves.toEqual(
|
||||
AuthenticationResult.notHandled()
|
||||
);
|
||||
|
||||
expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
|
||||
expect(request.headers).not.toHaveProperty('authorization');
|
||||
expect(nonAjaxRequest.headers).not.toHaveProperty('authorization');
|
||||
expect(ajaxRequest.headers).not.toHaveProperty('authorization');
|
||||
expect(optionalAuthRequest.headers).not.toHaveProperty('authorization');
|
||||
});
|
||||
|
||||
it('fails with 401 if existing token is expired, but certificate is not present.', async () => {
|
||||
|
|
|
@ -10,6 +10,7 @@ import { KibanaRequest } from '../../../../../../src/core/server';
|
|||
import { AuthenticationResult } from '../authentication_result';
|
||||
import { DeauthenticationResult } from '../deauthentication_result';
|
||||
import { HTTPAuthorizationHeader } from '../http_authentication';
|
||||
import { canRedirectRequest } from '../can_redirect_request';
|
||||
import { Tokens } from '../tokens';
|
||||
import { BaseAuthenticationProvider } from './base';
|
||||
|
||||
|
@ -33,8 +34,9 @@ interface ProviderState {
|
|||
* @param request Request instance.
|
||||
*/
|
||||
function canStartNewSession(request: KibanaRequest) {
|
||||
// We should try to establish new session only if request requires authentication.
|
||||
return request.route.options.authRequired === true;
|
||||
// We should try to establish new session only if request requires authentication and it's not an XHR request.
|
||||
// Technically we can authenticate XHR requests too, but we don't want these to create a new session unintentionally.
|
||||
return canRedirectRequest(request) && request.route.options.authRequired === true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -75,12 +77,14 @@ export class PKIAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
authenticationResult = await this.authenticateViaState(request, state);
|
||||
|
||||
// If access token expired or doesn't match to the certificate fingerprint we should try to get
|
||||
// a new one in exchange to peer certificate chain assuming request can initiate new session.
|
||||
// a new one in exchange to peer certificate chain. Since we know that we had a valid session
|
||||
// before we can safely assume that it's desired to automatically re-create session even for XHR
|
||||
// requests.
|
||||
const invalidAccessToken =
|
||||
authenticationResult.notHandled() ||
|
||||
(authenticationResult.failed() &&
|
||||
Tokens.isAccessTokenExpiredError(authenticationResult.error));
|
||||
if (invalidAccessToken && canStartNewSession(request)) {
|
||||
if (invalidAccessToken) {
|
||||
authenticationResult = await this.authenticateViaPeerCertificate(request);
|
||||
// If we have an active session that we couldn't use to authenticate user and at the same time
|
||||
// we couldn't use peer's certificate to establish a new one, then we should respond with 401
|
||||
|
@ -88,14 +92,12 @@ export class PKIAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
if (authenticationResult.notHandled()) {
|
||||
return AuthenticationResult.failed(Boom.unauthorized());
|
||||
}
|
||||
} else if (invalidAccessToken) {
|
||||
return AuthenticationResult.notHandled();
|
||||
}
|
||||
}
|
||||
|
||||
// If we couldn't authenticate by means of all methods above, let's try to check if we can authenticate
|
||||
// request using its peer certificate chain, otherwise just return authentication result we have.
|
||||
// We shouldn't establish new session if authentication isn't required for this particular request.
|
||||
// If we couldn't authenticate by means of all methods above, let's check if the request is allowed
|
||||
// to start a new session, and if so try to authenticate request using its peer certificate chain,
|
||||
// otherwise just return authentication result we have.
|
||||
return authenticationResult.notHandled() && canStartNewSession(request)
|
||||
? await this.authenticateViaPeerCertificate(request)
|
||||
: authenticationResult;
|
||||
|
|
|
@ -32,19 +32,19 @@ const onlyNotInCoverageTests = [
|
|||
require.resolve('../test/detection_engine_api_integration/basic/config.ts'),
|
||||
require.resolve('../test/lists_api_integration/security_and_spaces/config.ts'),
|
||||
require.resolve('../test/plugin_api_integration/config.ts'),
|
||||
require.resolve('../test/kerberos_api_integration/config.ts'),
|
||||
require.resolve('../test/kerberos_api_integration/anonymous_access.config.ts'),
|
||||
require.resolve('../test/security_api_integration/saml.config.ts'),
|
||||
require.resolve('../test/security_api_integration/session_idle.config.ts'),
|
||||
require.resolve('../test/security_api_integration/session_lifespan.config.ts'),
|
||||
require.resolve('../test/security_api_integration/login_selector.config.ts'),
|
||||
require.resolve('../test/security_api_integration/audit.config.ts'),
|
||||
require.resolve('../test/security_api_integration/kerberos.config.ts'),
|
||||
require.resolve('../test/security_api_integration/kerberos_anonymous_access.config.ts'),
|
||||
require.resolve('../test/security_api_integration/pki.config.ts'),
|
||||
require.resolve('../test/security_api_integration/oidc.config.ts'),
|
||||
require.resolve('../test/security_api_integration/oidc_implicit_flow.config.ts'),
|
||||
require.resolve('../test/token_api_integration/config.js'),
|
||||
require.resolve('../test/oidc_api_integration/config.ts'),
|
||||
require.resolve('../test/oidc_api_integration/implicit_flow.config.ts'),
|
||||
require.resolve('../test/observability_api_integration/basic/config.ts'),
|
||||
require.resolve('../test/observability_api_integration/trial/config.ts'),
|
||||
require.resolve('../test/pki_api_integration/config.ts'),
|
||||
require.resolve('../test/encrypted_saved_objects_api_integration/config.ts'),
|
||||
require.resolve('../test/spaces_api_integration/spaces_only/config.ts'),
|
||||
require.resolve('../test/spaces_api_integration/security_and_spaces/config_trial.ts'),
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
/*
|
||||
* 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 { FtrProviderContext } from '../ftr_provider_context';
|
||||
|
||||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('apis Kerberos', function () {
|
||||
this.tags('ciGroup6');
|
||||
loadTestFile(require.resolve('./security'));
|
||||
});
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
/*
|
||||
* 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 { GenericFtrProviderContext } from '@kbn/test/types/ftr';
|
||||
|
||||
import { services } from './services';
|
||||
|
||||
export type FtrProviderContext = GenericFtrProviderContext<typeof services, {}>;
|
|
@ -1,15 +0,0 @@
|
|||
/*
|
||||
* 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 { services as commonServices } from '../common/services';
|
||||
import { services as apiIntegrationServices } from '../api_integration/services';
|
||||
|
||||
export const services = {
|
||||
...commonServices,
|
||||
legacyEs: apiIntegrationServices.legacyEs,
|
||||
esSupertest: apiIntegrationServices.esSupertest,
|
||||
supertestWithoutAuth: apiIntegrationServices.supertestWithoutAuth,
|
||||
};
|
|
@ -1,11 +0,0 @@
|
|||
/*
|
||||
* 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 { GenericFtrProviderContext } from '@kbn/test/types/ftr';
|
||||
|
||||
import { services } from './services';
|
||||
|
||||
export type FtrProviderContext = GenericFtrProviderContext<typeof services, {}>;
|
|
@ -1,14 +0,0 @@
|
|||
/*
|
||||
* 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 { services as commonServices } from '../common/services';
|
||||
import { services as apiIntegrationServices } from '../api_integration/services';
|
||||
|
||||
export const services = {
|
||||
...commonServices,
|
||||
legacyEs: apiIntegrationServices.legacyEs,
|
||||
supertestWithoutAuth: apiIntegrationServices.supertestWithoutAuth,
|
||||
};
|
|
@ -1,14 +0,0 @@
|
|||
/*
|
||||
* 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 { FtrProviderContext } from '../ftr_provider_context';
|
||||
|
||||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('apis PKI', function () {
|
||||
this.tags('ciGroup6');
|
||||
loadTestFile(require.resolve('./security'));
|
||||
});
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
/*
|
||||
* 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 { GenericFtrProviderContext } from '@kbn/test/types/ftr';
|
||||
|
||||
import { services } from './services';
|
||||
|
||||
export type FtrProviderContext = GenericFtrProviderContext<typeof services, {}>;
|
|
@ -1,14 +0,0 @@
|
|||
/*
|
||||
* 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 { services as commonServices } from '../common/services';
|
||||
import { services as apiIntegrationServices } from '../api_integration/services';
|
||||
|
||||
export const services = {
|
||||
...commonServices,
|
||||
esSupertest: apiIntegrationServices.esSupertest,
|
||||
supertestWithoutAuth: apiIntegrationServices.supertestWithoutAuth,
|
||||
};
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { PluginInitializer } from '../../../../../../src/core/server';
|
||||
import type { PluginInitializer } from '../../../../../../../src/core/server';
|
||||
import { initRoutes } from './init_routes';
|
||||
|
||||
export const plugin: PluginInitializer<void, void> = () => ({
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { IRouter } from '../../../../../../src/core/server';
|
||||
import type { IRouter } from '../../../../../../../src/core/server';
|
||||
import { createTokens } from '../../oidc_tools';
|
||||
|
||||
export function initRoutes(router: IRouter) {
|
|
@ -11,21 +11,15 @@ import { services } from './services';
|
|||
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||
const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts'));
|
||||
|
||||
const kerberosKeytabPath = resolve(
|
||||
__dirname,
|
||||
'../../test/kerberos_api_integration/fixtures/krb5.keytab'
|
||||
);
|
||||
const kerberosConfigPath = resolve(
|
||||
__dirname,
|
||||
'../../test/kerberos_api_integration/fixtures/krb5.conf'
|
||||
);
|
||||
const kerberosKeytabPath = resolve(__dirname, './fixtures/kerberos/krb5.keytab');
|
||||
const kerberosConfigPath = resolve(__dirname, './fixtures/kerberos/krb5.conf');
|
||||
|
||||
return {
|
||||
testFiles: [require.resolve('./apis')],
|
||||
testFiles: [require.resolve('./tests/kerberos')],
|
||||
servers: xPackAPITestsConfig.get('servers'),
|
||||
services,
|
||||
junit: {
|
||||
reportName: 'X-Pack Kerberos API Integration Tests',
|
||||
reportName: 'X-Pack Security API Integration Tests (Kerberos)',
|
||||
},
|
||||
|
||||
esTestCluster: {
|
|
@ -7,13 +7,13 @@
|
|||
import { FtrConfigProviderContext } from '@kbn/test/types/ftr';
|
||||
|
||||
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||
const kerberosAPITestsConfig = await readConfigFile(require.resolve('./config.ts'));
|
||||
const kerberosAPITestsConfig = await readConfigFile(require.resolve('./kerberos.config.ts'));
|
||||
|
||||
return {
|
||||
...kerberosAPITestsConfig.getAll(),
|
||||
|
||||
junit: {
|
||||
reportName: 'X-Pack Kerberos API with Anonymous Access Integration Tests',
|
||||
reportName: 'X-Pack Security API Integration Tests (Kerberos with Anonymous Access)',
|
||||
},
|
||||
|
||||
esTestCluster: {
|
|
@ -15,13 +15,13 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts'));
|
||||
const kibanaPort = xPackAPITestsConfig.get('servers.kibana.port');
|
||||
|
||||
const kerberosKeytabPath = resolve(__dirname, '../kerberos_api_integration/fixtures/krb5.keytab');
|
||||
const kerberosConfigPath = resolve(__dirname, '../kerberos_api_integration/fixtures/krb5.conf');
|
||||
const kerberosKeytabPath = resolve(__dirname, './fixtures/kerberos/krb5.keytab');
|
||||
const kerberosConfigPath = resolve(__dirname, './fixtures/kerberos/krb5.conf');
|
||||
|
||||
const oidcJWKSPath = resolve(__dirname, '../oidc_api_integration/fixtures/jwks.json');
|
||||
const oidcIdPPlugin = resolve(__dirname, '../oidc_api_integration/fixtures/oidc_provider');
|
||||
const oidcJWKSPath = resolve(__dirname, './fixtures/oidc/jwks.json');
|
||||
const oidcIdPPlugin = resolve(__dirname, './fixtures/oidc/oidc_provider');
|
||||
|
||||
const pkiKibanaCAPath = resolve(__dirname, '../pki_api_integration/fixtures/kibana_ca.crt');
|
||||
const pkiKibanaCAPath = resolve(__dirname, './fixtures/pki/kibana_ca.crt');
|
||||
|
||||
const saml1IdPMetadataPath = resolve(__dirname, './fixtures/saml/idp_metadata.xml');
|
||||
const saml2IdPMetadataPath = resolve(__dirname, './fixtures/saml/idp_metadata_2.xml');
|
||||
|
|
|
@ -10,17 +10,17 @@ import { services } from './services';
|
|||
|
||||
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||
const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts'));
|
||||
const plugin = resolve(__dirname, './fixtures/oidc_provider');
|
||||
const plugin = resolve(__dirname, './fixtures/oidc/oidc_provider');
|
||||
const kibanaPort = xPackAPITestsConfig.get('servers.kibana.port');
|
||||
const jwksPath = resolve(__dirname, './fixtures/jwks.json');
|
||||
const jwksPath = resolve(__dirname, './fixtures/oidc/jwks.json');
|
||||
|
||||
return {
|
||||
testFiles: [require.resolve('./apis/authorization_code_flow')],
|
||||
testFiles: [require.resolve('./tests/oidc/authorization_code_flow')],
|
||||
servers: xPackAPITestsConfig.get('servers'),
|
||||
security: { disableTestUser: true },
|
||||
services,
|
||||
junit: {
|
||||
reportName: 'X-Pack OpenID Connect API Integration Tests',
|
||||
reportName: 'X-Pack Security API Integration Tests (OIDC - Authorization Code Flow)',
|
||||
},
|
||||
|
||||
esTestCluster: {
|
|
@ -7,14 +7,14 @@
|
|||
import { FtrConfigProviderContext } from '@kbn/test/types/ftr';
|
||||
|
||||
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||
const oidcAPITestsConfig = await readConfigFile(require.resolve('./config.ts'));
|
||||
const oidcAPITestsConfig = await readConfigFile(require.resolve('./oidc.config.ts'));
|
||||
|
||||
return {
|
||||
...oidcAPITestsConfig.getAll(),
|
||||
testFiles: [require.resolve('./apis/implicit_flow')],
|
||||
testFiles: [require.resolve('./tests/oidc/implicit_flow')],
|
||||
|
||||
junit: {
|
||||
reportName: 'X-Pack OpenID Connect API Integration Tests (Implicit Flow)',
|
||||
reportName: 'X-Pack Security API Integration Tests (OIDC - Implicit Flow)',
|
||||
},
|
||||
|
||||
esTestCluster: {
|
|
@ -25,12 +25,12 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
};
|
||||
|
||||
return {
|
||||
testFiles: [require.resolve('./apis')],
|
||||
testFiles: [require.resolve('./tests/pki')],
|
||||
servers,
|
||||
security: { disableTestUser: true },
|
||||
services,
|
||||
junit: {
|
||||
reportName: 'X-Pack PKI API Integration Tests',
|
||||
reportName: 'X-Pack Security API Integration Tests (PKI)',
|
||||
},
|
||||
|
||||
esTestCluster: {
|
||||
|
@ -58,7 +58,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
`--server.ssl.certificate=${KBN_CERT_PATH}`,
|
||||
`--server.ssl.certificateAuthorities=${JSON.stringify([
|
||||
CA_CERT_PATH,
|
||||
resolve(__dirname, './fixtures/kibana_ca.crt'),
|
||||
resolve(__dirname, './fixtures/pki/kibana_ca.crt'),
|
||||
])}`,
|
||||
`--server.ssl.clientAuthentication=required`,
|
||||
`--elasticsearch.hosts=${servers.elasticsearch.protocol}://${servers.elasticsearch.hostname}:${servers.elasticsearch.port}`,
|
|
@ -9,5 +9,6 @@ import { services as apiIntegrationServices } from '../api_integration/services'
|
|||
|
||||
export const services = {
|
||||
...commonServices,
|
||||
esSupertest: apiIntegrationServices.esSupertest,
|
||||
supertestWithoutAuth: apiIntegrationServices.supertestWithoutAuth,
|
||||
};
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('security', () => {
|
||||
describe('security APIs - Kerberos', function () {
|
||||
this.tags('ciGroup6');
|
||||
|
||||
loadTestFile(require.resolve('./kerberos_login'));
|
||||
});
|
||||
}
|
|
@ -11,7 +11,7 @@ import { FtrProviderContext } from '../../ftr_provider_context';
|
|||
import {
|
||||
getMutualAuthenticationResponseToken,
|
||||
getSPNEGOToken,
|
||||
} from '../../fixtures/kerberos_tools';
|
||||
} from '../../fixtures/kerberos/kerberos_tools';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const spnegoToken = getSPNEGOToken();
|
||||
|
@ -92,21 +92,21 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
expect(spnegoResponse.headers['www-authenticate']).to.be('Negotiate');
|
||||
});
|
||||
|
||||
it('AJAX requests should properly initiate SPNEGO', async () => {
|
||||
it('AJAX requests should not initiate SPNEGO', async () => {
|
||||
const ajaxResponse = await supertest
|
||||
.get('/abc/xyz/spnego?one=two three')
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.expect(401);
|
||||
|
||||
expect(ajaxResponse.headers['set-cookie']).to.be(undefined);
|
||||
expect(ajaxResponse.headers['www-authenticate']).to.be('Negotiate');
|
||||
expect(ajaxResponse.headers['www-authenticate']).to.be(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('finishing SPNEGO', () => {
|
||||
it('should properly set cookie and authenticate user', async () => {
|
||||
const response = await supertest
|
||||
.get('/internal/security/me')
|
||||
.get('/security/account')
|
||||
.set('Authorization', `Negotiate ${spnegoToken}`)
|
||||
.expect(200);
|
||||
|
||||
|
@ -153,7 +153,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
|
||||
it('should re-initiate SPNEGO handshake if token is rejected with 401', async () => {
|
||||
const spnegoResponse = await supertest
|
||||
.get('/internal/security/me')
|
||||
.get('/security/account')
|
||||
.set('Authorization', `Negotiate ${Buffer.from('Hello').toString('base64')}`)
|
||||
.expect(401);
|
||||
expect(spnegoResponse.headers['set-cookie']).to.be(undefined);
|
||||
|
@ -162,7 +162,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
|
||||
it('should fail if SPNEGO token is rejected because of unknown reason', async () => {
|
||||
const spnegoResponse = await supertest
|
||||
.get('/internal/security/me')
|
||||
.get('/security/account')
|
||||
.set('Authorization', 'Negotiate (:I am malformed:)')
|
||||
.expect(500);
|
||||
expect(spnegoResponse.headers['set-cookie']).to.be(undefined);
|
||||
|
@ -175,7 +175,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
|
||||
beforeEach(async () => {
|
||||
const response = await supertest
|
||||
.get('/internal/security/me')
|
||||
.get('/security/account')
|
||||
.set('Authorization', `Negotiate ${spnegoToken}`)
|
||||
.expect(200);
|
||||
|
||||
|
@ -239,7 +239,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
it('should redirect to `logged_out` page after successful logout', async () => {
|
||||
// First authenticate user to retrieve session cookie.
|
||||
const response = await supertest
|
||||
.get('/internal/security/me')
|
||||
.get('/security/account')
|
||||
.set('Authorization', `Negotiate ${spnegoToken}`)
|
||||
.expect(200);
|
||||
|
||||
|
@ -274,7 +274,9 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
expect(cookies).to.have.length(1);
|
||||
checkCookieIsCleared(request.cookie(cookies[0])!);
|
||||
|
||||
expect(apiResponse.headers['www-authenticate']).to.be('Negotiate');
|
||||
// Request with a session cookie that is linked to an invalidated/non-existent session is treated the same as
|
||||
// request without any session cookie at all.
|
||||
expect(apiResponse.headers['www-authenticate']).to.be(undefined);
|
||||
});
|
||||
|
||||
it('should redirect to home page if session cookie is not provided', async () => {
|
||||
|
@ -290,7 +292,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
|
||||
beforeEach(async () => {
|
||||
const response = await supertest
|
||||
.get('/internal/security/me')
|
||||
.get('/security/account')
|
||||
.set('Authorization', `Negotiate ${spnegoToken}`)
|
||||
.expect(200);
|
||||
|
||||
|
@ -342,7 +344,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
// This request should succeed and automatically refresh token. Returned cookie will contain
|
||||
// the new access and refresh token pair.
|
||||
const nonAjaxResponse = await supertest
|
||||
.get('/app/kibana')
|
||||
.get('/security/account')
|
||||
.set('Cookie', sessionCookie.cookieString())
|
||||
.expect(200);
|
||||
|
||||
|
@ -368,7 +370,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
|
||||
beforeEach(async () => {
|
||||
const response = await supertest
|
||||
.get('/internal/security/me')
|
||||
.get('/security/account')
|
||||
.set('Authorization', `Negotiate ${spnegoToken}`)
|
||||
.expect(200);
|
||||
|
||||
|
@ -405,7 +407,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
|
||||
it('non-AJAX call should initiate SPNEGO and clear existing cookie', async function () {
|
||||
const nonAjaxResponse = await supertest
|
||||
.get('/')
|
||||
.get('/security/account')
|
||||
.set('Cookie', sessionCookie.cookieString())
|
||||
.expect(401);
|
||||
|
|
@ -11,11 +11,11 @@ import url from 'url';
|
|||
import { CA_CERT_PATH } from '@kbn/dev-utils';
|
||||
import expect from '@kbn/expect';
|
||||
import type { AuthenticationProvider } from '../../../../plugins/security/common/types';
|
||||
import { getStateAndNonce } from '../../../oidc_api_integration/fixtures/oidc_tools';
|
||||
import { getStateAndNonce } from '../../fixtures/oidc/oidc_tools';
|
||||
import {
|
||||
getMutualAuthenticationResponseToken,
|
||||
getSPNEGOToken,
|
||||
} from '../../../kerberos_api_integration/fixtures/kerberos_tools';
|
||||
} from '../../fixtures/kerberos/kerberos_tools';
|
||||
import { getSAMLRequestId, getSAMLResponse } from '../../fixtures/saml/saml_tools';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
|
@ -29,9 +29,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
const validPassword = kibanaServerConfig.password;
|
||||
|
||||
const CA_CERT = readFileSync(CA_CERT_PATH);
|
||||
const CLIENT_CERT = readFileSync(
|
||||
resolve(__dirname, '../../../pki_api_integration/fixtures/first_client.p12')
|
||||
);
|
||||
const CLIENT_CERT = readFileSync(resolve(__dirname, '../../fixtures/pki/first_client.p12'));
|
||||
|
||||
async function checkSessionCookie(
|
||||
sessionCookie: Cookie,
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('apis', function () {
|
||||
describe('security APIs - OIDC (Authorization Code Flow)', function () {
|
||||
this.tags('ciGroup6');
|
||||
loadTestFile(require.resolve('./oidc_auth'));
|
||||
});
|
|
@ -8,8 +8,8 @@ import expect from '@kbn/expect';
|
|||
import request, { Cookie } from 'request';
|
||||
import url from 'url';
|
||||
import { delay } from 'bluebird';
|
||||
import { getStateAndNonce } from '../../fixtures/oidc_tools';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { getStateAndNonce } from '../../../fixtures/oidc/oidc_tools';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertestWithoutAuth');
|
|
@ -4,10 +4,10 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('apis', function () {
|
||||
describe('security APIs - OIDC (Implicit Flow)', function () {
|
||||
this.tags('ciGroup6');
|
||||
loadTestFile(require.resolve('./oidc_auth'));
|
||||
});
|
|
@ -8,8 +8,8 @@ import expect from '@kbn/expect';
|
|||
import { JSDOM } from 'jsdom';
|
||||
import request, { Cookie } from 'request';
|
||||
import { format as formatURL } from 'url';
|
||||
import { createTokens, getStateAndNonce } from '../../fixtures/oidc_tools';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { createTokens, getStateAndNonce } from '../../../fixtures/oidc/oidc_tools';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertestWithoutAuth');
|
|
@ -7,7 +7,9 @@
|
|||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('security', () => {
|
||||
describe('security APIs - PKI', function () {
|
||||
this.tags('ciGroup6');
|
||||
|
||||
loadTestFile(require.resolve('./pki_auth'));
|
||||
});
|
||||
}
|
|
@ -13,10 +13,10 @@ import { CA_CERT_PATH } from '@kbn/dev-utils';
|
|||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
const CA_CERT = readFileSync(CA_CERT_PATH);
|
||||
const FIRST_CLIENT_CERT = readFileSync(resolve(__dirname, '../../fixtures/first_client.p12'));
|
||||
const SECOND_CLIENT_CERT = readFileSync(resolve(__dirname, '../../fixtures/second_client.p12'));
|
||||
const FIRST_CLIENT_CERT = readFileSync(resolve(__dirname, '../../fixtures/pki/first_client.p12'));
|
||||
const SECOND_CLIENT_CERT = readFileSync(resolve(__dirname, '../../fixtures/pki/second_client.p12'));
|
||||
const UNTRUSTED_CLIENT_CERT = readFileSync(
|
||||
resolve(__dirname, '../../fixtures/untrusted_client.p12')
|
||||
resolve(__dirname, '../../fixtures/pki/untrusted_client.p12')
|
||||
);
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
|
@ -97,9 +97,20 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
// Do not assert on the `authentication_realm`, as the value differs for on-prem vs cloud
|
||||
});
|
||||
|
||||
it('AJAX requests should not create a new session', async () => {
|
||||
const ajaxResponse = await supertest
|
||||
.get('/internal/security/me')
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.ca(CA_CERT)
|
||||
.pfx(FIRST_CLIENT_CERT)
|
||||
.expect(401);
|
||||
|
||||
expect(ajaxResponse.headers['set-cookie']).to.be(undefined);
|
||||
});
|
||||
|
||||
it('should properly set cookie and authenticate user', async () => {
|
||||
const response = await supertest
|
||||
.get('/internal/security/me')
|
||||
.get('/security/account')
|
||||
.ca(CA_CERT)
|
||||
.pfx(FIRST_CLIENT_CERT)
|
||||
.expect(200);
|
||||
|
@ -110,35 +121,34 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
const sessionCookie = request.cookie(cookies[0])!;
|
||||
checkCookieIsSet(sessionCookie);
|
||||
|
||||
expect(response.body).to.eql({
|
||||
username: 'first_client',
|
||||
roles: ['kibana_admin'],
|
||||
full_name: null,
|
||||
email: null,
|
||||
enabled: true,
|
||||
metadata: {
|
||||
pki_delegated_by_realm: 'reserved',
|
||||
pki_delegated_by_user: 'kibana',
|
||||
pki_dn: 'CN=first_client',
|
||||
},
|
||||
authentication_realm: { name: 'pki1', type: 'pki' },
|
||||
lookup_realm: { name: 'pki1', type: 'pki' },
|
||||
authentication_provider: { name: 'pki', type: 'pki' },
|
||||
authentication_type: 'token',
|
||||
});
|
||||
|
||||
// Cookie should be accepted.
|
||||
await supertest
|
||||
.get('/internal/security/me')
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.ca(CA_CERT)
|
||||
.pfx(FIRST_CLIENT_CERT)
|
||||
.set('Cookie', sessionCookie.cookieString())
|
||||
.expect(200);
|
||||
.expect(200, {
|
||||
username: 'first_client',
|
||||
roles: ['kibana_admin'],
|
||||
full_name: null,
|
||||
email: null,
|
||||
enabled: true,
|
||||
metadata: {
|
||||
pki_delegated_by_realm: 'reserved',
|
||||
pki_delegated_by_user: 'kibana',
|
||||
pki_dn: 'CN=first_client',
|
||||
},
|
||||
authentication_realm: { name: 'pki1', type: 'pki' },
|
||||
lookup_realm: { name: 'pki1', type: 'pki' },
|
||||
authentication_provider: { name: 'pki', type: 'pki' },
|
||||
authentication_type: 'token',
|
||||
});
|
||||
});
|
||||
|
||||
it('should update session if new certificate is provided', async () => {
|
||||
let response = await supertest
|
||||
.get('/internal/security/me')
|
||||
.get('/security/account')
|
||||
.ca(CA_CERT)
|
||||
.pfx(FIRST_CLIENT_CERT)
|
||||
.expect(200);
|
||||
|
@ -177,7 +187,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
|
||||
it('should reject valid cookie if used with untrusted certificate', async () => {
|
||||
const response = await supertest
|
||||
.get('/internal/security/me')
|
||||
.get('/security/account')
|
||||
.ca(CA_CERT)
|
||||
.pfx(FIRST_CLIENT_CERT)
|
||||
.expect(200);
|
||||
|
@ -201,7 +211,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
|
||||
beforeEach(async () => {
|
||||
const response = await supertest
|
||||
.get('/internal/security/me')
|
||||
.get('/security/account')
|
||||
.ca(CA_CERT)
|
||||
.pfx(FIRST_CLIENT_CERT)
|
||||
.expect(200);
|
||||
|
@ -274,7 +284,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
it('should redirect to `logged_out` page after successful logout', async () => {
|
||||
// First authenticate user to retrieve session cookie.
|
||||
const response = await supertest
|
||||
.get('/internal/security/me')
|
||||
.get('/security/account')
|
||||
.ca(CA_CERT)
|
||||
.pfx(FIRST_CLIENT_CERT)
|
||||
.expect(200);
|
||||
|
@ -317,7 +327,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
|
||||
beforeEach(async () => {
|
||||
const response = await supertest
|
||||
.get('/internal/security/me')
|
||||
.get('/security/account')
|
||||
.ca(CA_CERT)
|
||||
.pfx(FIRST_CLIENT_CERT)
|
||||
.expect(200);
|
||||
|
@ -363,7 +373,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
// This request should succeed and automatically refresh token. Returned cookie will contain
|
||||
// the new access and refresh token pair.
|
||||
const nonAjaxResponse = await supertest
|
||||
.get('/app/kibana')
|
||||
.get('/security/account')
|
||||
.ca(CA_CERT)
|
||||
.pfx(FIRST_CLIENT_CERT)
|
||||
.set('Cookie', sessionCookie.cookieString())
|
|
@ -20,8 +20,11 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
);
|
||||
|
||||
const kibanaPort = kibanaFunctionalConfig.get('servers.kibana.port');
|
||||
const jwksPath = resolve(__dirname, '../oidc_api_integration/fixtures/jwks.json');
|
||||
const oidcOpPPlugin = resolve(__dirname, '../oidc_api_integration/fixtures/oidc_provider');
|
||||
const jwksPath = resolve(__dirname, '../security_api_integration/fixtures/oidc/jwks.json');
|
||||
const oidcOpPPlugin = resolve(
|
||||
__dirname,
|
||||
'../security_api_integration/fixtures/oidc/oidc_provider'
|
||||
);
|
||||
|
||||
return {
|
||||
testFiles: [resolve(__dirname, './tests/oidc')],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue