diff --git a/x-pack/plugins/beats/server/routes/api/index.js b/x-pack/plugins/beats/server/routes/api/index.js index 07d923876ee79..76cedde5cdf3d 100644 --- a/x-pack/plugins/beats/server/routes/api/index.js +++ b/x-pack/plugins/beats/server/routes/api/index.js @@ -6,8 +6,10 @@ import { registerCreateEnrollmentTokensRoute } from './register_create_enrollment_tokens_route'; import { registerEnrollBeatRoute } from './register_enroll_beat_route'; +import { registerListBeatsRoute } from './register_list_beats_route'; export function registerApiRoutes(server) { registerCreateEnrollmentTokensRoute(server); registerEnrollBeatRoute(server); + registerListBeatsRoute(server); } diff --git a/x-pack/plugins/beats/server/routes/api/register_list_beats_route.js b/x-pack/plugins/beats/server/routes/api/register_list_beats_route.js new file mode 100644 index 0000000000000..b84210988978f --- /dev/null +++ b/x-pack/plugins/beats/server/routes/api/register_list_beats_route.js @@ -0,0 +1,47 @@ +/* + * 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 { + get, + omit +} from "lodash"; +import { INDEX_NAMES } from "../../../common/constants"; +import { callWithRequestFactory } from '../../lib/client'; +import { wrapEsError } from "../../lib/error_wrappers"; + +async function getBeats(callWithRequest) { + const params = { + index: INDEX_NAMES.BEATS, + type: '_doc', + q: 'type:beat' + }; + + const response = await callWithRequest('search', params); + return get(response, 'hits.hits', []); +} + +// TODO: add license check pre-hook +export function registerListBeatsRoute(server) { + server.route({ + method: 'GET', + path: '/api/beats/agents', + handler: async (request, reply) => { + const callWithRequest = callWithRequestFactory(server, request); + let beats; + + try { + beats = await getBeats(callWithRequest); + } catch (err) { + return reply(wrapEsError(err)); + } + + const response = { + beats: beats.map(beat => omit(beat._source.beat, ['access_token'])) + }; + reply(response); + } + }); +} diff --git a/x-pack/test/api_integration/apis/beats/index.js b/x-pack/test/api_integration/apis/beats/index.js index dc6137f979019..6b3562863a2b7 100644 --- a/x-pack/test/api_integration/apis/beats/index.js +++ b/x-pack/test/api_integration/apis/beats/index.js @@ -19,5 +19,6 @@ export default function ({ getService, loadTestFile }) { loadTestFile(require.resolve('./create_enrollment_tokens')); loadTestFile(require.resolve('./enroll_beat')); + loadTestFile(require.resolve('./list_beats')); }); } diff --git a/x-pack/test/api_integration/apis/beats/list_beats.js b/x-pack/test/api_integration/apis/beats/list_beats.js new file mode 100644 index 0000000000000..dfd0dccf32cc0 --- /dev/null +++ b/x-pack/test/api_integration/apis/beats/list_beats.js @@ -0,0 +1,46 @@ +/* + * 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 expect from 'expect.js'; + +export default function ({ getService }) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('list_beats', () => { + const archive = 'beats/list'; + + beforeEach('load beats archive', () => esArchiver.load(archive)); + afterEach('unload beats archive', () => esArchiver.unload(archive)); + + it('should return all beats', async () => { + const { body: apiResponse } = await supertest + .get( + '/api/beats/agents' + ) + .expect(200); + + const beatsFromApi = apiResponse.beats; + + expect(beatsFromApi.length).to.be(3); + expect(beatsFromApi.filter(beat => beat.hasOwnProperty('verified_on')).length).to.be(1); + expect(beatsFromApi.find(beat => beat.hasOwnProperty('verified_on')).id).to.be('foo'); + }); + + it('should not return access tokens', async () => { + const { body: apiResponse } = await supertest + .get( + '/api/beats/agents' + ) + .expect(200); + + const beatsFromApi = apiResponse.beats; + + expect(beatsFromApi.length).to.be(3); + expect(beatsFromApi.filter(beat => beat.hasOwnProperty('access_token')).length).to.be(0); + }); + }); +} diff --git a/x-pack/test/functional/es_archives/beats/list/data.json.gz b/x-pack/test/functional/es_archives/beats/list/data.json.gz new file mode 100644 index 0000000000000..c5bcfc6fb14f9 Binary files /dev/null and b/x-pack/test/functional/es_archives/beats/list/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/beats/list/mappings.json b/x-pack/test/functional/es_archives/beats/list/mappings.json new file mode 100644 index 0000000000000..92d89fb159733 --- /dev/null +++ b/x-pack/test/functional/es_archives/beats/list/mappings.json @@ -0,0 +1,82 @@ +{ + "type": "index", + "value": { + "index": ".management-beats", + "settings": { + "index": { + "codec": "best_compression", + "number_of_shards": "1", + "auto_expand_replicas": "0-1", + "number_of_replicas": "0" + } + }, + "mappings": { + "_doc": { + "dynamic": "strict", + "properties": { + "type": { + "type": "keyword" + }, + "enrollment_token": { + "properties": { + "token": { + "type": "keyword" + }, + "expires_on": { + "type": "date" + } + } + }, + "configuration_block": { + "properties": { + "tag": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "block_yml": { + "type": "text" + } + } + }, + "beat": { + "properties": { + "id": { + "type": "keyword" + }, + "access_token": { + "type": "keyword" + }, + "verified_on": { + "type": "date" + }, + "type": { + "type": "keyword" + }, + "host_ip": { + "type": "ip" + }, + "host_name": { + "type": "keyword" + }, + "ephemeral_id": { + "type": "keyword" + }, + "local_configuration_yml": { + "type": "text" + }, + "central_configuration_yml": { + "type": "text" + }, + "metadata": { + "dynamic": "true", + "type": "object" + } + } + } + } + } + } + } +}