From 243b4faf4c74b458d0bfbd9b2cbb09bbc367b774 Mon Sep 17 00:00:00 2001 From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com> Date: Tue, 3 Sep 2024 09:59:13 -0400 Subject: [PATCH] [Security Solution][Endpoint] Update the `run_endpoint_agent` dev script to support use of an API Key (#190449) ## Summary - Updates the `/x-pack/plugins/security_solution/scripts/endpoint/run_endpoint_agent.js` with a new optional argument: `--apiKey` - This argument enables use of a Kibana/ES API key with the script instead of `--username` and `--password` - Ideal for use against Serverless environments where the use of `username/password` is not ideal --- .../common/endpoint/utils/kibana_status.ts | 11 ++++++++++- .../scripts/endpoint/common/stack_services.ts | 9 ++++++--- .../scripts/endpoint/endpoint_agent_runner/index.ts | 7 ++++++- .../scripts/endpoint/endpoint_agent_runner/runtime.ts | 2 ++ .../scripts/endpoint/endpoint_agent_runner/types.ts | 1 + 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/security_solution/common/endpoint/utils/kibana_status.ts b/x-pack/plugins/security_solution/common/endpoint/utils/kibana_status.ts index 4e34acd44d74d1..78147439eedd97 100644 --- a/x-pack/plugins/security_solution/common/endpoint/utils/kibana_status.ts +++ b/x-pack/plugins/security_solution/common/endpoint/utils/kibana_status.ts @@ -11,7 +11,16 @@ import type { StatusResponse } from '@kbn/core-status-common-internal'; import { catchAxiosErrorFormatAndThrow } from '../format_axios_error'; export const fetchKibanaStatus = async (kbnClient: KbnClient): Promise => { - return (await kbnClient.status.get().catch(catchAxiosErrorFormatAndThrow)) as StatusResponse; + // We DO NOT use `kbnClient.status.get()` here because the `kbnClient` passed on input could be our enhanced + // client (created by `x-pack/plugins/security_solution/scripts/endpoint/common/stack_services.ts:267`) + // which could be using an API key (which the core KbnClient does not support) + return kbnClient + .request({ + method: 'GET', + path: '/api/status', + }) + .then((response) => response.data as StatusResponse) + .catch(catchAxiosErrorFormatAndThrow); }; /** * Checks to see if Kibana/ES is running in serverless mode diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/stack_services.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/stack_services.ts index 95cd75ec9eb88b..3ec0c0f230423c 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/stack_services.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/stack_services.ts @@ -78,9 +78,11 @@ interface CreateRuntimeServicesOptions { class KbnClientExtended extends KbnClient { private readonly apiKey: string | undefined; - constructor({ apiKey, url, ...options }: KbnClientOptions & { apiKey?: string }) { + constructor(protected readonly options: KbnClientOptions & { apiKey?: string }) { + const { apiKey, url, ...opt } = options; + super({ - ...options, + ...opt, url: apiKey ? buildUrlWithCredentials(url, '', '') : url, }); @@ -94,6 +96,7 @@ class KbnClientExtended extends KbnClient { if (this.apiKey) { headers.Authorization = `ApiKey ${this.apiKey}`; + this.options.log.verbose(`Adding API key header to request header 'Authorization'`); } return super.request({ @@ -322,7 +325,7 @@ export const fetchStackVersion = async (kbnClient: KbnClient): Promise = export const waitForKibana = async (kbnClient: KbnClient): Promise => { await pRetry( async () => { - const response = await kbnClient.status.get(); + const response = await fetchKibanaStatus(kbnClient); if (response.status.overall.level !== 'available') { throw new Error( diff --git a/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/index.ts b/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/index.ts index 51fb0ea3b51488..03d0b14f9852c2 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/index.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/index.ts @@ -12,6 +12,7 @@ import { setupAll } from './setup'; const runSetupAll: RunFn = async (cliContext) => { const username = cliContext.flags.username as string; const password = cliContext.flags.password as string; + const apiKey = cliContext.flags.apiKey as string; const kibanaUrl = cliContext.flags.kibanaUrl as string; const elasticUrl = cliContext.flags.elasticUrl as string; const fleetServerUrl = cliContext.flags.fleetServerUrl as string; @@ -25,6 +26,7 @@ const runSetupAll: RunFn = async (cliContext) => { fleetServerUrl, username, password, + apiKey, version, policy, log, @@ -43,12 +45,13 @@ export const cli = () => { 'multipass', install Elastic Agent and enroll it with Fleet. Can be used multiple times against the same stack.`, flags: { - string: ['kibana', 'elastic', 'username', 'password', 'version', 'policy'], + string: ['kibana', 'elastic', 'username', 'password', 'version', 'policy', 'apiKey'], default: { kibanaUrl: 'http://127.0.0.1:5601', elasticUrl: 'http://127.0.0.1:9200', username: 'elastic', password: 'changeme', + apiKey: '', version: '', policy: '', }, @@ -62,6 +65,8 @@ export const cli = () => { --username Optional. User name to be used for auth against elasticsearch and kibana (Default: elastic). --password Optional. Password associated with the username (Default: changeme) + --apiKey Optional. A Kibana API key to use for authz. When defined, 'username' + and 'password' arguments are ignored. --kibanaUrl Optional. The url to Kibana (Default: http://127.0.0.1:5601) --elasticUrl Optional. The url to Elasticsearch (Default: http://127.0.0.1:9200) `, diff --git a/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/runtime.ts b/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/runtime.ts index ba3790e5bd7759..36fbf869470138 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/runtime.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/runtime.ts @@ -28,6 +28,7 @@ export const startRuntimeServices = async ({ fleetServerUrl, username, password, + apiKey, ...otherOptions }: StartRuntimeServicesOptions) => { const stackServices = await createRuntimeServices({ @@ -36,6 +37,7 @@ export const startRuntimeServices = async ({ fleetServerUrl, username, password, + apiKey, log, asSuperuser: otherOptions?.asSuperuser, }); diff --git a/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/types.ts b/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/types.ts index ac75d1f8f64e8d..673b0d6b3441d1 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/types.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/types.ts @@ -13,6 +13,7 @@ export interface StartRuntimeServicesOptions { fleetServerUrl?: string; username: string; password: string; + apiKey?: string; version?: string; policy?: string; log?: ToolingLog;