Skip to content
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

[POC] Granular Connector RBAC #197346

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ export interface UserRealm {
type: string;
}

export interface ApiKey {
/**
* Name of the API key.
*/
name: string;

/**
* The ID of the API key.
*/
id: string;
}

/**
* Represents the currently authenticated user.
*/
Expand Down Expand Up @@ -60,4 +72,9 @@ export interface AuthenticatedUser extends User {
* User profile ID of this user.
*/
profile_uid?: string;

/**
* Metadata of the API key that was used to authenticate the user.
*/
api_key?: ApiKey;
}
1 change: 1 addition & 0 deletions packages/kbn-actions-types/action_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ export interface ActionType {
minimumLicenseRequired: LicenseType;
supportedFeatureIds: string[];
isSystemActionType: boolean;
isEdrActionType: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ const transformConnectorType: RewriteRequestCase<ActionType> = ({
minimum_license_required: minimumLicenseRequired,
supported_feature_ids: supportedFeatureIds,
is_system_action_type: isSystemActionType,
is_edr_action_type: isEdrActionType,
...res
}: AsApiContract<ActionType>) => ({
enabledInConfig,
enabledInLicense,
minimumLicenseRequired,
supportedFeatureIds,
isSystemActionType,
isEdrActionType,
...res,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ export interface ActionTypeModel<ActionConfig = any, ActionSecrets = any, Action
hideInUi?: boolean;
modalWidth?: number;
isSystemActionType?: boolean;
isEdrActionType?: boolean;
}

export type ActionTypeRegistryContract<Connector = unknown, Params = unknown> = PublicMethodsOf<
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 @@ -28,6 +28,14 @@ export const SecurityConnectorFeatureId = 'siem';
export const GenerativeAIForSecurityConnectorFeatureId = 'generativeAIForSecurity';
export const GenerativeAIForObservabilityConnectorFeatureId = 'generativeAIForObservability';
export const GenerativeAIForSearchPlaygroundConnectorFeatureId = 'generativeAIForSearchPlayground';
export const EdrForSecurityConnectorFeatureId = 'edrForSecurity';

const compatibilityEdrForSecurity = i18n.translate(
'xpack.actions.availableConnectorFeatures.compatibility.edrForSecurity',
{
defaultMessage: 'EDR for Security',
}
);

const compatibilityGenerativeAIForSecurity = i18n.translate(
'xpack.actions.availableConnectorFeatures.compatibility.generativeAIForSecurity',
Expand Down Expand Up @@ -120,6 +128,12 @@ export const GenerativeAIForSearchPlaygroundFeature: ConnectorFeatureConfig = {
compatibility: compatibilityGenerativeAIForSearchPlayground,
};

export const EdrForSecurityFeature: ConnectorFeatureConfig = {
id: EdrForSecurityConnectorFeatureId,
name: compatibilityEdrForSecurity,
compatibility: compatibilityEdrForSecurity,
};

const AllAvailableConnectorFeatures = {
[AlertingConnectorFeature.id]: AlertingConnectorFeature,
[CasesConnectorFeature.id]: CasesConnectorFeature,
Expand All @@ -128,6 +142,7 @@ const AllAvailableConnectorFeatures = {
[GenerativeAIForSecurityFeature.id]: GenerativeAIForSecurityFeature,
[GenerativeAIForObservabilityFeature.id]: GenerativeAIForObservabilityFeature,
[GenerativeAIForSearchPlaygroundFeature.id]: GenerativeAIForSearchPlaygroundFeature,
[EdrForSecurityFeature.id]: EdrForSecurityFeature,
};

export function areValidFeatures(ids: string[]) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ export const connectorTypesResponseSchema = schema.object({
is_system_action_type: schema.boolean({
meta: { description: 'Indicates whether the action is a system action.' },
}),
is_edr_action_type: schema.boolean({
meta: { description: 'Indicates whether the action is an EDR action.' },
}),
});

export const connectorExecuteResponseSchema = schema.object({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export interface ConnectorTypesResponse {
minimum_license_required: ConnectorTypesResponseSchemaType['minimum_license_required'];
supported_feature_ids: ConnectorTypesResponseSchemaType['supported_feature_ids'];
is_system_action_type: ConnectorTypesResponseSchemaType['is_system_action_type'];
is_edr_action_type: ConnectorTypesResponseSchemaType['is_edr_action_type'];
}

type ConnectorExecuteResponseSchemaType = TypeOf<typeof connectorExecuteResponseSchema>;
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 @@ -24,6 +24,7 @@ export interface ActionType {
minimumLicenseRequired: LicenseType;
supportedFeatureIds: string[];
isSystemActionType: boolean;
isEdrActionType: boolean;
}

export enum InvalidEmailReason {
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/actions/server/action_type_registry.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const createActionTypeRegistryMock = () => {
isActionExecutable: jest.fn(),
isSystemActionType: jest.fn(),
getUtils: jest.fn(),
getSystemActionKibanaPrivileges: jest.fn(),
getActionKibanaPrivileges: jest.fn(),
};
return mocked;
};
Expand Down
10 changes: 5 additions & 5 deletions x-pack/plugins/actions/server/action_type_registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -767,7 +767,7 @@ describe('actionTypeRegistry', () => {
});
});

describe('getSystemActionKibanaPrivileges()', () => {
describe('getActionKibanaPrivileges()', () => {
it('should get the kibana privileges correctly for system actions', () => {
const registry = new ActionTypeRegistry(actionTypeRegistryParams);

Expand All @@ -786,7 +786,7 @@ describe('actionTypeRegistry', () => {
executor,
});

const result = registry.getSystemActionKibanaPrivileges('test.system-action');
const result = registry.getActionKibanaPrivileges('test.system-action');
expect(result).toEqual(['test/create']);
});

Expand All @@ -807,7 +807,7 @@ describe('actionTypeRegistry', () => {
executor,
});

const result = registry.getSystemActionKibanaPrivileges('test.system-action');
const result = registry.getActionKibanaPrivileges('test.system-action');
expect(result).toEqual([]);
});

Expand All @@ -827,7 +827,7 @@ describe('actionTypeRegistry', () => {
executor,
});

const result = registry.getSystemActionKibanaPrivileges('foo');
const result = registry.getActionKibanaPrivileges('foo');
expect(result).toEqual([]);
});

Expand All @@ -850,7 +850,7 @@ describe('actionTypeRegistry', () => {
executor,
});

registry.getSystemActionKibanaPrivileges('test.system-action', { foo: 'bar' });
registry.getActionKibanaPrivileges('test.system-action', { foo: 'bar' });
expect(getKibanaPrivileges).toHaveBeenCalledWith({ params: { foo: 'bar' } });
});
});
Expand Down
63 changes: 33 additions & 30 deletions x-pack/plugins/actions/server/action_type_registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,19 @@ import { RunContext, TaskManagerSetupContract, TaskCost } from '@kbn/task-manage
import { LicensingPluginSetup } from '@kbn/licensing-plugin/server';
import { ActionType as CommonActionType, areValidFeatures } from '../common';
import { ActionsConfigurationUtilities } from './actions_config';
import { getActionTypeFeatureUsageName, TaskRunnerFactory, ILicenseState } from './lib';
import {
getActionTypeFeatureUsageName,
TaskRunnerFactory,
ILicenseState,
ActionExecutionSourceType,
} from './lib';
import {
ActionType,
InMemoryConnector,
ActionTypeConfig,
ActionTypeSecrets,
ActionTypeParams,
} from './types';
import { isBidirectionalConnectorType } from './lib/bidirectional_connectors';

