Skip to content

Commit

Permalink
[Response Ops][Actions] Adding getAll to UnsecuredActionsClient (#1…
Browse files Browse the repository at this point in the history
…79090)

## Summary

Adds unsecured version of the `getAll` function that is exposed via the
`unsecuredActionsClient` on the actions plugin start contract. This
version doesn't expect any user request and doesn't check RBAC. Actions
saved objects are accessed via the internal saved objects repository
(instead of a saved objects client scoped to the request) and an ES
client that uses the internal user (instead of scoped to the request).
This function returns all connectors for the give space ID and
preconfigured connectors but does not return system actions.

## To Verify
1. Run this PR and create a connector via the UI or API in multiple
different spaces.
2. In `x-pack/plugins/alerting/server/plugin.ts` in the start function,
add the following:

```
const unsecuredActionsClient = plugins.actions.getUnsecuredActionsClient();
unsecuredActionsClient.getAll('default').then((results) => {
  console.log('default connectors');
  console.log(JSON.stringify(results));
});

unsecuredActionsClient.getAll(<differentSpaceId>).then((results) => {
  console.log(JSON.stringify(results));
});
```
3. Restart Kibana and verify that the appropriate connectors were
returned and logged.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
ymao1 and kibanamachine authored Mar 21, 2024
1 parent 3be6e23 commit c81301b
Show file tree
Hide file tree
Showing 15 changed files with 927 additions and 53 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,28 @@
* Get all actions with in-memory connectors
*/
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { AuditLogger } from '@kbn/security-plugin-types-server';
import { ElasticsearchClient, Logger } from '@kbn/core/server';
import { omit } from 'lodash';
import { InMemoryConnector } from '../../../..';
import { SavedObjectClientForFind } from '../../../../data/connector/types/params';
import { connectorWithExtraFindDataSchema } from '../../schemas';
import { findConnectorsSo, searchConnectorsSo } from '../../../../data/connector';
import { GetAllParams, InjectExtraFindDataParams } from './types';
import { ConnectorAuditAction, connectorAuditEvent } from '../../../../lib/audit_events';
import { connectorFromSavedObject, isConnectorDeprecated } from '../../lib';
import { ConnectorWithExtraFindData } from '../../types';
import { GetAllUnsecuredParams } from './types/params';

interface GetAllHelperOpts {
auditLogger?: AuditLogger;
esClient: ElasticsearchClient;
inMemoryConnectors: InMemoryConnector[];
kibanaIndices: string[];
logger: Logger;
namespace?: string;
savedObjectsClient: SavedObjectClientForFind;
}

