Enable bearer scheme by default to support service token authorization (#112654)

Co-authored-by: Aleh Zasypkin <aleh.zasypkin@gmail.com>
This commit is contained in:
Josh Dover 2021-10-11 18:16:26 +02:00 committed by GitHub
parent 32e00f1b0c
commit b03237a72d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 174 additions and 8 deletions

View file

@ -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 <<api-keys, `ApiKey`>> 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 <<api-keys, `ApiKey`>> and <<http-authentication, `Bearer`>> schemes.
|===

View file

@ -437,14 +437,14 @@ This type of authentication is usually useful for machine-to-machine interaction
By default {kib} supports <<api-keys, `ApiKey`>> 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]]

View file

@ -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', () => {

View file

@ -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 {

View file

@ -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(

View file

@ -46,7 +46,7 @@ describe('Security UsageCollector', () => {
authProviderCount: 1,
enabledAuthProviders: ['basic'],
loginSelectorEnabled: false,
httpAuthSchemes: ['apikey'],
httpAuthSchemes: ['apikey', 'bearer'],
sessionIdleTimeoutInMinutes: 60,
sessionLifespanInMinutes: 43200,
sessionCleanupInMinutes: 60,

View file

@ -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'),

View file

@ -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'),
},
};
}

View file

@ -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);
});
});
}

View file

@ -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'));
});
}