diff --git a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts index f0991ab01a6e..511a0abecbc1 100644 --- a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts +++ b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts @@ -14,6 +14,7 @@ import type { SavedObjectsClientContract, ElasticsearchClient } from 'src/core/s import { esKuery } from '../../../../../../src/plugins/data/server'; import type { ESSearchResponse as SearchResponse } from '../../../../../../typings/elasticsearch'; import type { EnrollmentAPIKey, FleetServerEnrollmentAPIKey } from '../../types'; +import { IngestManagerError } from '../../errors'; import { ENROLLMENT_API_KEYS_INDEX } from '../../constants'; import { agentPolicyService } from '../agent_policy'; import { escapeSearchQueryPhrase } from '../saved_object'; @@ -28,10 +29,13 @@ export async function listEnrollmentApiKeys( page?: number; perPage?: number; kuery?: string; + query?: ReturnType; showInactive?: boolean; } ): Promise<{ items: EnrollmentAPIKey[]; total: any; page: any; perPage: any }> { const { page = 1, perPage = 20, kuery } = options; + const query = + options.query ?? (kuery && esKuery.toElasticsearchQuery(esKuery.fromKueryExpression(kuery))); const res = await esClient.search>({ index: ENROLLMENT_API_KEYS_INDEX, @@ -40,9 +44,7 @@ export async function listEnrollmentApiKeys( sort: 'created_at:desc', track_total_hits: true, ignore_unavailable: true, - body: kuery - ? { query: esKuery.toElasticsearchQuery(esKuery.fromKueryExpression(kuery)) } - : undefined, + body: query ? { query } : undefined, }); // @ts-expect-error @elastic/elasticsearch @@ -159,7 +161,7 @@ export async function generateEnrollmentAPIKey( const { items } = await listEnrollmentApiKeys(esClient, { page: page++, perPage: 100, - kuery: `policy_id:"${agentPolicyId}" AND name:${providedKeyName.replace(/ /g, '\\ ')}*`, + query: getQueryForExistingKeyNameOnPolicy(agentPolicyId, providedKeyName), }); if (items.length === 0) { hasMore = false; @@ -176,7 +178,7 @@ export async function generateEnrollmentAPIKey( k.name?.replace(providedKeyName, '').trim().match(uuidRegex) ) ) { - throw new Error( + throw new IngestManagerError( i18n.translate('xpack.fleet.serverError.enrollmentKeyDuplicate', { defaultMessage: 'An enrollment key named {providedKeyName} already exists for agent policy {agentPolicyId}', @@ -254,6 +256,29 @@ export async function generateEnrollmentAPIKey( }; } +function getQueryForExistingKeyNameOnPolicy(agentPolicyId: string, providedKeyName: string) { + const query = { + bool: { + filter: [ + { + bool: { + should: [{ match_phrase: { policy_id: agentPolicyId } }], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [{ query_string: { fields: ['name'], query: `(${providedKeyName}) *` } }], + minimum_should_match: 1, + }, + }, + ], + }, + }; + + return query; +} + export async function getEnrollmentAPIKeyById(esClient: ElasticsearchClient, apiKeyId: string) { const res = await esClient.search({ index: ENROLLMENT_API_KEYS_INDEX, diff --git a/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts b/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts index 5f38a6e050f4..25fb71ae4280 100644 --- a/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts +++ b/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts @@ -103,7 +103,7 @@ export default function (providerContext: FtrProviderContext) { .expect(400); }); - it('should allow to create an enrollment api key with an agent policy', async () => { + it('should allow to create an enrollment api key with only an agent policy', async () => { const { body: apiResponse } = await supertest .post(`/api/fleet/enrollment-api-keys`) .set('kbn-xsrf', 'xxx') @@ -115,6 +115,55 @@ export default function (providerContext: FtrProviderContext) { expect(apiResponse.item).to.have.keys('id', 'api_key', 'api_key_id', 'name', 'policy_id'); }); + it('should allow to create an enrollment api key with agent policy and unique name', async () => { + const { body: noSpacesRes } = await supertest + .post(`/api/fleet/enrollment-api-keys`) + .set('kbn-xsrf', 'xxx') + .send({ + policy_id: 'policy1', + name: 'something', + }); + expect(noSpacesRes.item).to.have.keys('id', 'api_key', 'api_key_id', 'name', 'policy_id'); + + const { body: hasSpacesRes } = await supertest + .post(`/api/fleet/enrollment-api-keys`) + .set('kbn-xsrf', 'xxx') + .send({ + policy_id: 'policy1', + name: 'something else', + }); + expect(hasSpacesRes.item).to.have.keys('id', 'api_key', 'api_key_id', 'name', 'policy_id'); + + const { body: noSpacesDupe } = await supertest + .post(`/api/fleet/enrollment-api-keys`) + .set('kbn-xsrf', 'xxx') + .send({ + policy_id: 'policy1', + name: 'something', + }) + .expect(400); + + expect(noSpacesDupe).to.eql({ + statusCode: 400, + error: 'Bad Request', + message: 'An enrollment key named something already exists for agent policy policy1', + }); + + const { body: hasSpacesDupe } = await supertest + .post(`/api/fleet/enrollment-api-keys`) + .set('kbn-xsrf', 'xxx') + .send({ + policy_id: 'policy1', + name: 'something else', + }) + .expect(400); + expect(hasSpacesDupe).to.eql({ + statusCode: 400, + error: 'Bad Request', + message: 'An enrollment key named something else already exists for agent policy policy1', + }); + }); + it('should create an ES ApiKey with metadata', async () => { const { body: apiResponse } = await supertest .post(`/api/fleet/enrollment-api-keys`)