-
Notifications
You must be signed in to change notification settings - Fork 8.5k
[Security solution] Security AI prompts integration #216106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 25 commits
b4cbaf9
593512a
a49e283
f10e13a
e66000e
987e9fa
ce93e88
1bff213
bd13130
c37ebf4
77e9927
ab70229
cb06cb2
fc38ea6
21097de
43d11cf
d0c4af3
9b5f519
25a997a
b4b0b43
4832e69
5bb9f94
b7d574e
596ee6c
49a237d
90e3f69
02eefe4
bb76dc3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -58,4 +58,4 @@ | |
| "name": "security_detection_engine", | ||
| "version": "9.0.3" | ||
| } | ||
| ] | ||
| ] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<void> => { | ||
| 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'); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| /* | ||
| * 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 { | ||
| await findLatestPackageVersion(context, SECURITY_AI_PROMPTS_PACKAGE_NAME); | ||
| } catch (e) { | ||
| // fail silently | ||
| return null; | ||
| } | ||
|
|
||
| const pkgVersion = await findLatestPackageVersion(context, SECURITY_AI_PROMPTS_PACKAGE_NAME); | ||
|
|
||
| return context.getInternalFleetServices().packages.ensureInstalledPackage({ | ||
| pkgName: SECURITY_AI_PROMPTS_PACKAGE_NAME, | ||
| pkgVersion, | ||
| }); | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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', | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible to have conflicts if multiple packages have prompts with the same id? I think that for other assets we prefix their ids with the name of the package to avoid these issues.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The ids will be UUID autogenerated by saved object client, so we will not have this issue. this id was just for tests following other examples |
||
| }); | ||
| } 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', | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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" | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error should probably be handled like this to avoid a double
findLatestPackageVersioncall when there's no error:Another good option would be to adjust
findLatestPackageVersionto returnnullwhen no package is found, instead of throwing an error. That would help avoid using a clumsy empty catch block, which can backfire in some cases.