diff --git a/docs/user/security/api-keys/index.asciidoc b/docs/user/security/api-keys/index.asciidoc index bc277609d43e4..3011f17ee08c8 100644 --- a/docs/user/security/api-keys/index.asciidoc +++ b/docs/user/security/api-keys/index.asciidoc @@ -45,6 +45,11 @@ curl --location --request GET 'http://localhost:5601/api/security/role' \ --header 'kbn-xsrf: true' \ --header 'Authorization: ApiKey aVZlLUMzSUJuYndxdDJvN0k1bU46aGxlYUpNS2lTa2FKeVZua1FnY1VEdw==' \ +[IMPORTANT] +============================================================================ +API keys are intended for programatic access to {kib} and {es). Do not use API keys to authenticate access via a web browser. +============================================================================ + [float] [[view-api-keys]] === View and delete API keys diff --git a/docs/user/security/authentication/index.asciidoc b/docs/user/security/authentication/index.asciidoc index 007d1af017df3..9b3d4b0f831f8 100644 --- a/docs/user/security/authentication/index.asciidoc +++ b/docs/user/security/authentication/index.asciidoc @@ -394,6 +394,12 @@ HTTP protocol provides a simple authentication framework that can be used by a c This type of authentication is usually useful for machine-to-machine interaction that requires authentication and where human intervention is not desired or just infeasible. There are a number of use cases when HTTP authentication support comes in handy for {kib} users as well. +[IMPORTANT] +============================================================================ +API keys are intended for programatic access to {kib} and {es). Do not use API keys to authenticate access via a web browser. +============================================================================ + + By default {kib} supports <> authentication scheme _and_ any scheme supported by the currently enabled authentication provider. For example, `Basic` authentication scheme is automatically supported when basic authentication provider is enabled, or `Bearer` scheme when any of the token based authentication providers is enabled (Token, SAML, OpenID Connect, PKI or Kerberos). But it's also possible to add support for any other authentication scheme in the `kibana.yml` configuration file, as follows: NOTE: Don't forget to explicitly specify the default `apikey` and `bearer` schemes when you just want to add a new one to the list. diff --git a/x-pack/plugins/security/server/routes/analytics/authentication_type.test.ts b/x-pack/plugins/security/server/routes/analytics/authentication_type.test.ts index 3ea35308347a1..5a298421815e4 100644 --- a/x-pack/plugins/security/server/routes/analytics/authentication_type.test.ts +++ b/x-pack/plugins/security/server/routes/analytics/authentication_type.test.ts @@ -19,6 +19,7 @@ import { routeDefinitionParamsMock } from '../index.mock'; import { defineRecordAnalyticsOnAuthTypeRoutes } from './authentication_type'; const FAKE_TIMESTAMP = 1637665318135; + function getMockContext( licenseCheckResult: { state: string; message?: string } = { state: 'valid' } ) { @@ -34,6 +35,7 @@ describe('POST /internal/security/analytics/_record_auth_type', () => { let routeHandler: RequestHandler; let routeParamsMock: DeeplyMockedKeys; + beforeEach(() => { routeParamsMock = routeDefinitionParamsMock.create(); defineRecordAnalyticsOnAuthTypeRoutes(routeParamsMock); @@ -49,6 +51,10 @@ describe('POST /internal/security/analytics/_record_auth_type', () => { const response = await routeHandler(getMockContext(), request, kibanaResponseFactory); expect(response.status).toBe(204); + expect(routeParamsMock.logger.warn).toBeCalledWith( + 'Cannot record authentication type: current user could not be retrieved.' + ); + expect(routeParamsMock.analyticsService.reportAuthenticationTypeEvent).not.toHaveBeenCalled(); }); @@ -294,11 +300,13 @@ describe('POST /internal/security/analytics/_record_auth_type', () => { routeParamsMock.getAuthenticationService.mockReturnValue(mockAuthc); const response = await routeHandler(getMockContext(), request, kibanaResponseFactory); + expect(response.status).toBe(200); expect(response.payload).toEqual({ timestamp: FAKE_TIMESTAMP, signature: 'f4f6b485690816127c33d5aa13cd6cd12c9892641ba23b5d58e5c6590cd43db0', }); + routeParamsMock.analyticsService.reportAuthenticationTypeEvent.mockClear(); initialTimestamp = response.payload.timestamp; @@ -312,13 +320,13 @@ describe('POST /internal/security/analytics/_record_auth_type', () => { }); const response = await routeHandler(getMockContext(), request, kibanaResponseFactory); + expect(response.status).toBe(200); expect(response.payload).toEqual({ timestamp: initialTimestamp, signature: '46d5841ad21d29ca6c7c1c639adc6294c176c394adb0b40dfc05797cfe29218e', }); expect(response.payload.signature).not.toEqual(initialSignature); - expect(routeParamsMock.analyticsService.reportAuthenticationTypeEvent).toHaveBeenCalledTimes( 1 ); @@ -329,4 +337,68 @@ describe('POST /internal/security/analytics/_record_auth_type', () => { }); }); }); + + describe('logApiKeyWithInteractiveUserDeprecated', () => { + it('should log a deprecation warning if API key is being used for access via a web browser', async () => { + const request = httpServerMock.createKibanaRequest({ + headers: { authorization: 'ApiKey xxxx' }, + }); + + const mockAuthc = authenticationServiceMock.createStart(); + + mockAuthc.getCurrentUser.mockReturnValue( + mockAuthenticatedUser({ + authentication_provider: { type: 'http', name: '__http__' }, + }) + ); + + routeParamsMock.getAuthenticationService.mockReturnValue(mockAuthc); + + const response = await routeHandler(getMockContext(), request, kibanaResponseFactory); + + expect(response.status).toBe(200); + expect(routeParamsMock.analyticsService.reportAuthenticationTypeEvent).toHaveBeenCalledTimes( + 1 + ); + expect(routeParamsMock.analyticsService.reportAuthenticationTypeEvent).toHaveBeenCalledWith({ + authenticationProviderType: 'http', + authenticationRealmType: 'native', + httpAuthenticationScheme: 'ApiKey', + }); + expect(routeParamsMock.logger.warn).toHaveBeenCalledTimes(1); + expect(routeParamsMock.logger.warn).toBeCalledWith( + 'API keys are intended for programmatic access. Do not use API keys to authenticate access via a web browser.', + { tags: ['deprecation'] } + ); + }); + + it('should not log a deprecation warning if other http auth scheme is being used for access via a web browser', async () => { + const request = httpServerMock.createKibanaRequest({ + headers: { authorization: 'Basic' }, + }); + + const mockAuthc = authenticationServiceMock.createStart(); + + mockAuthc.getCurrentUser.mockReturnValue( + mockAuthenticatedUser({ + authentication_provider: { type: 'http', name: '__http__' }, + }) + ); + + routeParamsMock.getAuthenticationService.mockReturnValue(mockAuthc); + + const response = await routeHandler(getMockContext(), request, kibanaResponseFactory); + + expect(response.status).toBe(200); + expect(routeParamsMock.analyticsService.reportAuthenticationTypeEvent).toHaveBeenCalledTimes( + 1 + ); + expect(routeParamsMock.analyticsService.reportAuthenticationTypeEvent).toHaveBeenCalledWith({ + authenticationProviderType: 'http', + authenticationRealmType: 'native', + httpAuthenticationScheme: 'Basic', + }); + expect(routeParamsMock.logger.warn).toHaveBeenCalledTimes(0); + }); + }); }); diff --git a/x-pack/plugins/security/server/routes/analytics/authentication_type.ts b/x-pack/plugins/security/server/routes/analytics/authentication_type.ts index c3246667c1aa7..f2bf76c71b1ab 100644 --- a/x-pack/plugins/security/server/routes/analytics/authentication_type.ts +++ b/x-pack/plugins/security/server/routes/analytics/authentication_type.ts @@ -8,6 +8,7 @@ import { createHash } from 'crypto'; import { schema } from '@kbn/config-schema'; +import type { Logger } from '@kbn/logging'; import type { RouteDefinitionParams } from '..'; import type { AuthenticationTypeAnalyticsEvent } from '../../analytics'; @@ -74,6 +75,11 @@ export function defineRecordAnalyticsOnAuthTypeRoutes({ previouslyRegisteredSignature !== signature ) { analyticsService.reportAuthenticationTypeEvent(authTypeEventToReport); + + logApiKeyWithInteractiveUserDeprecated( + authTypeEventToReport.httpAuthenticationScheme, + logger + ); } else { timestamp = previousRegistrationTimestamp; } @@ -88,3 +94,23 @@ export function defineRecordAnalyticsOnAuthTypeRoutes({ }) ); } + +/** + * API Key authentication by interactive users is deprecated, this method logs a deprecation warning. + * + * @param httpAuthenticationScheme A string representing the authentication type event's scheme (ApiKey, etc.) by an interactive user. + * @param logger A reference to the Logger to log the deprecation message. + */ +function logApiKeyWithInteractiveUserDeprecated( + httpAuthenticationScheme: string = '', + logger: Logger +): void { + const isUsingApiKey = httpAuthenticationScheme?.toLowerCase() === 'apikey'; + + if (isUsingApiKey) { + logger.warn( + `API keys are intended for programmatic access. Do not use API keys to authenticate access via a web browser.`, + { tags: ['deprecation'] } + ); + } +}