export interface ActionTypeRegistryOpts {
licensing: LicensingPluginSetup;
Expand Down Expand Up @@ -113,19 +117,19 @@ export class ActionTypeRegistry {
Boolean(this.actionTypes.get(actionTypeId)?.isSystemActionType);

/**
* Returns the kibana privileges of a system action type
* Returns the kibana privileges
*/
public getSystemActionKibanaPrivileges<Params extends ActionTypeParams = ActionTypeParams>(
public getActionKibanaPrivileges<Params extends ActionTypeParams = ActionTypeParams>(
actionTypeId: string,
params?: Params
params?: Params,
source?: ActionExecutionSourceType
): string[] {
const actionType = this.actionTypes.get(actionTypeId);

if (!actionType?.isSystemActionType) {
if (!actionType?.isSystemActionType && !actionType?.isEdrActionType) {
return [];
}

return actionType?.getKibanaPrivileges?.({ params }) ?? [];
return actionType?.getKibanaPrivileges?.({ params, source }) ?? [];
}

/**
Expand Down Expand Up @@ -175,11 +179,15 @@ export class ActionTypeRegistry {
);
}

if (!actionType.isSystemActionType && actionType.getKibanaPrivileges) {
if (
!actionType.isSystemActionType &&
!actionType.isEdrActionType &&
actionType.getKibanaPrivileges
) {
throw new Error(
i18n.translate('xpack.actions.actionTypeRegistry.register.invalidKibanaPrivileges', {
defaultMessage:
'Kibana privilege authorization is only supported for system action types',
'Kibana privilege authorization is only supported for system and EDR action types',
})
);
}
Expand Down Expand Up @@ -233,26 +241,21 @@ export class ActionTypeRegistry {
* Returns a list of registered action types [{ id, name, enabled }], filtered by featureId if provided.
*/
public list(featureId?: string): CommonActionType[] {
return (
Array.from(this.actionTypes)
.filter(([_, actionType]) =>
featureId ? actionType.supportedFeatureIds.includes(featureId) : true
)
// Temporarily don't return SentinelOne and Crowdstrike connector for Security Solution Rule Actions
.filter(([actionTypeId]) =>
featureId ? !isBidirectionalConnectorType(actionTypeId) : true
)
.map(([actionTypeId, actionType]) => ({
id: actionTypeId,
name: actionType.name,
minimumLicenseRequired: actionType.minimumLicenseRequired,
enabled: this.isActionTypeEnabled(actionTypeId),
enabledInConfig: this.actionsConfigUtils.isActionTypeEnabled(actionTypeId),
enabledInLicense: !!this.licenseState.isLicenseValidForActionType(actionType).isValid,
supportedFeatureIds: actionType.supportedFeatureIds,
isSystemActionType: !!actionType.isSystemActionType,
}))
);
return Array.from(this.actionTypes)
.filter(([_, actionType]) => {
return featureId ? actionType.supportedFeatureIds.includes(featureId) : true;
})
.map(([actionTypeId, actionType]) => ({
id: actionTypeId,
name: actionType.name,
minimumLicenseRequired: actionType.minimumLicenseRequired,
enabled: this.isActionTypeEnabled(actionTypeId),
enabledInConfig: this.actionsConfigUtils.isActionTypeEnabled(actionTypeId),
enabledInLicense: !!this.licenseState.isLicenseValidForActionType(actionType).isValid,
supportedFeatureIds: actionType.supportedFeatureIds,
isSystemActionType: !!actionType.isSystemActionType,
isEdrActionType: !!actionType.isEdrActionType,
}));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import { v4 as uuidv4 } from 'uuid';
import { RawAction, ActionTypeExecutorResult } from '../../../../types';
import { getSystemActionKibanaPrivileges } from '../../../../lib/get_system_action_kibana_privileges';
import { getActionKibanaPrivileges } from '../../../../lib/get_action_kibana_privileges';
import { isPreconfigured } from '../../../../lib/is_preconfigured';
import { isSystemAction } from '../../../../lib/is_system_action';
import { ConnectorExecuteParams } from './types';
Expand All @@ -20,7 +20,7 @@ export async function execute(
): Promise<ActionTypeExecutorResult<unknown>> {
const log = context.logger;
const { actionId, params, source, relatedSavedObjects } = connectorExecuteParams;
const additionalPrivileges = getSystemActionKibanaPrivileges(context, actionId, params);
const additionalPrivileges = getActionKibanaPrivileges(context, actionId, params, source?.type);
let actionTypeId: string | undefined;

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ export const connectorTypeSchema = schema.object({
]),
supportedFeatureIds: schema.arrayOf(schema.string()),
isSystemActionType: schema.boolean(),
isEdrActionType: schema.boolean(),
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ export interface ConnectorType {
minimumLicenseRequired: ConnectorTypeSchemaType['minimumLicenseRequired'];
supportedFeatureIds: ConnectorTypeSchemaType['supportedFeatureIds'];
isSystemActionType: ConnectorTypeSchemaType['isSystemActionType'];
isEdrActionType: ConnectorTypeSchemaType['isEdrActionType'];
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
ACTION_SAVED_OBJECT_TYPE,
ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE,
} from '../constants/saved_objects';
import { isBidirectionalConnectorType } from '../lib/bidirectional_connectors';

export interface ConstructorOptions {
request: KibanaRequest;
Expand Down Expand Up @@ -56,15 +55,7 @@ export class ActionsAuthorization {
: [authorization.actions.savedObject.get(ACTION_SAVED_OBJECT_TYPE, operation)];

const { hasAllRequested } = await checkPrivileges({
kibana: [
...privileges,
...additionalPrivileges,
// SentinelOne and Crowdstrike sub-actions require that a user have `all` privilege to Actions and Connectors.
// This is a temporary solution until a more robust RBAC approach can be implemented for sub-actions
isBidirectionalConnectorType(actionTypeId)
? 'api:actions:execute-advanced-connectors'
: 'api:actions:execute-basic-connectors',
],
kibana: [...privileges, ...additionalPrivileges],
});
if (!hasAllRequested) {
throw Boom.forbidden(
Expand Down
Loading