diff --git a/fleet_packages.json b/fleet_packages.json index ac00e9cf940af..8beebd62c1c28 100644 --- a/fleet_packages.json +++ b/fleet_packages.json @@ -58,4 +58,4 @@ "name": "security_detection_engine", "version": "9.0.3" } -] \ No newline at end of file +] diff --git a/x-pack/solutions/security/packages/security-ai-prompts/src/saved_object_mappings.ts b/x-pack/solutions/security/packages/security-ai-prompts/src/saved_object_mappings.ts index afbc0ea3ef363..73e2d19f2c7bb 100644 --- a/x-pack/solutions/security/packages/security-ai-prompts/src/saved_object_mappings.ts +++ b/x-pack/solutions/security/packages/security-ai-prompts/src/saved_object_mappings.ts @@ -45,7 +45,7 @@ export const promptType: SavedObjectsType = { hidden: false, management: { importableAndExportable: true, - visibleInManagement: false, + visibleInManagement: true, // <--show in management }, namespaceType: 'agnostic', mappings: promptSavedObjectMappings, diff --git a/x-pack/solutions/security/plugins/elastic_assistant/package.json b/x-pack/solutions/security/plugins/elastic_assistant/package.json index 140015eed0b82..0fd3f4f2dec11 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/package.json +++ b/x-pack/solutions/security/plugins/elastic_assistant/package.json @@ -6,6 +6,7 @@ "license": "Elastic License 2.0", "scripts": { "evaluate-model": "node ./scripts/model_evaluator", + "generate-security-ai-prompts": "node ./scripts/generate_security_ai_prompts", "draw-graph": "node ./scripts/draw_graph" } } diff --git a/x-pack/solutions/security/plugins/elastic_assistant/scripts/generate_security_ai_prompts.js b/x-pack/solutions/security/plugins/elastic_assistant/scripts/generate_security_ai_prompts.js new file mode 100644 index 0000000000000..c5b3389ea024f --- /dev/null +++ b/x-pack/solutions/security/plugins/elastic_assistant/scripts/generate_security_ai_prompts.js @@ -0,0 +1,9 @@ +/* + * 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. + */ + +require('../../../../../../src/setup_node_env'); +require('./generate_security_ai_prompts_script').generateSecurityAiPrompts(); diff --git a/x-pack/solutions/security/plugins/elastic_assistant/scripts/generate_security_ai_prompts_script.ts b/x-pack/solutions/security/plugins/elastic_assistant/scripts/generate_security_ai_prompts_script.ts new file mode 100644 index 0000000000000..1853dd5fda922 --- /dev/null +++ b/x-pack/solutions/security/plugins/elastic_assistant/scripts/generate_security_ai_prompts_script.ts @@ -0,0 +1,117 @@ +/* + * 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. + */ + +/* eslint-disable no-console */ + +import { Prompt } from '@kbn/security-ai-prompts'; +import * as fs from 'fs/promises'; +import { existsSync, mkdirSync } from 'fs'; +import * as path from 'path'; +import globby from 'globby'; +import { v4 as uuidv4 } from 'uuid'; + +import { localPrompts } from '../server/lib/prompt/local_prompt_object'; +import { localToolPrompts } from '../server/lib/prompt/tool_prompts'; + +export const OUTPUT_DIR = '../../../../../target/security_ai_prompts'; +export const DELETE_FILES_PATTERN = '*.json'; +export const SAVED_OBJECT_ID_PREFIX = 'security_ai_prompts-'; + +interface SecurityAiPromptSavedObject { + attributes: Prompt; + id: string; + type: 'security-ai-prompt'; +} + +export const createOutputDir = () => { + if (!existsSync(OUTPUT_DIR)) { + console.log(`Creating output directory: ${OUTPUT_DIR}`); + mkdirSync(OUTPUT_DIR); + } +}; + +export const deleteFilesByPattern = async ({ + directoryPath, + pattern, +}: { + directoryPath: string; + pattern: string; +}): Promise => { + try { + console.log(`Deleting files matching pattern "${pattern}" in directory "${directoryPath}"`); + const files = await globby(pattern, { cwd: directoryPath }); + + if (files.length === 0) { + console.log(`No files found matching pattern "${pattern}" in directory "${directoryPath}".`); + return; + } + + for (const file of files) { + const filePath = path.join(directoryPath, file); + await fs.unlink(filePath); + console.log(`Deleted file: "${filePath}"`); + } + } catch (error) { + console.error(`Error deleting files: ${error.message}`); + throw error; + } +}; + +export const writeSavedObjects = async ({ + direcotryPath, + savedObjects, +}: { + direcotryPath: string; + savedObjects: SecurityAiPromptSavedObject[]; +}) => { + for (const savedObject of savedObjects) { + const filePath = path.join(direcotryPath, `${savedObject.id}.json`); + + await fs.writeFile(filePath, `${JSON.stringify(savedObject, null, 2)}\n`); + console.log(`Wrote saved object to file: "${filePath}"`); + } +}; + +export const generateSavedObject = (prompt: Prompt): SecurityAiPromptSavedObject => ({ + attributes: { + ...prompt, + prompt: { + default: `${prompt.prompt.default}`, + }, + }, + id: `${SAVED_OBJECT_ID_PREFIX}${uuidv4()}`, + type: 'security-ai-prompt', +}); + +export const generateSavedObjects = (prompts: Prompt[]): SecurityAiPromptSavedObject[] => + prompts.map(generateSavedObject); + +export const generateSecurityAiPrompts = async () => { + console.log('Generating Security AI prompts'); + + createOutputDir(); + + await deleteFilesByPattern({ + directoryPath: OUTPUT_DIR, + pattern: DELETE_FILES_PATTERN, + }); + + const prompts = [...localPrompts, ...localToolPrompts]; + + console.log('--> prompts', JSON.stringify(prompts, null, 2)); + + const savedObjects = generateSavedObjects(prompts); + + console.log('--> savedObjects', JSON.stringify(savedObjects, null, 2)); + + await writeSavedObjects({ + direcotryPath: OUTPUT_DIR, + savedObjects, + }); + + console.log('Done'); +}; diff --git a/x-pack/solutions/security/plugins/elastic_assistant/tsconfig.json b/x-pack/solutions/security/plugins/elastic_assistant/tsconfig.json index 492ecdb70036c..b7fd59b776525 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/tsconfig.json +++ b/x-pack/solutions/security/plugins/elastic_assistant/tsconfig.json @@ -1,4 +1,5 @@ { + "esModuleInterop": true, "extends": "../../../../../tsconfig.base.json", "compilerOptions": { "outDir": "target/types", diff --git a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/constants.ts b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/constants.ts index 6507a94392ddd..a9b3fc6ff6169 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/constants.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/constants.ts @@ -30,6 +30,7 @@ export enum RULE_PREVIEW_FROM { export const PREBUILT_RULES_PACKAGE_NAME = 'security_detection_engine'; export const ENDPOINT_PACKAGE_NAME = 'endpoint'; +export const SECURITY_AI_PROMPTS_PACKAGE_NAME = 'security_ai_prompts'; /** * Rule signature id (`rule.rule_id`) of the prebuilt "Endpoint Security" rule. diff --git a/x-pack/solutions/security/plugins/security_solution/common/experimental_features.ts b/x-pack/solutions/security/plugins/security_solution/common/experimental_features.ts index 49adb405ab3be..76b4b79117d25 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/experimental_features.ts @@ -258,6 +258,11 @@ export const allowedExperimentalValues = Object.freeze({ /** Enables new Data View Picker */ newDataViewPickerEnabled: false, + + /** + * Automatically installs the security AI prompts package + */ + securityAIPromptsEnabled: false, }); type ExperimentalConfigKeys = Array; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules_handler.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules_handler.ts index e7e4337db9426..fccc70ce14419 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules_handler.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules_handler.ts @@ -8,6 +8,7 @@ import type { IKibanaResponse, KibanaRequest, KibanaResponseFactory } from '@kbn/core/server'; import { ProductFeatureSecurityKey } from '@kbn/security-solution-features/keys'; import { transformError } from '@kbn/securitysolution-es-utils'; +import { installSecurityAiPromptsPackage } from '../../logic/integrations/install_ai_prompts'; import type { BootstrapPrebuiltRulesResponse, PackageInstallStatus, @@ -32,6 +33,7 @@ export const bootstrapPrebuiltRulesHandler = async ( const ctx = await context.resolve(['securitySolution', 'alerting', 'core']); const securityContext = ctx.securitySolution; const config = securityContext.getConfig(); + const securityAIPromptsEnabled = config.experimentalFeatures.securityAIPromptsEnabled; const savedObjectsClient = ctx.core.savedObjects.client; const detectionRulesClient = securityContext.getDetectionRulesClient(); @@ -72,6 +74,18 @@ export const bootstrapPrebuiltRulesHandler = async ( }); } + const securityAiPromptsResult = securityAIPromptsEnabled + ? await installSecurityAiPromptsPackage(config, securityContext) + : null; + + if (securityAiPromptsResult !== null) { + packageResults.push({ + name: securityAiPromptsResult.package.name, + version: securityAiPromptsResult.package.version, + status: securityAiPromptsResult.status, + }); + } + const responseBody: BootstrapPrebuiltRulesResponse = { packages: packageResults, rules: ruleResults, diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/integrations/install_ai_prompts.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/integrations/install_ai_prompts.ts new file mode 100644 index 0000000000000..cbed059fa2df7 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/integrations/install_ai_prompts.ts @@ -0,0 +1,27 @@ +/* + * 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 { SECURITY_AI_PROMPTS_PACKAGE_NAME } from '../../../../../../common/detection_engine/constants'; +import type { SecuritySolutionApiRequestHandlerContext } from '../../../../../types'; +import type { ConfigType } from '../../../../../config'; +import { findLatestPackageVersion } from './find_latest_package_version'; + +export async function installSecurityAiPromptsPackage( + config: ConfigType, + context: SecuritySolutionApiRequestHandlerContext +) { + try { + const pkgVersion = await findLatestPackageVersion(context, SECURITY_AI_PROMPTS_PACKAGE_NAME); + return context.getInternalFleetServices().packages.ensureInstalledPackage({ + pkgName: SECURITY_AI_PROMPTS_PACKAGE_NAME, + pkgVersion, + }); + } catch (e) { + // fail silently + return null; + } +} diff --git a/x-pack/test/fleet_api_integration/apis/epm/__snapshots__/bulk_get_assets.snap b/x-pack/test/fleet_api_integration/apis/epm/__snapshots__/bulk_get_assets.snap index addc0892383e2..de1e6ab8880d7 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/__snapshots__/bulk_get_assets.snap +++ b/x-pack/test/fleet_api_integration/apis/epm/__snapshots__/bulk_get_assets.snap @@ -177,6 +177,12 @@ Array [ "id": "sample_search", "type": "search", }, + Object { + "appLink": "", + "attributes": Object {}, + "id": "sample_security_ai_prompt", + "type": "security-ai-prompt", + }, Object { "appLink": "", "attributes": Object { diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts b/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts index c791a7908b50f..64fb4904023f5 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts @@ -279,6 +279,17 @@ export default function (providerContext: FtrProviderContext) { resOsquerySavedQuery = err; } expect(resOsquerySavedQuery.response.data.statusCode).equal(404); + let securityAiPrompt; + try { + securityAiPrompt = await kibanaServer.savedObjects.get({ + type: 'security-ai-prompt', + id: 'sample_security_ai_prompt', + }); + } catch (err) { + checkErrorWithResponseDataOrThrow(err); + securityAiPrompt = err; + } + expect(securityAiPrompt.response.data.statusCode).equal(404); }); it('should have removed the saved object', async function () { let res; @@ -483,6 +494,12 @@ const expectAssetsInstalled = ({ type: 'csp-rule-template', id: 'sample_csp_rule_template', }); + const resSecurityAiPrompt = await kibanaServer.savedObjects.get({ + type: 'security-ai-prompt', + id: 'sample_security_ai_prompt', + }); + expect(resSecurityAiPrompt.id).equal('sample_security_ai_prompt'); + expect(resSecurityAiPrompt.managed).be(true); expect(resCloudSecurityPostureRuleTemplate.id).equal('sample_csp_rule_template'); expect(resCloudSecurityPostureRuleTemplate.managed).be(true); const resTag = await kibanaServer.savedObjects.get({ @@ -581,6 +598,10 @@ const expectAssetsInstalled = ({ id: 'sample_search', type: 'search', }, + { + id: 'sample_security_ai_prompt', + type: 'security-ai-prompt', + }, { id: 'sample_security_rule', type: 'security-rule', @@ -786,6 +807,11 @@ const expectAssetsInstalled = ({ path: 'all_assets-0.1.0/kibana/search/sample_search.json', type: 'epm-packages-assets', }, + { + id: '5d12ad91-0624-5dce-800d-b1f9a7732f7c', + path: 'all_assets-0.1.0/kibana/security_ai_prompt/sample_security_ai_prompts.json', + type: 'epm-packages-assets', + }, { id: 'd8b175c3-0d42-5ec7-90c1-d1e4b307a4c2', path: 'all_assets-0.1.0/kibana/security_rule/sample_security_rule.json', diff --git a/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts b/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts index 4f9387e168ab5..8e248101a0cbb 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts @@ -381,6 +381,10 @@ export default function (providerContext: FtrProviderContext) { id: 'sample_osquery_saved_query', type: 'osquery-saved-query', }, + { + id: 'sample_security_ai_prompt', + type: 'security-ai-prompt', + }, { id: 'sample_tag', type: 'tag', @@ -552,6 +556,11 @@ export default function (providerContext: FtrProviderContext) { path: 'all_assets-0.2.0/kibana/csp_rule_template/sample_csp_rule_template.json', type: 'epm-packages-assets', }, + { + id: '848d7b69-26d1-52c1-8afc-65e627b34812', + path: 'all_assets-0.2.0/kibana/security_ai_prompt/sample_security_ai_prompts.json', + type: 'epm-packages-assets', + }, { id: '8c665f28-a439-5f43-b5fd-8fda7b576735', path: 'all_assets-0.2.0/manifest.yml', diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/kibana/security_ai_prompt/sample_security_ai_prompts.json b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/kibana/security_ai_prompt/sample_security_ai_prompts.json new file mode 100644 index 0000000000000..6331085735cae --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/kibana/security_ai_prompt/sample_security_ai_prompts.json @@ -0,0 +1,12 @@ +{ + "attributes": { + "promptId": "systemPrompt", + "promptGroupId": "aiAssistant", + "provider": "openai", + "prompt": { + "default": "You always talk like a pirate." + } + }, + "id": "sample_security_ai_prompt", + "type": "security-ai-prompt" +} diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.2.0/kibana/security_ai_prompt/sample_security_ai_prompts.json b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.2.0/kibana/security_ai_prompt/sample_security_ai_prompts.json new file mode 100644 index 0000000000000..6331085735cae --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.2.0/kibana/security_ai_prompt/sample_security_ai_prompts.json @@ -0,0 +1,12 @@ +{ + "attributes": { + "promptId": "systemPrompt", + "promptGroupId": "aiAssistant", + "provider": "openai", + "prompt": { + "default": "You always talk like a pirate." + } + }, + "id": "sample_security_ai_prompt", + "type": "security-ai-prompt" +}