diff --git a/src/core/server/http/integration_tests/core_service.test.mocks.ts b/src/core/server/http/integration_tests/core_service.test.mocks.ts index 8e782970e2a8e4..dd1e27a97d7d7b 100644 --- a/src/core/server/http/integration_tests/core_service.test.mocks.ts +++ b/src/core/server/http/integration_tests/core_service.test.mocks.ts @@ -18,10 +18,11 @@ */ import { elasticsearchServiceMock } from '../../elasticsearch/elasticsearch_service.mock'; +export const clusterClientInstanceMock = elasticsearchServiceMock.createScopedClusterClient(); export const clusterClientMock = jest.fn(); jest.doMock('../../elasticsearch/scoped_cluster_client', () => ({ ScopedClusterClient: clusterClientMock.mockImplementation(function () { - return elasticsearchServiceMock.createScopedClusterClient(); + return clusterClientInstanceMock; }), })); diff --git a/src/core/server/http/integration_tests/core_services.test.ts b/src/core/server/http/integration_tests/core_services.test.ts index c7925f5b6d8217..be7aa4690346bf 100644 --- a/src/core/server/http/integration_tests/core_services.test.ts +++ b/src/core/server/http/integration_tests/core_services.test.ts @@ -16,9 +16,13 @@ * specific language governing permissions and limitations * under the License. */ + +import { clusterClientMock, clusterClientInstanceMock } from './core_service.test.mocks'; + import Boom from 'boom'; import { Request } from 'hapi'; -import { clusterClientMock } from './core_service.test.mocks'; +import { errors as esErrors } from 'elasticsearch'; +import { ElasticsearchErrorHelpers } from '../../elasticsearch/errors'; import * as kbnTestServer from '../../../../test_utils/kbn_server'; @@ -352,7 +356,7 @@ describe('http service', () => { }); }); }); - describe('elasticsearch', () => { + describe('legacy elasticsearch client', () => { let root: ReturnType; beforeEach(async () => { root = kbnTestServer.createRoot({ plugins: { initialize: false } }); @@ -416,5 +420,31 @@ describe('http service', () => { const [, , dataClientHeaders] = dataClient; expect(dataClientHeaders).toEqual({ authorization: authorizationHeader }); }); + + it('forwards 401 errors returned from elasticsearch', async () => { + const { http } = await root.setup(); + const { createRouter } = http; + + const authenticationError = ElasticsearchErrorHelpers.decorateNotAuthorizedError( + new (esErrors.AuthenticationException as any)('Authentication Exception', { + body: { error: { header: { 'WWW-Authenticate': 'authenticate header' } } }, + statusCode: 401, + }) + ); + + clusterClientInstanceMock.callAsCurrentUser.mockRejectedValue(authenticationError); + + const router = createRouter('/new-platform'); + router.get({ path: '/', validate: false }, async (context, req, res) => { + await context.core.elasticsearch.adminClient.callAsCurrentUser('ping'); + return res.ok(); + }); + + await root.start(); + + const response = await kbnTestServer.request.get(root, '/new-platform/').expect(401); + + expect(response.header['www-authenticate']).toEqual('authenticate header'); + }); }); }); diff --git a/src/core/server/http/router/router.ts b/src/core/server/http/router/router.ts index 69402a74eda5f2..35eec746163ceb 100644 --- a/src/core/server/http/router/router.ts +++ b/src/core/server/http/router/router.ts @@ -22,6 +22,7 @@ import Boom from 'boom'; import { isConfigSchema } from '@kbn/config-schema'; import { Logger } from '../../logging'; +import { LegacyElasticsearchErrorHelpers } from '../../elasticsearch/legacy/errors'; import { KibanaRequest } from './request'; import { KibanaResponseFactory, kibanaResponseFactory, IKibanaResponse } from './response'; import { RouteConfig, RouteConfigOptions, RouteMethod, validBodyOutput } from './route'; @@ -263,6 +264,10 @@ export class Router implements IRouter { return hapiResponseAdapter.handle(kibanaResponse); } catch (e) { this.log.error(e); + // forward 401 (boom) error from ES + if (LegacyElasticsearchErrorHelpers.isNotAuthorizedError(e)) { + return e; + } return hapiResponseAdapter.toInternalError(); } }