Skip to content

Commit

Permalink
[Security solution] AWS Bedrock connector (#166662)
Browse files Browse the repository at this point in the history
  • Loading branch information
stephmilovic authored Sep 27, 2023
1 parent 107239c commit bacebd2
Show file tree
Hide file tree
Showing 77 changed files with 2,732 additions and 708 deletions.
12 changes: 8 additions & 4 deletions docs/management/action-types.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ Connectors provide a central place to store connection information for services
[cols="2"]
|===

a| <<bedrock-action-type,AWS Bedrock>>

| Send a request to AWS Bedrock.

a| <<d3security-action-type,D3 Security>>

| Send a request to D3 Security.
Expand All @@ -15,10 +19,6 @@ a| <<email-action-type,Email>>

| Send email from your server.

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

| Send a request to OpenAI.

a| <<resilient-action-type,{ibm-r}>>

| Create an incident in {ibm-r}.
Expand All @@ -35,6 +35,10 @@ a| <<teams-action-type,Microsoft Teams>>

| Send a message to a Microsoft Teams channel.

a| <<gen-ai-action-type,OpenAI>>

| Send a request to OpenAI.

a| <<opsgenie-action-type,{opsgenie}>>

| Create or close an alert in {opsgenie}.
Expand Down
68 changes: 68 additions & 0 deletions docs/management/connectors/action-types/bedrock.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
[[bedrock-action-type]]
== AWS Bedrock connector and action
++++
<titleabbrev>AWS Bedrock</titleabbrev>
++++
:frontmatter-description: Add a connector that can send requests to AWS Bedrock.
:frontmatter-tags-products: [kibana]
:frontmatter-tags-content-type: [how-to]
:frontmatter-tags-user-goals: [configure]


The AWS Bedrock connector uses https://github.com/axios/axios[axios] to send a POST request to AWS Bedrock. The connector uses the <<execute-connector-api,run connector API>> to send the request.

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

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

[role="screenshot"]
// TODO: need logo before screenshot
image::management/connectors/images/bedrock-connector.png[AWS Bedrock connector]

[float]
[[bedrock-connector-configuration]]
==== Connector configuration

AWS Bedrock connectors have the following configuration properties:

Name:: The name of the connector.
API URL:: The AWS Bedrock request URL.
Default model:: The GAI model for AWS Bedrock to use. Current support is for the Anthropic Claude models, defaulting to Claude 2. The model can be set on a per request basis by including a "model" parameter alongside the request body.
Region:: The AWS Bedrock request URL.
Access Key:: The AWS access key for authentication.
Secret:: The secret for authentication.

[float]
[[bedrock-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"]
// TODO: need logo before screenshot
image::management/connectors/images/bedrock-params.png[AWS Bedrock params test]

The AWS Bedrock actions have the following configuration properties.

Body:: A stringified JSON payload sent to the AWS Bedrock Invoke Model API URL. For example:
+
[source,text]
--
{
body: JSON.stringify({
prompt: `${combinedMessages} \n\nAssistant:`,
max_tokens_to_sample: 300,
stop_sequences: ['\n\nHuman:']
})
}
--
Model:: An optional string that will overwrite the connector's default model. For

[float]
[[bedrock-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.
Binary file modified docs/management/connectors/images/gen-ai-connector.png
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
@@ -1,3 +1,4 @@
include::action-types/bedrock.asciidoc[leveloffset=+1]
include::action-types/d3security.asciidoc[leveloffset=+1]
include::action-types/email.asciidoc[leveloffset=+1]
include::action-types/gen-ai.asciidoc[leveloffset=+1]
Expand Down
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`, `.gen-ai`, `.d3security`, 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`, `.bedrock`, `.d3security`, 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
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,7 @@
"antlr4ts": "^0.5.0-alpha.3",
"archiver": "^5.3.1",
"async": "^3.2.3",
"aws4": "^1.12.0",
"axios": "^1.4.0",
"base64-js": "^1.3.1",
"bitmap-sdf": "^1.0.3",
Expand Down Expand Up @@ -1272,6 +1273,7 @@
"@types/adm-zip": "^0.5.0",
"@types/archiver": "^5.3.1",
"@types/async": "^3.2.3",
"@types/aws4": "^1.5.0",
"@types/babel__core": "^7.20.0",
"@types/babel__generator": "^7.6.4",
"@types/babel__helper-plugin-utils": "^7.10.0",
Expand Down
62 changes: 6 additions & 56 deletions x-pack/packages/kbn-elastic-assistant/impl/assistant/api.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe('fetchConnectorExecuteAction', () => {
expect(mockHttp.fetch).toHaveBeenCalledWith(
'/internal/elastic_assistant/actions/connector/foo/_execute',
{
body: '{"params":{"subActionParams":{"body":"{\\"model\\":\\"gpt-4\\",\\"messages\\":[{\\"role\\":\\"user\\",\\"content\\":\\"This is a test\\"}],\\"n\\":1,\\"stop\\":null,\\"temperature\\":0.2}"},"subAction":"test"}}',
body: '{"params":{"subActionParams":{"model":"gpt-4","messages":[{"role":"user","content":"This is a test"}],"n":1,"stop":null,"temperature":0.2},"subAction":"invokeAI"}}',
headers: { 'Content-Type': 'application/json' },
method: 'POST',
signal: undefined,
Expand All @@ -65,7 +65,7 @@ describe('fetchConnectorExecuteAction', () => {
await fetchConnectorExecuteAction(testProps);

expect(mockHttp.fetch).toHaveBeenCalledWith('/api/actions/connector/foo/_execute', {
body: '{"params":{"subActionParams":{"body":"{\\"model\\":\\"gpt-4\\",\\"messages\\":[{\\"role\\":\\"user\\",\\"content\\":\\"This is a test\\"}],\\"n\\":1,\\"stop\\":null,\\"temperature\\":0.2}"},"subAction":"test"}}',
body: '{"params":{"subActionParams":{"model":"gpt-4","messages":[{"role":"user","content":"This is a test"}],"n":1,"stop":null,"temperature":0.2},"subAction":"invokeAI"}}',
headers: { 'Content-Type': 'application/json' },
method: 'POST',
signal: undefined,
Expand All @@ -88,7 +88,7 @@ describe('fetchConnectorExecuteAction', () => {
});

it('returns API_ERROR when there are no choices', async () => {
(mockHttp.fetch as jest.Mock).mockResolvedValue({ status: 'ok', data: {} });
(mockHttp.fetch as jest.Mock).mockResolvedValue({ status: 'ok', data: '' });
const testProps: FetchConnectorExecuteAction = {
assistantLangChain: false,
http: mockHttp,
Expand All @@ -101,46 +101,12 @@ describe('fetchConnectorExecuteAction', () => {
expect(result).toBe(API_ERROR);
});

it('return the trimmed first `choices` `message` `content` when the API call is successful', async () => {
(mockHttp.fetch as jest.Mock).mockResolvedValue({
status: 'ok',
data: {
choices: [
{
message: {
content: ' Test response ', // leading and trailing whitespace
},
},
],
},
});

const testProps: FetchConnectorExecuteAction = {
assistantLangChain: false,
http: mockHttp,
messages,
apiConfig,
};

const result = await fetchConnectorExecuteAction(testProps);

expect(result).toBe('Test response');
});

it('returns the value of the action_input property when assistantLangChain is true, and `content` has properly prefixed and suffixed JSON with the action_input property', async () => {
const content = '```json\n{"action_input": "value from action_input"}\n```';

(mockHttp.fetch as jest.Mock).mockResolvedValue({
status: 'ok',
data: {
choices: [
{
message: {
content,
},
},
],
},
data: content,
});

const testProps: FetchConnectorExecuteAction = {
Expand All @@ -160,15 +126,7 @@ describe('fetchConnectorExecuteAction', () => {

(mockHttp.fetch as jest.Mock).mockResolvedValue({
status: 'ok',
data: {
choices: [
{
message: {
content,
},
},
],
},
data: content,
});

const testProps: FetchConnectorExecuteAction = {
Expand All @@ -188,15 +146,7 @@ describe('fetchConnectorExecuteAction', () => {

(mockHttp.fetch as jest.Mock).mockResolvedValue({
status: 'ok',
data: {
choices: [
{
message: {
content,
},
},
],
},
data: content,
});

const testProps: FetchConnectorExecuteAction = {
Expand Down
41 changes: 17 additions & 24 deletions x-pack/packages/kbn-elastic-assistant/impl/assistant/api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,14 @@ export const fetchConnectorExecuteAction = async ({
temperature: 0.2,
}
: {
// Azure OpenAI and Bedrock invokeAI both expect this body format
messages: outboundMessages,
};

const requestBody = {
params: {
subActionParams: {
body: JSON.stringify(body),
},
subAction: 'test',
subActionParams: body,
subAction: 'invokeAI',
},
};

Expand All @@ -61,29 +60,23 @@ export const fetchConnectorExecuteAction = async ({
? `/internal/elastic_assistant/actions/connector/${apiConfig?.connectorId}/_execute`
: `/api/actions/connector/${apiConfig?.connectorId}/_execute`;

// TODO: Find return type for this API
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const response = await http.fetch<any>(path, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody),
signal,
});

const data = response.data;
if (response.status !== 'ok') {
const response = await http.fetch<{ connector_id: string; status: string; data: string }>(
path,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody),
signal,
}
);

if (response.status !== 'ok' || !response.data) {
return API_ERROR;
}

if (data.choices && data.choices.length > 0 && data.choices[0].message.content) {
const result = data.choices[0].message.content.trim();

return assistantLangChain ? getFormattedMessageContent(result) : result;
} else {
return API_ERROR;
}
return assistantLangChain ? getFormattedMessageContent(response.data) : response.data;
} catch (error) {
return API_ERROR;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ export const AssistantTitle: React.FC<{
<EuiFlexItem grow={false}>
<ConnectorSelectorInline
isDisabled={isDisabled || selectedConversation === undefined}
onConnectorModalVisibilityChange={() => {}}
selectedConnectorId={selectedConnectorId}
selectedConversation={selectedConversation}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import { EuiFormRow, EuiLink, EuiTitle, EuiText, EuiHorizontalRule, EuiSpacer } from '@elastic/eui';
import React, { useCallback, useMemo } from 'react';

import { ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public';
import { HttpSetup } from '@kbn/core-http-browser';
import { FormattedMessage } from '@kbn/i18n-react';
import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/public/common';
Expand All @@ -27,7 +26,6 @@ import { useLoadConnectors } from '../../../connectorland/use_load_connectors';
import { getGenAiConfig } from '../../../connectorland/helpers';

export interface ConversationSettingsProps {
actionTypeRegistry: ActionTypeRegistryContract;
allSystemPrompts: Prompt[];
conversationSettings: UseAssistantContext['conversations'];
defaultConnectorId?: string;
Expand All @@ -46,7 +44,6 @@ export interface ConversationSettingsProps {
*/
export const ConversationSettings: React.FC<ConversationSettingsProps> = React.memo(
({
actionTypeRegistry,
allSystemPrompts,
defaultConnectorId,
defaultProvider,
Expand Down Expand Up @@ -250,10 +247,7 @@ export const ConversationSettings: React.FC<ConversationSettingsProps> = React.m
}
>
<ConnectorSelector
actionTypeRegistry={actionTypeRegistry}
http={http}
isDisabled={selectedConversation == null}
onConnectorModalVisibilityChange={() => {}}
onConnectorSelectionChange={handleOnConnectorSelectionChange}
selectedConnectorId={selectedConnector?.id}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,8 @@ export const AssistantSettings: React.FC<Props> = React.memo(
selectedConversation: defaultSelectedConversation,
setSelectedConversationId,
}) => {
const {
actionTypeRegistry,
assistantLangChain,
http,
selectedSettingsTab,
setSelectedSettingsTab,
} = useAssistantContext();
const { assistantLangChain, http, selectedSettingsTab, setSelectedSettingsTab } =
useAssistantContext();
const {
conversationSettings,
defaultAllow,
Expand Down Expand Up @@ -267,7 +262,6 @@ export const AssistantSettings: React.FC<Props> = React.memo(
conversationSettings={conversationSettings}
setUpdatedConversationSettings={setUpdatedConversationSettings}
allSystemPrompts={systemPromptSettings}
actionTypeRegistry={actionTypeRegistry}
selectedConversation={selectedConversation}
onSelectedConversationChange={onHandleSelectedConversationChange}
http={http}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export interface ConversationTheme {
export interface Conversation {
apiConfig: {
connectorId?: string;
connectorTypeTitle?: string;
defaultSystemPromptId?: string;
provider?: OpenAiProviderType;
model?: string;
Expand Down
Loading

0 comments on commit bacebd2

Please sign in to comment.