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

[Cases] Attachments RBAC #97756

Merged
Merged
Show file tree
Hide file tree
Changes from 16 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
10 changes: 10 additions & 0 deletions x-pack/plugins/cases/common/api/cases/comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import * as rt from 'io-ts';
import { SavedObjectFindOptionsRt } from '../saved_object';

import { UserRT } from '../user';

Expand All @@ -27,6 +28,7 @@ export const CommentAttributesBasicRt = rt.type({
]),
created_at: rt.string,
created_by: UserRT,
owner: rt.string,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm adding owner here even though that means that patch requests must include the owner. This is to keep it consistent with how patch was already working. All of the fields need to be included in the patch and we have checks to make sure that certain fields like type and owner aren't different in the request than what is already defined in the object.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if we should make a type for owner like:

const OwnerTypeRT = rt.union([rt.literal('securitySolution'), rt.literal('observability')]);

Not for this PR, but I think it would be good to have strict control over that type and not just accept any old string.

pushed_at: rt.union([rt.string, rt.null]),
pushed_by: rt.union([UserRT, rt.null]),
updated_at: rt.union([rt.string, rt.null]),
Expand All @@ -42,6 +44,7 @@ export enum CommentType {
export const ContextTypeUserRt = rt.type({
comment: rt.string,
type: rt.literal(CommentType.user),
owner: rt.string,
});

/**
Expand All @@ -57,6 +60,7 @@ export const AlertCommentRequestRt = rt.type({
id: rt.union([rt.string, rt.null]),
name: rt.union([rt.string, rt.null]),
}),
owner: rt.string,
});

const AttributesTypeUserRt = rt.intersection([ContextTypeUserRt, CommentAttributesBasicRt]);
Expand Down Expand Up @@ -112,6 +116,12 @@ export const CommentsResponseRt = rt.type({

export const AllCommentsResponseRt = rt.array(CommentResponseRt);

export const FindQueryParamsRt = rt.partial({
...SavedObjectFindOptionsRt.props,
subCaseId: rt.string,
});

export type FindQueryParams = rt.TypeOf<typeof FindQueryParamsRt>;
export type AttributesTypeAlerts = rt.TypeOf<typeof AttributesTypeAlertsRt>;
export type AttributesTypeUser = rt.TypeOf<typeof AttributesTypeUserRt>;
export type CommentAttributes = rt.TypeOf<typeof CommentAttributesRt>;
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/cases/common/api/cases/user_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const UserActionFieldTypeRt = rt.union([
rt.literal('status'),
rt.literal('settings'),
rt.literal('sub_case'),
rt.literal('owner'),
]);
const UserActionFieldRt = rt.array(UserActionFieldTypeRt);
const UserActionRt = rt.union([
Expand Down
5 changes: 5 additions & 0 deletions x-pack/plugins/cases/common/api/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
SUB_CASES_URL,
CASE_PUSH_URL,
SUB_CASE_USER_ACTIONS_URL,
CASE_CONFIGURE_DETAILS_URL,
} from '../constants';

export const getCaseDetailsUrl = (id: string): string => {
Expand Down Expand Up @@ -47,3 +48,7 @@ export const getSubCaseUserActionUrl = (caseID: string, subCaseId: string): stri
export const getCasePushUrl = (caseId: string, connectorId: string): string => {
return CASE_PUSH_URL.replace('{case_id}', caseId).replace('{connector_id}', connectorId);
};

export const getCaseConfigurationDetailsUrl = (configureID: string): string => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is so the UI can send the correct patch request with the configuration_id.

return CASE_CONFIGURE_DETAILS_URL.replace('{configuration_id}', configureID);
};
16 changes: 8 additions & 8 deletions x-pack/plugins/cases/server/authorization/audit_logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
* 2.0.
*/

import { OperationDetails } from '.';
import { AuditLogger, EventCategory, EventOutcome } from '../../../security/server';
import { DATABASE_CATEGORY, ECS_OUTCOMES, OperationDetails } from '.';
import { AuditLogger } from '../../../security/server';

enum AuthorizationResult {
Unauthorized = 'Unauthorized',
Expand Down Expand Up @@ -51,9 +51,9 @@ export class AuthorizationAuditLogger {
message: `${username ?? 'unknown user'} ${message}`,
event: {
action: operation.action,
category: EventCategory.DATABASE,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The security plugin no longer defines these. They're using the ECS library directly now. So I defined some constants for us.

Copy link
Contributor

@stephmilovic stephmilovic Apr 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe something to come back to. I defined some ECS in the RAC work we did (no overlap). maybe we should use library?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that could be helpful. The struggle was that the ECS library defines types but not the values from what I could tell 🤷‍♂️ . I don't anticipate needing to add more than what was in this PR but if we do then yeah maybe a library would be a good option.

type: operation.type,
outcome: EventOutcome.SUCCESS,
category: DATABASE_CATEGORY,
type: [operation.type],
outcome: ECS_OUTCOMES.success,
},
...(username != null && {
user: {
Expand Down Expand Up @@ -81,9 +81,9 @@ export class AuthorizationAuditLogger {
message: `${username ?? 'unknown user'} ${message}`,
event: {
action: operation.action,
category: EventCategory.DATABASE,
type: operation.type,
outcome: EventOutcome.FAILURE,
category: DATABASE_CATEGORY,
type: [operation.type],
outcome: ECS_OUTCOMES.failure,
},
// add the user information if we have it
...(username != null && {
Expand Down
116 changes: 99 additions & 17 deletions x-pack/plugins/cases/server/authorization/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@
* 2.0.
*/

import { EventType } from '../../../security/server';
import { CASE_CONFIGURE_SAVED_OBJECT, CASE_SAVED_OBJECT } from '../../common/constants';
import { EcsEventCategory, EcsEventOutcome, EcsEventType } from 'kibana/server';
import {
CASE_COMMENT_SAVED_OBJECT,
CASE_CONFIGURE_SAVED_OBJECT,
CASE_SAVED_OBJECT,
} from '../../common/constants';
import { Verbs, ReadOperations, WriteOperations, OperationDetails } from './types';

export * from './authorization';
Expand Down Expand Up @@ -37,89 +41,167 @@ const deleteVerbs: Verbs = {
past: 'deleted',
};

const eventTypes: Record<string, EcsEventType> = {
creation: 'creation',
deletion: 'deletion',
change: 'change',
access: 'access',
};

/**
* Database constant for ECS category for use for audit logging.
*/
export const DATABASE_CATEGORY: EcsEventCategory[] = ['database'];

/**
* ECS Outcomes for audit logging.
*/
export const ECS_OUTCOMES: Record<string, EcsEventOutcome> = {
failure: 'failure',
success: 'success',
unknown: 'unknown',
};

/**
* Definition of all APIs within the cases backend.
*/
export const Operations: Record<ReadOperations | WriteOperations, OperationDetails> = {
// case operations
[WriteOperations.CreateCase]: {
type: EventType.CREATION,
type: eventTypes.creation,
name: WriteOperations.CreateCase,
action: 'create-case',
verbs: createVerbs,
docType: 'case',
savedObjectType: CASE_SAVED_OBJECT,
},
[WriteOperations.DeleteCase]: {
type: EventType.DELETION,
type: eventTypes.deletion,
name: WriteOperations.DeleteCase,
action: 'delete-case',
verbs: deleteVerbs,
docType: 'case',
savedObjectType: CASE_SAVED_OBJECT,
},
[WriteOperations.UpdateCase]: {
type: EventType.CHANGE,
type: eventTypes.change,
name: WriteOperations.UpdateCase,
action: 'update-case',
verbs: updateVerbs,
docType: 'case',
savedObjectType: CASE_SAVED_OBJECT,
},
[WriteOperations.CreateConfiguration]: {
type: EventType.CREATION,
type: eventTypes.creation,
name: WriteOperations.CreateConfiguration,
action: 'create-configuration',
verbs: createVerbs,
docType: 'case configuration',
savedObjectType: CASE_CONFIGURE_SAVED_OBJECT,
},
[WriteOperations.UpdateConfiguration]: {
type: EventType.CHANGE,
type: eventTypes.change,
name: WriteOperations.UpdateConfiguration,
action: 'update-configuration',
verbs: updateVerbs,
docType: 'case configuration',
savedObjectType: CASE_CONFIGURE_SAVED_OBJECT,
},
[ReadOperations.FindConfigurations]: {
type: eventTypes.access,
name: ReadOperations.FindConfigurations,
action: 'find-configurations',
verbs: accessVerbs,
docType: 'case configurations',
savedObjectType: CASE_CONFIGURE_SAVED_OBJECT,
},
[ReadOperations.GetCase]: {
type: EventType.ACCESS,
type: eventTypes.access,
name: ReadOperations.GetCase,
action: 'get-case',
verbs: accessVerbs,
docType: 'case',
savedObjectType: CASE_SAVED_OBJECT,
},
[ReadOperations.FindCases]: {
type: EventType.ACCESS,
type: eventTypes.access,
name: ReadOperations.FindCases,
action: 'find-cases',
verbs: accessVerbs,
docType: 'cases',
savedObjectType: CASE_SAVED_OBJECT,
},
[ReadOperations.GetTags]: {
type: EventType.ACCESS,
type: eventTypes.access,
name: ReadOperations.GetCase,
action: 'get-tags',
verbs: accessVerbs,
docType: 'case',
savedObjectType: CASE_SAVED_OBJECT,
},
[ReadOperations.GetReporters]: {
type: EventType.ACCESS,
type: eventTypes.access,
name: ReadOperations.GetReporters,
action: 'get-reporters',
verbs: accessVerbs,
docType: 'case',
savedObjectType: CASE_SAVED_OBJECT,
},
[ReadOperations.FindConfigurations]: {
type: EventType.ACCESS,
name: ReadOperations.FindConfigurations,
action: 'find-configurations',
// comments operations
[WriteOperations.CreateComment]: {
type: eventTypes.creation,
name: WriteOperations.CreateComment,
action: 'create-comment',
verbs: createVerbs,
docType: 'comments',
savedObjectType: CASE_COMMENT_SAVED_OBJECT,
},
[WriteOperations.DeleteAllComments]: {
type: eventTypes.deletion,
name: WriteOperations.DeleteAllComments,
action: 'delete-all-comments',
verbs: deleteVerbs,
docType: 'comments',
savedObjectType: CASE_COMMENT_SAVED_OBJECT,
},
[WriteOperations.DeleteComment]: {
type: eventTypes.deletion,
name: WriteOperations.DeleteComment,
action: 'delete-comment',
verbs: deleteVerbs,
docType: 'comments',
savedObjectType: CASE_COMMENT_SAVED_OBJECT,
},
[WriteOperations.UpdateComment]: {
type: eventTypes.change,
name: WriteOperations.UpdateComment,
action: 'update-comments',
verbs: updateVerbs,
docType: 'comments',
savedObjectType: CASE_COMMENT_SAVED_OBJECT,
},
[ReadOperations.GetComment]: {
type: eventTypes.access,
name: ReadOperations.GetComment,
action: 'get-comment',
verbs: accessVerbs,
docType: 'case configurations',
savedObjectType: CASE_CONFIGURE_SAVED_OBJECT,
docType: 'comments',
savedObjectType: CASE_COMMENT_SAVED_OBJECT,
},
[ReadOperations.GetAllComments]: {
type: eventTypes.access,
name: ReadOperations.GetAllComments,
action: 'get-all-comment',
verbs: accessVerbs,
docType: 'comments',
savedObjectType: CASE_COMMENT_SAVED_OBJECT,
},
[ReadOperations.FindComments]: {
type: eventTypes.access,
name: ReadOperations.FindComments,
action: 'find-comments',
verbs: accessVerbs,
docType: 'comments',
savedObjectType: CASE_COMMENT_SAVED_OBJECT,
},
};
30 changes: 21 additions & 9 deletions x-pack/plugins/cases/server/authorization/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
* 2.0.
*/

import { KibanaRequest } from 'kibana/server';
import { EcsEventType, KibanaRequest } from 'kibana/server';
import { KueryNode } from 'src/plugins/data/common';
import { EventType } from '../../../security/server';
import { Space } from '../../../spaces/server';

/**
Expand All @@ -21,24 +20,37 @@ export interface Verbs {

export type GetSpaceFn = (request: KibanaRequest) => Promise<Space | undefined>;

// TODO: we need to have an operation per entity route so I think we need to create a bunch like
// getCase, getComment, getSubCase etc for each, need to think of a clever way of creating them for all the routes easily?

// if you add a value here you'll likely also need to make changes here:
// x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/cases.ts
/**
* Read operations for the cases APIs.
*
* NOTE: If you add a value here you'll likely also need to make changes here:
* x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/cases.ts
*/
export enum ReadOperations {
GetCase = 'getCase',
FindCases = 'findCases',
GetComment = 'getComment',
GetAllComments = 'getAllComments',
FindComments = 'findComments',
GetTags = 'getTags',
GetReporters = 'getReporters',
FindConfigurations = 'findConfigurations',
}

// TODO: comments
/**
* Write operations for the cases APIs.
*
* NOTE: If you add a value here you'll likely also need to make changes here:
* x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/cases.ts
*/
export enum WriteOperations {
CreateCase = 'createCase',
DeleteCase = 'deleteCase',
UpdateCase = 'updateCase',
CreateComment = 'createComment',
DeleteAllComments = 'deleteAllComments',
DeleteComment = 'deleteComment',
UpdateComment = 'updateComment',
CreateConfiguration = 'createConfiguration',
UpdateConfiguration = 'updateConfiguration',
}
Expand All @@ -47,7 +59,7 @@ export enum WriteOperations {
* Defines the structure for a case API route.
*/
export interface OperationDetails {
type: EventType;
type: EcsEventType;
name: ReadOperations | WriteOperations;
action: string;
verbs: Verbs;
Expand Down
Loading