Skip to content

Commit

Permalink
[Security solution] Generative AI Connector (#157228)
Browse files Browse the repository at this point in the history
  • Loading branch information
stephmilovic authored May 24, 2023
1 parent 2781645 commit 029eb31
Show file tree
Hide file tree
Showing 45 changed files with 2,094 additions and 17 deletions.
5 changes: 5 additions & 0 deletions docs/management/action-types.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ a| <<xmatters-action-type,xMatters>>
a| <<torq-action-type,Torq>>

| Trigger a Torq workflow.

a| <<gen-ai-action-type,Generative AI>>

| Send a request to OpenAI.

|===

[NOTE]
Expand Down
89 changes: 89 additions & 0 deletions docs/management/connectors/action-types/gen-ai.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
[[gen-ai-action-type]]
== Generative AI connector and action
++++
<titleabbrev>Generative AI</titleabbrev>
++++

The Generative AI connector uses https://github.com/axios/axios[axios] to send a POST request to an OpenAI provider, either OpenAI or Azure OpenAI. The connector uses the <<execute-connector-api,run connector API>> to send the request.

[float]
[[define-gen-ai-ui]]
=== Create connectors in {kib}

You can create connectors in *{stack-manage-app} > {connectors-ui}*. For example:

[role="screenshot"]
image::management/connectors/images/gen-ai-connector.png[Generative AI connector]

[float]
[[gen-ai-connector-configuration]]
==== Connector configuration

Generative AI connectors have the following configuration properties:

Name:: The name of the connector.
API Provider:: The OpenAI API provider, either OpenAI or Azure OpenAI.
API URL:: The OpenAI request URL.
API Key:: The OpenAI or Azure OpenAI API key for authentication.

[float]
[[preconfigured-gen-ai-configuration]]
=== Create preconfigured connectors

If you are running {kib} on-prem, you can define connectors by
adding `xpack.actions.preconfigured` settings to your `kibana.yml` file.
For example:

[source,text]
--
xpack.actions.preconfigured:
my-gen-ai:
name: preconfigured-gen-ai-connector-type
actionTypeId: .gen-ai
config:
apiUrl: https://api.openai.com/v1/chat/completions
apiProvider: 'Azure OpenAI'
secrets:
apiKey: superlongapikey
--

Config defines information for the connector type.

`apiProvider`:: A string that corresponds to *OpenAI API Provider*.
`apiUrl`:: A URL string that corresponds to the *OpenAI API URL*.

Secrets defines sensitive information for the connector type.

`apiKey`:: A string that corresponds to *OpenAI API Key*.

[float]
[[gen-ai-action-configuration]]
=== Test connectors

You can test connectors with the <<execute-connector-api,run connector API>> or
as you're creating or editing the connector in {kib}. For example:

[role="screenshot"]
image::management/connectors/images/gen-ai-params-test.png[Generative AI params test]

The Generative AI actions have the following configuration properties.

Body:: A JSON payload sent to the OpenAI API URL. For example:
+
[source,text]
--
{
"model": "gpt-3.5-turbo",
"messages": [
{
"role": "user",
"content": "Hello world"
}
]
}
--
[float]
[[gen-ai-connector-networking-configuration]]
=== Connector networking configuration

Use the <<action-settings, Action configuration settings>> to customize connector networking configurations, such as proxies, certificates, or TLS settings. You can set configurations that apply to all your connectors or use `xpack.actions.customHostSettings` to set per-host configurations.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/management/connectors/index.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ include::action-types/torq.asciidoc[leveloffset=+1]
include::action-types/webhook.asciidoc[leveloffset=+1]
include::action-types/cases-webhook.asciidoc[leveloffset=+1]
include::action-types/xmatters.asciidoc[leveloffset=+1]
include::action-types/gen-ai.asciidoc[leveloffset=+1]
include::pre-configured-connectors.asciidoc[leveloffset=+1]
2 changes: 1 addition & 1 deletion docs/settings/alert-action-settings.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ WARNING: This feature is available in {kib} 7.17.4 and 8.3.0 onwards but is not
A boolean value indicating that a footer with a relevant link should be added to emails sent as alerting actions. Default: true.

`xpack.actions.enabledActionTypes` {ess-icon}::
A list of action types that are enabled. It defaults to `[*]`, enabling all types. The names for built-in {kib} action types are prefixed with a `.` and include: `.email`, `.index`, `.jira`, `.opsgenie`, `.pagerduty`, `.resilient`, `.server-log`, `.servicenow`, .`servicenow-itom`, `.servicenow-sir`, `.slack`, `.swimlane`, `.teams`, `.tines`, `.torq`, `.xmatters`, and `.webhook`. An empty list `[]` will disable all action types.
A list of action types that are enabled. It defaults to `[*]`, enabling all types. The names for built-in {kib} action types are prefixed with a `.` and include: `.email`, `.index`, `.jira`, `.opsgenie`, `.pagerduty`, `.resilient`, `.server-log`, `.servicenow`, .`servicenow-itom`, `.servicenow-sir`, `.slack`, `.swimlane`, `.teams`, `.tines`, `.torq`, `.xmatters`, `.gen-ai`, and `.webhook`. An empty list `[]` will disable all action types.
+
Disabled action types will not appear as an option when creating new connectors, but existing connectors and actions of that type will remain in {kib} and will not function.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {

describe('areValidFeatures', () => {
it('returns true when all inputs are valid features', () => {
expect(areValidFeatures(['alerting', 'cases'])).toBeTruthy();
expect(areValidFeatures(['alerting', 'cases', 'general'])).toBeTruthy();
});

it('returns true when only one input and it is a valid feature', () => {
Expand Down Expand Up @@ -42,9 +42,10 @@ describe('getConnectorFeatureName', () => {

describe('getConnectorCompatibility', () => {
it('returns the compatibility list for valid feature ids', () => {
expect(getConnectorCompatibility(['alerting', 'cases', 'uptime', 'siem'])).toEqual([
expect(getConnectorCompatibility(['alerting', 'cases', 'uptime', 'siem', 'general'])).toEqual([
'Alerting Rules',
'Cases',
'General',
]);
});

Expand Down
15 changes: 15 additions & 0 deletions x-pack/plugins/actions/common/connector_feature_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ export const AlertingConnectorFeatureId = 'alerting';
export const CasesConnectorFeatureId = 'cases';
export const UptimeConnectorFeatureId = 'uptime';
export const SecurityConnectorFeatureId = 'siem';
export const GeneralConnectorFeatureId = 'general';

const compatibilityGeneral = i18n.translate(
'xpack.actions.availableConnectorFeatures.compatibility.general',
{
defaultMessage: 'General',
}
);

const compatibilityAlertingRules = i18n.translate(
'xpack.actions.availableConnectorFeatures.compatibility.alertingRules',
Expand Down Expand Up @@ -72,11 +80,18 @@ export const SecuritySolutionFeature: ConnectorFeatureConfig = {
compatibility: compatibilityAlertingRules,
};

export const GeneralFeature: ConnectorFeatureConfig = {
id: GeneralConnectorFeatureId,
name: compatibilityGeneral,
compatibility: compatibilityGeneral,
};

const AllAvailableConnectorFeatures = {
[AlertingConnectorFeature.id]: AlertingConnectorFeature,
[CasesConnectorFeature.id]: CasesConnectorFeature,
[UptimeConnectorFeature.id]: UptimeConnectorFeature,
[SecuritySolutionFeature.id]: SecuritySolutionFeature,
[GeneralFeature.id]: GeneralFeature,
};

export function areValidFeatures(ids: string[]) {
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/actions/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export {
CasesConnectorFeatureId,
UptimeConnectorFeatureId,
SecurityConnectorFeatureId,
GeneralConnectorFeatureId,
} from './connector_feature_config';
export interface ActionType {
id: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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 { assertURL } from './validators';

describe('Validators', () => {
describe('assertURL function', () => {
it('valid URL with a valid protocol and hostname does not throw an error', () => {
expect(() => assertURL('https://www.example.com')).not.toThrow();
});

it('invalid URL throws an error with a relevant message', () => {
expect(() => assertURL('invalidurl')).toThrowError('Invalid URL');
});

it('URL with an invalid protocol throws an error with a relevant message', () => {
expect(() => assertURL('ftp://www.example.com')).toThrowError('Invalid protocol');
});

it('function handles case sensitivity of protocols correctly', () => {
expect(() => assertURL('hTtPs://www.example.com')).not.toThrow();
});

it('function handles URLs with query parameters and fragment identifiers correctly', () => {
expect(() => assertURL('https://www.example.com/path?query=value#fragment')).not.toThrow();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,23 @@ import { i18n } from '@kbn/i18n';
import { get } from 'lodash';
import { ValidatorServices } from '../../types';

const validProtocols: string[] = ['http:', 'https:'];
export const assertURL = (url: string) => {
try {
const parsedUrl = new URL(url);

if (!parsedUrl.hostname) {
throw new Error(`URL must contain hostname`);
}

if (!validProtocols.includes(parsedUrl.protocol)) {
throw new Error(`Invalid protocol`);
}
} catch (error) {
throw new Error(`URL Error: ${error.message}`);
}
};

export const urlAllowListValidator = <T>(urlKey: string) => {
return (obj: T, validatorServices: ValidatorServices) => {
const { configurationUtilities } = validatorServices;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { isPlainObject, isEmpty } from 'lodash';
import { Type } from '@kbn/config-schema';
import { Logger } from '@kbn/logging';
import axios, { AxiosInstance, AxiosResponse, AxiosError, AxiosRequestHeaders } from 'axios';
import { assertURL } from './helpers/validators';
import { ActionsConfigurationUtilities } from '../actions_config';
import { SubAction, SubActionRequestParams } from './types';
import { ServiceParams } from './types';
Expand All @@ -24,7 +25,6 @@ const isAxiosError = (error: unknown): error is AxiosError => (error as AxiosErr
export abstract class SubActionConnector<Config, Secrets> {
[k: string]: ((params: unknown) => unknown) | unknown;
private axiosInstance: AxiosInstance;
private validProtocols: string[] = ['http:', 'https:'];
private subActions: Map<string, SubAction> = new Map();
private configurationUtilities: ActionsConfigurationUtilities;
protected logger: Logger;
Expand Down Expand Up @@ -56,19 +56,7 @@ export abstract class SubActionConnector<Config, Secrets> {
}

private assertURL(url: string) {
try {
const parsedUrl = new URL(url);

if (!parsedUrl.hostname) {
throw new Error('URL must contain hostname');
}

if (!this.validProtocols.includes(parsedUrl.protocol)) {
throw new Error('Invalid protocol');
}
} catch (error) {
throw new Error(`URL Error: ${error.message}`);
}
assertURL(url);
}

private ensureUriAllowed(url: string) {
Expand Down
24 changes: 24 additions & 0 deletions x-pack/plugins/stack_connectors/common/gen_ai/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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 { i18n } from '@kbn/i18n';

export const GEN_AI_TITLE = i18n.translate(
'xpack.stackConnectors.components.genAi.connectorTypeTitle',
{
defaultMessage: 'Generative AI',
}
);
export const GEN_AI_CONNECTOR_ID = '.gen-ai';
export enum SUB_ACTION {
RUN = 'run',
TEST = 'test',
}
export enum OpenAiProviderType {
OpenAi = 'OpenAI',
AzureAi = 'Azure OpenAI',
}
22 changes: 22 additions & 0 deletions x-pack/plugins/stack_connectors/common/gen_ai/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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 { schema } from '@kbn/config-schema';

// Connector schema
export const GenAiConfigSchema = schema.object({
apiProvider: schema.string(),
apiUrl: schema.string(),
});

export const GenAiSecretsSchema = schema.object({ apiKey: schema.string() });

// Run action schema
export const GenAiRunActionParamsSchema = schema.object({
body: schema.string(),
});
export const GenAiRunActionResponseSchema = schema.object({}, { unknowns: 'ignore' });
19 changes: 19 additions & 0 deletions x-pack/plugins/stack_connectors/common/gen_ai/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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 { TypeOf } from '@kbn/config-schema';
import {
GenAiConfigSchema,
GenAiSecretsSchema,
GenAiRunActionParamsSchema,
GenAiRunActionResponseSchema,
} from './schema';

export type GenAiConfig = TypeOf<typeof GenAiConfigSchema>;
export type GenAiSecrets = TypeOf<typeof GenAiSecretsSchema>;
export type GenAiRunActionParams = TypeOf<typeof GenAiRunActionParamsSchema>;
export type GenAiRunActionResponse = TypeOf<typeof GenAiRunActionResponseSchema>;
3 changes: 3 additions & 0 deletions x-pack/plugins/stack_connectors/kibana.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
"actions",
"esUiShared",
"triggersActionsUi"
],
"extraPublicDirs": [
"public/common"
]
}
}
11 changes: 11 additions & 0 deletions x-pack/plugins/stack_connectors/public/common/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* 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 GenAiLogo from '../connector_types/gen_ai/logo';

export { GEN_AI_CONNECTOR_ID } from '../../common/gen_ai/constants';
export { GenAiLogo };
Loading

0 comments on commit 029eb31

Please sign in to comment.