diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts index 4ea395e7b53de..af26d1e60414a 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts @@ -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); }); }); diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.ts index 34ed9ac920e93..fa578b9dca45f 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.ts @@ -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 { diff --git a/x-pack/plugins/security/server/authentication/providers/pki.test.ts b/x-pack/plugins/security/server/authentication/providers/pki.test.ts index 969682b225ceb..94308ab5f2403 100644 --- a/x-pack/plugins/security/server/authentication/providers/pki.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/pki.test.ts @@ -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,55 +408,102 @@ 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); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', { - body: { - x509_certificate_chain: [ - 'fingerprint:2A:7A:C2:DD:base64', - 'fingerprint:3B:8B:D3:EE:base64', - ], - }, + 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' }, + }) + ); - 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({ + const optionalAuthRequest = httpServerMock.createKibanaRequest({ routeAuthRequired: false, socket: getMockSocket({ authorized: true, - peerCertificate: getMockPeerCertificate(['2A:7A:C2:DD', '3B:8B:D3:EE']), + peerCertificate: getMockPeerCertificate(['4A:7A:C2:DD', '3B:8B:D3:EE']), }), }); - const state = { accessToken: 'existing-token', peerCertificateFingerprint256: '2A:7A:C2:DD' }; - - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValueOnce( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + 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' }, + }) ); - mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); - await expect(provider.authenticate(request, state)).resolves.toEqual( - AuthenticationResult.notHandled() - ); + expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(3); + expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', { + body: { + x509_certificate_chain: [ + 'fingerprint:2A:7A:C2:DD:base64', + 'fingerprint:3B:8B:D3:EE:base64', + ], + }, + }); + 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', + ], + }, + }); - 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 () => { diff --git a/x-pack/plugins/security/server/authentication/providers/pki.ts b/x-pack/plugins/security/server/authentication/providers/pki.ts index 9214a025484fe..3629a0ac34f02 100644 --- a/x-pack/plugins/security/server/authentication/providers/pki.ts +++ b/x-pack/plugins/security/server/authentication/providers/pki.ts @@ -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; diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index e5e1dceb6ac89..234071d36998d 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -30,19 +30,19 @@ require('@kbn/test').runTestsCli([ 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'), - require.resolve('../test/kerberos_api_integration/anonymous_access.config'), 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/oidc_api_integration/config.ts'), - require.resolve('../test/oidc_api_integration/implicit_flow.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/observability_api_integration/basic/config.ts'), require.resolve('../test/observability_api_integration/trial/config.ts'), - require.resolve('../test/pki_api_integration/config'), require.resolve('../test/encrypted_saved_objects_api_integration/config'), require.resolve('../test/spaces_api_integration/spaces_only/config'), require.resolve('../test/spaces_api_integration/security_and_spaces/config_trial'), diff --git a/x-pack/test/kerberos_api_integration/apis/index.ts b/x-pack/test/kerberos_api_integration/apis/index.ts deleted file mode 100644 index 17da3ea7acc8d..0000000000000 --- a/x-pack/test/kerberos_api_integration/apis/index.ts +++ /dev/null @@ -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')); - }); -} diff --git a/x-pack/test/kerberos_api_integration/ftr_provider_context.d.ts b/x-pack/test/kerberos_api_integration/ftr_provider_context.d.ts deleted file mode 100644 index e3add3748f56d..0000000000000 --- a/x-pack/test/kerberos_api_integration/ftr_provider_context.d.ts +++ /dev/null @@ -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; diff --git a/x-pack/test/kerberos_api_integration/services.ts b/x-pack/test/kerberos_api_integration/services.ts deleted file mode 100644 index dadae9c331a46..0000000000000 --- a/x-pack/test/kerberos_api_integration/services.ts +++ /dev/null @@ -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, -}; diff --git a/x-pack/test/oidc_api_integration/ftr_provider_context.d.ts b/x-pack/test/oidc_api_integration/ftr_provider_context.d.ts deleted file mode 100644 index e3add3748f56d..0000000000000 --- a/x-pack/test/oidc_api_integration/ftr_provider_context.d.ts +++ /dev/null @@ -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; diff --git a/x-pack/test/oidc_api_integration/services.ts b/x-pack/test/oidc_api_integration/services.ts deleted file mode 100644 index e2abfa71451bc..0000000000000 --- a/x-pack/test/oidc_api_integration/services.ts +++ /dev/null @@ -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, -}; diff --git a/x-pack/test/pki_api_integration/apis/index.ts b/x-pack/test/pki_api_integration/apis/index.ts deleted file mode 100644 index 01b537fc07d1b..0000000000000 --- a/x-pack/test/pki_api_integration/apis/index.ts +++ /dev/null @@ -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')); - }); -} diff --git a/x-pack/test/pki_api_integration/ftr_provider_context.d.ts b/x-pack/test/pki_api_integration/ftr_provider_context.d.ts deleted file mode 100644 index e3add3748f56d..0000000000000 --- a/x-pack/test/pki_api_integration/ftr_provider_context.d.ts +++ /dev/null @@ -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; diff --git a/x-pack/test/pki_api_integration/services.ts b/x-pack/test/pki_api_integration/services.ts deleted file mode 100644 index 73ec6fe396392..0000000000000 --- a/x-pack/test/pki_api_integration/services.ts +++ /dev/null @@ -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, -}; diff --git a/x-pack/test/kerberos_api_integration/fixtures/README.md b/x-pack/test/security_api_integration/fixtures/kerberos/README.md similarity index 100% rename from x-pack/test/kerberos_api_integration/fixtures/README.md rename to x-pack/test/security_api_integration/fixtures/kerberos/README.md diff --git a/x-pack/test/kerberos_api_integration/fixtures/kerberos_tools.ts b/x-pack/test/security_api_integration/fixtures/kerberos/kerberos_tools.ts similarity index 100% rename from x-pack/test/kerberos_api_integration/fixtures/kerberos_tools.ts rename to x-pack/test/security_api_integration/fixtures/kerberos/kerberos_tools.ts diff --git a/x-pack/test/kerberos_api_integration/fixtures/krb5.conf b/x-pack/test/security_api_integration/fixtures/kerberos/krb5.conf similarity index 100% rename from x-pack/test/kerberos_api_integration/fixtures/krb5.conf rename to x-pack/test/security_api_integration/fixtures/kerberos/krb5.conf diff --git a/x-pack/test/kerberos_api_integration/fixtures/krb5.keytab b/x-pack/test/security_api_integration/fixtures/kerberos/krb5.keytab similarity index 100% rename from x-pack/test/kerberos_api_integration/fixtures/krb5.keytab rename to x-pack/test/security_api_integration/fixtures/kerberos/krb5.keytab diff --git a/x-pack/test/oidc_api_integration/fixtures/README.md b/x-pack/test/security_api_integration/fixtures/oidc/README.md similarity index 100% rename from x-pack/test/oidc_api_integration/fixtures/README.md rename to x-pack/test/security_api_integration/fixtures/oidc/README.md diff --git a/x-pack/test/oidc_api_integration/fixtures/jwks.json b/x-pack/test/security_api_integration/fixtures/oidc/jwks.json similarity index 100% rename from x-pack/test/oidc_api_integration/fixtures/jwks.json rename to x-pack/test/security_api_integration/fixtures/oidc/jwks.json diff --git a/x-pack/test/oidc_api_integration/fixtures/jwks_private.pem b/x-pack/test/security_api_integration/fixtures/oidc/jwks_private.pem similarity index 100% rename from x-pack/test/oidc_api_integration/fixtures/jwks_private.pem rename to x-pack/test/security_api_integration/fixtures/oidc/jwks_private.pem diff --git a/x-pack/test/oidc_api_integration/fixtures/jwks_public.pem b/x-pack/test/security_api_integration/fixtures/oidc/jwks_public.pem similarity index 100% rename from x-pack/test/oidc_api_integration/fixtures/jwks_public.pem rename to x-pack/test/security_api_integration/fixtures/oidc/jwks_public.pem diff --git a/x-pack/test/oidc_api_integration/fixtures/oidc_provider/kibana.json b/x-pack/test/security_api_integration/fixtures/oidc/oidc_provider/kibana.json similarity index 100% rename from x-pack/test/oidc_api_integration/fixtures/oidc_provider/kibana.json rename to x-pack/test/security_api_integration/fixtures/oidc/oidc_provider/kibana.json diff --git a/x-pack/test/oidc_api_integration/fixtures/oidc_provider/server/index.ts b/x-pack/test/security_api_integration/fixtures/oidc/oidc_provider/server/index.ts similarity index 85% rename from x-pack/test/oidc_api_integration/fixtures/oidc_provider/server/index.ts rename to x-pack/test/security_api_integration/fixtures/oidc/oidc_provider/server/index.ts index 58401e725830f..082fec55c3413 100644 --- a/x-pack/test/oidc_api_integration/fixtures/oidc_provider/server/index.ts +++ b/x-pack/test/security_api_integration/fixtures/oidc/oidc_provider/server/index.ts @@ -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 = () => ({ diff --git a/x-pack/test/oidc_api_integration/fixtures/oidc_provider/server/init_routes.ts b/x-pack/test/security_api_integration/fixtures/oidc/oidc_provider/server/init_routes.ts similarity index 98% rename from x-pack/test/oidc_api_integration/fixtures/oidc_provider/server/init_routes.ts rename to x-pack/test/security_api_integration/fixtures/oidc/oidc_provider/server/init_routes.ts index 73f92139806e3..8f75246d995c3 100644 --- a/x-pack/test/oidc_api_integration/fixtures/oidc_provider/server/init_routes.ts +++ b/x-pack/test/security_api_integration/fixtures/oidc/oidc_provider/server/init_routes.ts @@ -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) { diff --git a/x-pack/test/oidc_api_integration/fixtures/oidc_tools.ts b/x-pack/test/security_api_integration/fixtures/oidc/oidc_tools.ts similarity index 100% rename from x-pack/test/oidc_api_integration/fixtures/oidc_tools.ts rename to x-pack/test/security_api_integration/fixtures/oidc/oidc_tools.ts diff --git a/x-pack/test/pki_api_integration/fixtures/README.md b/x-pack/test/security_api_integration/fixtures/pki/README.md similarity index 100% rename from x-pack/test/pki_api_integration/fixtures/README.md rename to x-pack/test/security_api_integration/fixtures/pki/README.md diff --git a/x-pack/test/pki_api_integration/fixtures/first_client.p12 b/x-pack/test/security_api_integration/fixtures/pki/first_client.p12 similarity index 100% rename from x-pack/test/pki_api_integration/fixtures/first_client.p12 rename to x-pack/test/security_api_integration/fixtures/pki/first_client.p12 diff --git a/x-pack/test/pki_api_integration/fixtures/kibana_ca.crt b/x-pack/test/security_api_integration/fixtures/pki/kibana_ca.crt similarity index 100% rename from x-pack/test/pki_api_integration/fixtures/kibana_ca.crt rename to x-pack/test/security_api_integration/fixtures/pki/kibana_ca.crt diff --git a/x-pack/test/pki_api_integration/fixtures/kibana_ca.key b/x-pack/test/security_api_integration/fixtures/pki/kibana_ca.key similarity index 100% rename from x-pack/test/pki_api_integration/fixtures/kibana_ca.key rename to x-pack/test/security_api_integration/fixtures/pki/kibana_ca.key diff --git a/x-pack/test/pki_api_integration/fixtures/second_client.p12 b/x-pack/test/security_api_integration/fixtures/pki/second_client.p12 similarity index 100% rename from x-pack/test/pki_api_integration/fixtures/second_client.p12 rename to x-pack/test/security_api_integration/fixtures/pki/second_client.p12 diff --git a/x-pack/test/pki_api_integration/fixtures/untrusted_client.p12 b/x-pack/test/security_api_integration/fixtures/pki/untrusted_client.p12 similarity index 100% rename from x-pack/test/pki_api_integration/fixtures/untrusted_client.p12 rename to x-pack/test/security_api_integration/fixtures/pki/untrusted_client.p12 diff --git a/x-pack/test/kerberos_api_integration/config.ts b/x-pack/test/security_api_integration/kerberos.config.ts similarity index 82% rename from x-pack/test/kerberos_api_integration/config.ts rename to x-pack/test/security_api_integration/kerberos.config.ts index 6dab1213de992..11686dcd66678 100644 --- a/x-pack/test/kerberos_api_integration/config.ts +++ b/x-pack/test/security_api_integration/kerberos.config.ts @@ -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: { diff --git a/x-pack/test/kerberos_api_integration/anonymous_access.config.ts b/x-pack/test/security_api_integration/kerberos_anonymous_access.config.ts similarity index 87% rename from x-pack/test/kerberos_api_integration/anonymous_access.config.ts rename to x-pack/test/security_api_integration/kerberos_anonymous_access.config.ts index 17362831a6cd0..6621b536c7ca9 100644 --- a/x-pack/test/kerberos_api_integration/anonymous_access.config.ts +++ b/x-pack/test/security_api_integration/kerberos_anonymous_access.config.ts @@ -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: { diff --git a/x-pack/test/security_api_integration/login_selector.config.ts b/x-pack/test/security_api_integration/login_selector.config.ts index 0e43715ba808e..9688d42cb4361 100644 --- a/x-pack/test/security_api_integration/login_selector.config.ts +++ b/x-pack/test/security_api_integration/login_selector.config.ts @@ -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'); diff --git a/x-pack/test/oidc_api_integration/config.ts b/x-pack/test/security_api_integration/oidc.config.ts similarity index 89% rename from x-pack/test/oidc_api_integration/config.ts rename to x-pack/test/security_api_integration/oidc.config.ts index 08aa0a6d9c0dd..cb92282b40d32 100644 --- a/x-pack/test/oidc_api_integration/config.ts +++ b/x-pack/test/security_api_integration/oidc.config.ts @@ -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: { diff --git a/x-pack/test/oidc_api_integration/implicit_flow.config.ts b/x-pack/test/security_api_integration/oidc_implicit_flow.config.ts similarity index 88% rename from x-pack/test/oidc_api_integration/implicit_flow.config.ts rename to x-pack/test/security_api_integration/oidc_implicit_flow.config.ts index 992115d05c5a8..8907998f4df6d 100644 --- a/x-pack/test/oidc_api_integration/implicit_flow.config.ts +++ b/x-pack/test/security_api_integration/oidc_implicit_flow.config.ts @@ -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: { diff --git a/x-pack/test/pki_api_integration/config.ts b/x-pack/test/security_api_integration/pki.config.ts similarity index 93% rename from x-pack/test/pki_api_integration/config.ts rename to x-pack/test/security_api_integration/pki.config.ts index 5ce3111530dd9..1ce8bf9971fe0 100644 --- a/x-pack/test/pki_api_integration/config.ts +++ b/x-pack/test/security_api_integration/pki.config.ts @@ -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}`, diff --git a/x-pack/test/security_api_integration/services.ts b/x-pack/test/security_api_integration/services.ts index a8d8048462693..73ec6fe396392 100644 --- a/x-pack/test/security_api_integration/services.ts +++ b/x-pack/test/security_api_integration/services.ts @@ -9,5 +9,6 @@ import { services as apiIntegrationServices } from '../api_integration/services' export const services = { ...commonServices, + esSupertest: apiIntegrationServices.esSupertest, supertestWithoutAuth: apiIntegrationServices.supertestWithoutAuth, }; diff --git a/x-pack/test/kerberos_api_integration/apis/security/index.ts b/x-pack/test/security_api_integration/tests/kerberos/index.ts similarity index 84% rename from x-pack/test/kerberos_api_integration/apis/security/index.ts rename to x-pack/test/security_api_integration/tests/kerberos/index.ts index 77a053ab14748..3fa2d155353a7 100644 --- a/x-pack/test/kerberos_api_integration/apis/security/index.ts +++ b/x-pack/test/security_api_integration/tests/kerberos/index.ts @@ -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')); }); } diff --git a/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts b/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts similarity index 95% rename from x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts rename to x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts index c31f6b689e972..e63f8cd2ebe32 100644 --- a/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts +++ b/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts @@ -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); diff --git a/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts b/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts index 432fd6ff91280..cf141972b044a 100644 --- a/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts +++ b/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts @@ -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, diff --git a/x-pack/test/oidc_api_integration/apis/implicit_flow/index.ts b/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/index.ts similarity index 73% rename from x-pack/test/oidc_api_integration/apis/implicit_flow/index.ts rename to x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/index.ts index 0acae074f129f..4def5388abae0 100644 --- a/x-pack/test/oidc_api_integration/apis/implicit_flow/index.ts +++ b/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/index.ts @@ -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')); }); diff --git a/x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.ts b/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/oidc_auth.ts similarity index 99% rename from x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.ts rename to x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/oidc_auth.ts index 1fdb15a86ce0a..aac41374734b2 100644 --- a/x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.ts +++ b/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/oidc_auth.ts @@ -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'); diff --git a/x-pack/test/oidc_api_integration/apis/authorization_code_flow/index.ts b/x-pack/test/security_api_integration/tests/oidc/implicit_flow/index.ts similarity index 74% rename from x-pack/test/oidc_api_integration/apis/authorization_code_flow/index.ts rename to x-pack/test/security_api_integration/tests/oidc/implicit_flow/index.ts index 0acae074f129f..0441d14b9196d 100644 --- a/x-pack/test/oidc_api_integration/apis/authorization_code_flow/index.ts +++ b/x-pack/test/security_api_integration/tests/oidc/implicit_flow/index.ts @@ -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')); }); diff --git a/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts b/x-pack/test/security_api_integration/tests/oidc/implicit_flow/oidc_auth.ts similarity index 97% rename from x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts rename to x-pack/test/security_api_integration/tests/oidc/implicit_flow/oidc_auth.ts index 7c408d8b903e3..ced9598809e10 100644 --- a/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts +++ b/x-pack/test/security_api_integration/tests/oidc/implicit_flow/oidc_auth.ts @@ -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'); diff --git a/x-pack/test/pki_api_integration/apis/security/index.ts b/x-pack/test/security_api_integration/tests/pki/index.ts similarity index 85% rename from x-pack/test/pki_api_integration/apis/security/index.ts rename to x-pack/test/security_api_integration/tests/pki/index.ts index 63dca75d075fa..380335ba25f84 100644 --- a/x-pack/test/pki_api_integration/apis/security/index.ts +++ b/x-pack/test/security_api_integration/tests/pki/index.ts @@ -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')); }); } diff --git a/x-pack/test/pki_api_integration/apis/security/pki_auth.ts b/x-pack/test/security_api_integration/tests/pki/pki_auth.ts similarity index 90% rename from x-pack/test/pki_api_integration/apis/security/pki_auth.ts rename to x-pack/test/security_api_integration/tests/pki/pki_auth.ts index 43b728d12311d..0331f756712ca 100644 --- a/x-pack/test/pki_api_integration/apis/security/pki_auth.ts +++ b/x-pack/test/security_api_integration/tests/pki/pki_auth.ts @@ -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()) diff --git a/x-pack/test/security_functional/oidc.config.ts b/x-pack/test/security_functional/oidc.config.ts index 1ed5d51098420..add6f0f164b3a 100644 --- a/x-pack/test/security_functional/oidc.config.ts +++ b/x-pack/test/security_functional/oidc.config.ts @@ -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')],