From b03237a72d6675bd6deb976a73c2dddcdcb6b445 Mon Sep 17 00:00:00 2001 From: Josh Dover <1813008+joshdover@users.noreply.github.com> Date: Mon, 11 Oct 2021 18:16:26 +0200 Subject: [PATCH] Enable `bearer` scheme by default to support service token authorization (#112654) Co-authored-by: Aleh Zasypkin --- docs/settings/security-settings.asciidoc | 2 +- .../security/authentication/index.asciidoc | 6 +- .../authentication/authenticator.test.ts | 6 +- x-pack/plugins/security/server/config.test.ts | 9 ++ x-pack/plugins/security/server/config.ts | 2 +- .../security_usage_collector.test.ts | 2 +- x-pack/scripts/functional_tests.js | 1 + .../http_bearer.config.ts | 36 ++++++ .../tests/http_bearer/header.ts | 103 ++++++++++++++++++ .../tests/http_bearer/index.ts | 15 +++ 10 files changed, 174 insertions(+), 8 deletions(-) create mode 100644 x-pack/test/security_api_integration/http_bearer.config.ts create mode 100644 x-pack/test/security_api_integration/tests/http_bearer/header.ts create mode 100644 x-pack/test/security_api_integration/tests/http_bearer/index.ts diff --git a/docs/settings/security-settings.asciidoc b/docs/settings/security-settings.asciidoc index 906af1dfbb28..11072509da1f 100644 --- a/docs/settings/security-settings.asciidoc +++ b/docs/settings/security-settings.asciidoc @@ -218,7 +218,7 @@ There is a very limited set of cases when you'd want to change these settings. F | Determines if HTTP authentication schemes used by the enabled authentication providers should be automatically supported during HTTP authentication. By default, this setting is set to `true`. | `xpack.security.authc.http.schemes[]` -| List of HTTP authentication schemes that {kib} HTTP authentication should support. By default, this setting is set to `['apikey']` to support HTTP authentication with <> scheme. +| List of HTTP authentication schemes that {kib} HTTP authentication should support. By default, this setting is set to `['apikey', 'bearer']` to support HTTP authentication with the <> and <> schemes. |=== diff --git a/docs/user/security/authentication/index.asciidoc b/docs/user/security/authentication/index.asciidoc index bc564308c057..2f2b27938979 100644 --- a/docs/user/security/authentication/index.asciidoc +++ b/docs/user/security/authentication/index.asciidoc @@ -437,14 +437,14 @@ This type of authentication is usually useful for machine-to-machine interaction 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 default `apikey` scheme when you just want to add a new one to the list. +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. [source,yaml] -------------------------------------------------------------------------------- -xpack.security.authc.http.schemes: [apikey, basic, something-custom] +xpack.security.authc.http.schemes: [apikey, bearer, basic, something-custom] -------------------------------------------------------------------------------- -With this configuration, you can send requests to {kib} with the `Authorization` header using `ApiKey`, `Basic` or `Something-Custom` HTTP schemes (case insensitive). Under the hood, {kib} relays this header to {es}, then {es} authenticates the request using the credentials in the header. +With this configuration, you can send requests to {kib} with the `Authorization` header using `ApiKey`, `Bearer`, `Basic` or `Something-Custom` HTTP schemes (case insensitive). Under the hood, {kib} relays this header to {es}, then {es} authenticates the request using the credentials in the header. [float] [[embedded-content-authentication]] diff --git a/x-pack/plugins/security/server/authentication/authenticator.test.ts b/x-pack/plugins/security/server/authentication/authenticator.test.ts index ce97c142f558..4e35b84a9311 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.test.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.test.ts @@ -210,7 +210,7 @@ describe('Authenticator', () => { expect( jest.requireMock('./providers/http').HTTPAuthenticationProvider ).toHaveBeenCalledWith(expect.anything(), { - supportedSchemes: new Set(['apikey', 'basic']), + supportedSchemes: new Set(['apikey', 'bearer', 'basic']), }); }); @@ -238,7 +238,9 @@ describe('Authenticator', () => { expect( jest.requireMock('./providers/http').HTTPAuthenticationProvider - ).toHaveBeenCalledWith(expect.anything(), { supportedSchemes: new Set(['apikey']) }); + ).toHaveBeenCalledWith(expect.anything(), { + supportedSchemes: new Set(['apikey', 'bearer']), + }); }); it('disabled if explicitly disabled', () => { diff --git a/x-pack/plugins/security/server/config.test.ts b/x-pack/plugins/security/server/config.test.ts index 4a7d8c7961cf..1baf3fd4aac5 100644 --- a/x-pack/plugins/security/server/config.test.ts +++ b/x-pack/plugins/security/server/config.test.ts @@ -27,6 +27,7 @@ describe('config schema', () => { "enabled": true, "schemes": Array [ "apikey", + "bearer", ], }, "providers": Object { @@ -80,6 +81,7 @@ describe('config schema', () => { "enabled": true, "schemes": Array [ "apikey", + "bearer", ], }, "providers": Object { @@ -133,6 +135,7 @@ describe('config schema', () => { "enabled": true, "schemes": Array [ "apikey", + "bearer", ], }, "providers": Object { @@ -311,6 +314,7 @@ describe('config schema', () => { "enabled": true, "schemes": Array [ "apikey", + "bearer", ], }, "oidc": Object { @@ -342,6 +346,7 @@ describe('config schema', () => { "enabled": true, "schemes": Array [ "apikey", + "bearer", ], }, "oidc": Object { @@ -373,6 +378,7 @@ describe('config schema', () => { "enabled": true, "schemes": Array [ "apikey", + "bearer", ], }, "providers": Array [ @@ -391,6 +397,7 @@ describe('config schema', () => { "enabled": true, "schemes": Array [ "apikey", + "bearer", ], }, "providers": Array [ @@ -412,6 +419,7 @@ describe('config schema', () => { "enabled": true, "schemes": Array [ "apikey", + "bearer", ], }, "providers": Array [ @@ -1485,6 +1493,7 @@ describe('createConfig()', () => { "enabled": true, "schemes": Array [ "apikey", + "bearer", ], }, "providers": Object { diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index 07ff81e092f5..89918e73369d 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -269,7 +269,7 @@ export const ConfigSchema = schema.object({ http: schema.object({ enabled: schema.boolean({ defaultValue: true }), autoSchemesEnabled: schema.boolean({ defaultValue: true }), - schemes: schema.arrayOf(schema.string(), { defaultValue: ['apikey'] }), + schemes: schema.arrayOf(schema.string(), { defaultValue: ['apikey', 'bearer'] }), }), }), audit: schema.object( diff --git a/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts b/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts index 0515a1e1969b..83f09ef017b0 100644 --- a/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts +++ b/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts @@ -46,7 +46,7 @@ describe('Security UsageCollector', () => { authProviderCount: 1, enabledAuthProviders: ['basic'], loginSelectorEnabled: false, - httpAuthSchemes: ['apikey'], + httpAuthSchemes: ['apikey', 'bearer'], sessionIdleTimeoutInMinutes: 60, sessionLifespanInMinutes: 43200, sessionCleanupInMinutes: 60, diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index 3c1cdd5790f3..f7b978c2b58b 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -54,6 +54,7 @@ const onlyNotInCoverageTests = [ 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/http_bearer.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'), diff --git a/x-pack/test/security_api_integration/http_bearer.config.ts b/x-pack/test/security_api_integration/http_bearer.config.ts new file mode 100644 index 000000000000..b0a9f4a92034 --- /dev/null +++ b/x-pack/test/security_api_integration/http_bearer.config.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; +import { services } from './services'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts')); + + return { + testFiles: [require.resolve('./tests/http_bearer')], + servers: xPackAPITestsConfig.get('servers'), + security: { disableTestUser: true }, + services, + junit: { + reportName: 'X-Pack Security API Integration Tests (HTTP Bearer)', + }, + + esTestCluster: { + ...xPackAPITestsConfig.get('esTestCluster'), + serverArgs: [ + ...xPackAPITestsConfig.get('esTestCluster.serverArgs'), + 'xpack.security.authc.token.enabled=true', + 'xpack.security.authc.token.timeout=15s', + ], + }, + + kbnTestServer: { + ...xPackAPITestsConfig.get('kbnTestServer'), + }, + }; +} diff --git a/x-pack/test/security_api_integration/tests/http_bearer/header.ts b/x-pack/test/security_api_integration/tests/http_bearer/header.ts new file mode 100644 index 000000000000..f7ebef4f16d0 --- /dev/null +++ b/x-pack/test/security_api_integration/tests/http_bearer/header.ts @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { adminTestUser } from '@kbn/test'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertestWithoutAuth'); + const es = getService('es'); + + async function createToken() { + const { + body: { access_token: accessToken, authentication }, + } = await es.security.getToken({ + body: { + grant_type: 'password', + ...adminTestUser, + }, + }); + + return { + accessToken, + expectedUser: { + ...authentication, + authentication_provider: { name: '__http__', type: 'http' }, + authentication_type: 'token', + }, + }; + } + + describe('header', () => { + it('accepts valid access token via authorization Bearer header', async () => { + const { accessToken, expectedUser } = await createToken(); + + const response = await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'true') + .set('authorization', `Bearer ${accessToken}`) + .expect(200, expectedUser); + + // Make sure we don't automatically create a session + expect(response.headers['set-cookie']).to.be(undefined); + }); + + it('accepts multiple requests for a single valid access token', async () => { + const { accessToken, expectedUser } = await createToken(); + + // try it once + await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'true') + .set('authorization', `Bearer ${accessToken}`) + .expect(200, expectedUser); + + // try it again to verity it isn't invalidated after a single request + await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'true') + .set('authorization', `Bearer ${accessToken}`) + .expect(200, expectedUser); + }); + + it('rejects invalid access token via authorization Bearer header', async () => { + await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'true') + .set('authorization', 'Bearer notreal') + .expect(401); + }); + + it('rejects invalidated access token via authorization Bearer header', async () => { + const { accessToken } = await createToken(); + await es.security.invalidateToken({ body: { token: accessToken } }); + + await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'true') + .set('authorization', `Bearer ${accessToken}`) + .expect(401); + }); + + it('rejects expired access token via authorization Bearer header', async function () { + this.timeout(40000); + + const { accessToken } = await createToken(); + + // Access token expiration is set to 15s for API integration tests. + // Let's wait for 20s to make sure token expires. + await new Promise((resolve) => setTimeout(resolve, 20000)); + + await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'true') + .set('authorization', `Bearer ${accessToken}`) + .expect(401); + }); + }); +} diff --git a/x-pack/test/security_api_integration/tests/http_bearer/index.ts b/x-pack/test/security_api_integration/tests/http_bearer/index.ts new file mode 100644 index 000000000000..4dbad2660eba --- /dev/null +++ b/x-pack/test/security_api_integration/tests/http_bearer/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('security APIs - HTTP Bearer', function () { + this.tags('ciGroup6'); + loadTestFile(require.resolve('./header')); + }); +}