export async function getAll({
context,
Expand All @@ -32,28 +48,70 @@ export async function getAll({
throw error;
}

return await getAllHelper({
auditLogger: context.auditLogger,
esClient: context.scopedClusterClient.asInternalUser,
inMemoryConnectors: includeSystemActions
? context.inMemoryConnectors
: context.inMemoryConnectors.filter((connector) => !connector.isSystemAction),
kibanaIndices: context.kibanaIndices,
logger: context.logger,
savedObjectsClient: context.unsecuredSavedObjectsClient,
});
}

export async function getAllUnsecured({
esClient,
inMemoryConnectors,
internalSavedObjectsRepository,
kibanaIndices,
logger,
spaceId,
}: GetAllUnsecuredParams): Promise<ConnectorWithExtraFindData[]> {
const namespace = spaceId && spaceId !== 'default' ? spaceId : undefined;

const connectors = await getAllHelper({
esClient,
// Unsecured execution does not currently support system actions so we filter them out
inMemoryConnectors: inMemoryConnectors.filter((connector) => !connector.isSystemAction),
kibanaIndices,
logger,
namespace,
savedObjectsClient: internalSavedObjectsRepository,
});

return connectors.map((connector) => omit(connector, 'secrets'));
}

async function getAllHelper({
auditLogger,
esClient,
inMemoryConnectors,
kibanaIndices,
logger,
namespace,
savedObjectsClient,
}: GetAllHelperOpts): Promise<ConnectorWithExtraFindData[]> {
const savedObjectsActions = (
await findConnectorsSo({ unsecuredSavedObjectsClient: context.unsecuredSavedObjectsClient })
await findConnectorsSo({ savedObjectsClient, namespace })
).saved_objects.map((rawAction) =>
connectorFromSavedObject(rawAction, isConnectorDeprecated(rawAction.attributes))
);

savedObjectsActions.forEach(({ id }) =>
context.auditLogger?.log(
connectorAuditEvent({
action: ConnectorAuditAction.FIND,
savedObject: { type: 'action', id },
})
)
);

const inMemoryConnectorsFiltered = includeSystemActions
? context.inMemoryConnectors
: context.inMemoryConnectors.filter((connector) => !connector.isSystemAction);
if (auditLogger) {
savedObjectsActions.forEach(({ id }) =>
auditLogger.log(
connectorAuditEvent({
action: ConnectorAuditAction.FIND,
savedObject: { type: 'action', id },
})
)
);
}

const mergedResult = [
...savedObjectsActions,
...inMemoryConnectorsFiltered.map((inMemoryConnector) => ({
...inMemoryConnectors.map((inMemoryConnector) => ({
id: inMemoryConnector.id,
actionTypeId: inMemoryConnector.actionTypeId,
name: inMemoryConnector.name,
Expand All @@ -64,8 +122,8 @@ export async function getAll({
].sort((a, b) => a.name.localeCompare(b.name));

const connectors = await injectExtraFindData({
kibanaIndices: context.kibanaIndices,
scopedClusterClient: context.scopedClusterClient,
kibanaIndices,
esClient,
connectors: mergedResult,
});

Expand All @@ -74,7 +132,7 @@ export async function getAll({
try {
connectorWithExtraFindDataSchema.validate(connector);
} catch (e) {
context.logger.warn(`Error validating connector: ${connector.id}, ${e}`);
logger.warn(`Error validating connector: ${connector.id}, ${e}`);
}
});

Expand All @@ -83,7 +141,7 @@ export async function getAll({

async function injectExtraFindData({
kibanaIndices,
scopedClusterClient,
esClient,
connectors,
}: InjectExtraFindDataParams): Promise<ConnectorWithExtraFindData[]> {
const aggs: Record<string, estypes.AggregationsAggregationContainer> = {};
Expand Down Expand Up @@ -121,7 +179,7 @@ async function injectExtraFindData({
};
}

const aggregationResult = await searchConnectorsSo({ scopedClusterClient, kibanaIndices, aggs });
const aggregationResult = await searchConnectorsSo({ esClient, kibanaIndices, aggs });

return connectors.map((connector) => ({
...connector,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
* 2.0.
*/

export { getAll } from './get_all';
export { getAll, getAllUnsecured } from './get_all';
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
* 2.0.
*/

import { IScopedClusterClient } from '@kbn/core-elasticsearch-server';
import { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
import { ISavedObjectsRepository, Logger } from '@kbn/core/server';
import { AuditLogger } from '@kbn/security-plugin/server';
import { InMemoryConnector } from '../../../../..';
import { ActionsClientContext } from '../../../../../actions_client';
import { Connector } from '../../../types';

Expand All @@ -14,8 +17,18 @@ export interface GetAllParams {
context: ActionsClientContext;
}

export interface GetAllUnsecuredParams {
auditLogger?: AuditLogger;
esClient: ElasticsearchClient;
inMemoryConnectors: InMemoryConnector[];
internalSavedObjectsRepository: ISavedObjectsRepository;
kibanaIndices: string[];
logger: Logger;
spaceId: string;
}

export interface InjectExtraFindDataParams {
kibanaIndices: string[];
scopedClusterClient: IScopedClusterClient;
esClient: ElasticsearchClient;
connectors: Connector[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import { FindConnectorsSoResult, FindConnectorsSoParams } from './types';
import { MAX_ACTIONS_RETURNED } from './constants';

export const findConnectorsSo = async ({
unsecuredSavedObjectsClient,
savedObjectsClient,
namespace,
}: FindConnectorsSoParams): Promise<FindConnectorsSoResult> => {
return unsecuredSavedObjectsClient.find({
return savedObjectsClient.find({
perPage: MAX_ACTIONS_RETURNED,
type: 'action',
...(namespace ? { namespaces: [namespace] } : {}),
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
import { SearchConnectorsSoParams } from './types';

export const searchConnectorsSo = async ({
scopedClusterClient,
esClient,
kibanaIndices,
aggs,
}: SearchConnectorsSoParams) => {
return scopedClusterClient.asInternalUser.search({
return esClient.search({
index: kibanaIndices,
ignore_unavailable: true,
body: {
Expand Down
9 changes: 6 additions & 3 deletions x-pack/plugins/actions/server/data/connector/types/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@
* 2.0.
*/

import { IScopedClusterClient } from '@kbn/core-elasticsearch-server';
import { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
import { SavedObjectsClient } from '@kbn/core/server';

export type SavedObjectClientForFind = Pick<SavedObjectsClient, 'find'>;
export interface SearchConnectorsSoParams {
kibanaIndices: string[];
scopedClusterClient: IScopedClusterClient;
esClient: ElasticsearchClient;
aggs: Record<string, estypes.AggregationsAggregationContainer>;
}

export interface FindConnectorsSoParams {
unsecuredSavedObjectsClient: SavedObjectsClientContract;
savedObjectsClient: SavedObjectClientForFind;
namespace?: string;
}

export interface GetConnectorSoParams {
Expand Down
6 changes: 5 additions & 1 deletion x-pack/plugins/actions/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,18 +486,22 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon

const getUnsecuredActionsClient = () => {
const internalSavedObjectsRepository = core.savedObjects.createInternalRepository([
ACTION_SAVED_OBJECT_TYPE,
ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE,
]);

return new UnsecuredActionsClient({
actionExecutor: actionExecutor!,
internalSavedObjectsRepository,
clusterClient: core.elasticsearch.client,
executionEnqueuer: createBulkUnsecuredExecutionEnqueuerFunction({
taskManager: plugins.taskManager,
connectorTypeRegistry: actionTypeRegistry!,
inMemoryConnectors: this.inMemoryConnectors,
configurationUtilities: actionsConfigUtils,
}),
inMemoryConnectors: this.inMemoryConnectors,
internalSavedObjectsRepository,
kibanaIndices: core.savedObjects.getAllIndices(),
logger: this.logger,
});
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type UnsecuredActionsClientMock = jest.Mocked<IUnsecuredActionsClient>;

const createUnsecuredActionsClientMock = () => {
const mocked: UnsecuredActionsClientMock = {
getAll: jest.fn(),
execute: jest.fn(),
bulkEnqueueExecution: jest.fn(),
};
Expand Down
Loading

0 comments on commit c81301b

Please sign in to comment.