[Security Solution][Endpoint] Adds FTR service for roles and users create/delete using endpoint security RBAC privileges (#144710)

## Summary

- New FTR service for create/delete rules and users using roles with new
security sub-privileges in.
- Create/delete roles and users during before/after suite hook instead
of doing it on each test case.
- Updates a test using new roles/users.

### TBD

- Move role files to the common folder once this is merged:
https://github.com/elastic/kibana/pull/143880 - DONE
- Use new role/users in other tests in this suite.
- Move new service to an upper level?

### For maintainers

- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
David Sánchez 2022-12-02 09:13:26 +01:00 committed by GitHub
parent 2b3755e395
commit 2a34e0e783
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 420 additions and 82 deletions

View file

@ -0,0 +1,25 @@
/*
* 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 type { Role } from '@kbn/security-plugin/common';
import { getNoResponseActionsRole } from './without_response_actions_role';
export const getDetectionsEngineer: () => Omit<Role, 'name'> = () => {
const noResponseActionsRole = getNoResponseActionsRole();
return {
...noResponseActionsRole,
kibana: [
{
...noResponseActionsRole.kibana[0],
feature: {
...noResponseActionsRole.kibana[0].feature,
siem: ['minimal_all', 'actions_log_management_read'],
},
},
],
};
};

View file

@ -0,0 +1,30 @@
/*
* 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 type { Role } from '@kbn/security-plugin/common';
import { getNoResponseActionsRole } from './without_response_actions_role';
export const getEndpointOperationsAnalyst: () => Omit<Role, 'name'> = () => {
const noResponseActionsRole = getNoResponseActionsRole();
return {
...noResponseActionsRole,
kibana: [
{
...noResponseActionsRole.kibana[0],
feature: {
...noResponseActionsRole.kibana[0].feature,
siem: [
'minimal_all',
'actions_log_management_all',
'host_isolation_all',
'process_operations_all',
],
},
},
],
};
};

View file

@ -0,0 +1,25 @@
/*
* 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 type { Role } from '@kbn/security-plugin/common';
import { getNoResponseActionsRole } from './without_response_actions_role';
export const getEndpointSecurityPolicyManager: () => Omit<Role, 'name'> = () => {
const noResponseActionsRole = getNoResponseActionsRole();
return {
...noResponseActionsRole,
kibana: [
{
...noResponseActionsRole.kibana[0],
feature: {
...noResponseActionsRole.kibana[0].feature,
siem: ['minimal_all'],
},
},
],
};
};

View file

@ -0,0 +1,30 @@
/*
* 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 type { Role } from '@kbn/security-plugin/common';
import { getNoResponseActionsRole } from './without_response_actions_role';
export const getHunter: () => Omit<Role, 'name'> = () => {
const noResponseActionsRole = getNoResponseActionsRole();
return {
...noResponseActionsRole,
kibana: [
{
...noResponseActionsRole.kibana[0],
feature: {
...noResponseActionsRole.kibana[0].feature,
siem: [
'minimal_all',
'actions_log_management_all',
'host_isolation_all',
'process_operations_all',
],
},
},
],
};
};

View file

@ -1,10 +0,0 @@
/*
* 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 withResponseActionsUser from './with_response_actions_user.json';
import noResponseActionsUser from './without_response_actions_user.json';
export { withResponseActionsUser, noResponseActionsUser };

View file

@ -0,0 +1,25 @@
/*
* 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 type { Role } from '@kbn/security-plugin/common';
import { getNoResponseActionsRole } from './without_response_actions_role';
export const getPlatformEngineer: () => Omit<Role, 'name'> = () => {
const noResponseActionsRole = getNoResponseActionsRole();
return {
...noResponseActionsRole,
kibana: [
{
...noResponseActionsRole.kibana[0],
feature: {
...noResponseActionsRole.kibana[0].feature,
siem: ['minimal_all', 'actions_log_management_read'],
},
},
],
};
};

View file

@ -0,0 +1,30 @@
/*
* 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 type { Role } from '@kbn/security-plugin/common';
import { getNoResponseActionsRole } from './without_response_actions_role';
export const getSocManager: () => Omit<Role, 'name'> = () => {
const noResponseActionsRole = getNoResponseActionsRole();
return {
...noResponseActionsRole,
kibana: [
{
...noResponseActionsRole.kibana[0],
feature: {
...noResponseActionsRole.kibana[0].feature,
siem: [
'minimal_all',
'actions_log_management_all',
'host_isolation_all',
'process_operations_all',
],
},
},
],
};
};

View file

@ -0,0 +1,25 @@
/*
* 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 type { Role } from '@kbn/security-plugin/common';
import { getNoResponseActionsRole } from './without_response_actions_role';
export const getT1Analyst: () => Omit<Role, 'name'> = () => {
const noResponseActionsRole = getNoResponseActionsRole();
return {
...noResponseActionsRole,
kibana: [
{
...noResponseActionsRole.kibana[0],
feature: {
...noResponseActionsRole.kibana[0].feature,
siem: ['minimal_all'],
},
},
],
};
};

View file

@ -0,0 +1,25 @@
/*
* 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 type { Role } from '@kbn/security-plugin/common';
import { getNoResponseActionsRole } from './without_response_actions_role';
export const getT2Analyst: () => Omit<Role, 'name'> = () => {
const noResponseActionsRole = getNoResponseActionsRole();
return {
...noResponseActionsRole,
kibana: [
{
...noResponseActionsRole.kibana[0],
feature: {
...noResponseActionsRole.kibana[0].feature,
siem: ['minimal_all', 'actions_log_management_read'],
},
},
],
};
};

View file

@ -0,0 +1,25 @@
/*
* 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 type { Role } from '@kbn/security-plugin/common';
import { getNoResponseActionsRole } from './without_response_actions_role';
export const getThreadIntelligenceAnalyst: () => Omit<Role, 'name'> = () => {
const noResponseActionsRole = getNoResponseActionsRole();
return {
...noResponseActionsRole,
kibana: [
{
...noResponseActionsRole.kibana[0],
feature: {
...noResponseActionsRole.kibana[0].feature,
siem: ['minimal_all', 'actions_log_management_read'],
},
},
],
};
};

View file

@ -6,22 +6,25 @@
*/
import type { Role } from '@kbn/security-plugin/common';
import { noResponseActionsRole } from './without_response_actions_role';
import { getNoResponseActionsRole } from './without_response_actions_role';
export const withResponseActionsRole: Omit<Role, 'name'> = {
...noResponseActionsRole,
kibana: [
{
...noResponseActionsRole.kibana[0],
feature: {
...noResponseActionsRole.kibana[0].feature,
siem: [
...noResponseActionsRole.kibana[0].feature.siem,
'host_isolation_all',
'process_operations_all',
'file_operations_all',
],
export const getWithResponseActionsRole: () => Omit<Role, 'name'> = () => {
const noResponseActionsRole = getNoResponseActionsRole();
return {
...noResponseActionsRole,
kibana: [
{
...noResponseActionsRole.kibana[0],
feature: {
...noResponseActionsRole.kibana[0].feature,
siem: [
...noResponseActionsRole.kibana[0].feature.siem,
'host_isolation_all',
'process_operations_all',
'file_operations_all',
],
},
},
},
],
],
};
};

View file

@ -1,6 +0,0 @@
{
"username": "withResponseActions",
"full_name": "With RA",
"password": "changeme",
"roles": ["withResponseActions"]
}

View file

@ -7,7 +7,7 @@
import type { Role } from '@kbn/security-plugin/common';
export const noResponseActionsRole: Omit<Role, 'name'> = {
export const getNoResponseActionsRole: () => Omit<Role, 'name'> = () => ({
elasticsearch: {
cluster: ['manage'],
indices: [
@ -59,4 +59,4 @@ export const noResponseActionsRole: Omit<Role, 'name'> = {
spaces: ['*'],
},
],
};
});

View file

@ -1,6 +0,0 @@
{
"password": "changeme",
"roles": ["noResponseActions"],
"username": "noResponseActions",
"full_name": "No RA"
}

View file

@ -21,9 +21,29 @@ import { indexHostsAndAlerts } from '../../common/endpoint/index_data';
import { ANCESTRY_LIMIT, EndpointDocGenerator } from '../../common/endpoint/generate_data';
import { fetchStackVersion } from './common/stack_services';
import { ENDPOINT_ALERTS_INDEX, ENDPOINT_EVENTS_INDEX } from './common/constants';
import { withResponseActionsUser, noResponseActionsUser } from './common/roles_users';
import { withResponseActionsRole } from './common/roles_users/with_response_actions_role';
import { noResponseActionsRole } from './common/roles_users/without_response_actions_role';
import { getWithResponseActionsRole } from './common/roles_users/with_response_actions_role';
import { getNoResponseActionsRole } from './common/roles_users/without_response_actions_role';
import { getT1Analyst } from './common/roles_users/t1_analyst';
import { getT2Analyst } from './common/roles_users/t2_analyst';
import { getEndpointOperationsAnalyst } from './common/roles_users/endpoint_operations_analyst';
import { getEndpointSecurityPolicyManager } from './common/roles_users/endpoint_security_policy_manager';
import { getHunter } from './common/roles_users/hunter';
import { getPlatformEngineer } from './common/roles_users/platform_engineer';
import { getSocManager } from './common/roles_users/soc_manager';
import { getThreadIntelligenceAnalyst } from './common/roles_users/thread_intelligence_analyst';
const rolesMapping: { [id: string]: Omit<Role, 'name'> } = {
t1Analyst: getT1Analyst(),
t2Analyst: getT2Analyst(),
hunter: getHunter(),
threadIntelligenceAnalyst: getThreadIntelligenceAnalyst(),
socManager: getSocManager(),
platformEngineer: getPlatformEngineer(),
endpointOperationsAnalyst: getEndpointOperationsAnalyst(),
endpointSecurityPolicyManager: getEndpointSecurityPolicyManager(),
withResponseActionsRole: getWithResponseActionsRole(),
noResponseActionsRole: getNoResponseActionsRole(),
};
main();
@ -293,7 +313,7 @@ async function main() {
rbacUser: {
alias: 'rbac',
describe:
"Creates the 'WithResponseActions' and 'NoResponseActions' users and roles, password=changeme. The former has the kibana permissions for response actions and the latter does not. Neither have the superuser role. ",
'Creates a set of roles and users, password=changeme, with RBAC privileges enabled/disabled. Neither have the superuser role. ',
type: 'boolean',
default: false,
},
@ -371,28 +391,18 @@ async function main() {
}
if (argv.rbacUser) {
// Add role and user with response actions kibana privileges
const withRARole = await addRole(kbnClient, {
name: 'withResponseActions',
...withResponseActionsRole,
});
if (withRARole) {
console.log(`Successfully added ${withRARole} role`);
await addUser(client, withResponseActionsUser);
} else {
console.log('Failed to add role, withResponseActions');
}
// Add role and user with no response actions kibana privileges
const noRARole = await addRole(kbnClient, {
name: 'noResponseActions',
...noResponseActionsRole,
});
if (noRARole) {
console.log(`Successfully added ${noRARole} role`);
await addUser(client, noResponseActionsUser);
} else {
console.log('Failed to add role, noResponseActions');
// Add roles and users with response actions kibana privileges
for (const role of Object.keys(rolesMapping)) {
const addedRole = await addRole(kbnClient, {
name: role,
...rolesMapping[role],
});
if (addedRole) {
console.log(`Successfully added ${role} role`);
await addUser(client, { username: role, password: 'changeme', roles: [role] });
} else {
console.log(`Failed to add role, ${role}`);
}
}
}

View file

@ -17,26 +17,12 @@ import {
UNISOLATE_HOST_ROUTE_V2,
} from '@kbn/security-solution-plugin/common/endpoint/constants';
import { FtrProviderContext } from '../ftr_provider_context';
import {
createUserAndRole,
deleteUserAndRole,
ROLES,
} from '../../common/services/security_solution';
import { ROLE } from '../services/roles_users';
export default function ({ getService }: FtrProviderContext) {
const supertestWithoutAuth = getService('supertestWithoutAuth');
describe('When attempting to call an endpoint api with no authz', () => {
before(async () => {
// create role/user
await createUserAndRole(getService, ROLES.t1_analyst);
});
after(async () => {
// delete role/user
await deleteUserAndRole(getService, ROLES.t1_analyst);
});
const apiList = [
{
method: 'get',
@ -90,7 +76,7 @@ export default function ({ getService }: FtrProviderContext) {
apiListItem.path
}]`, async () => {
await supertestWithoutAuth[apiListItem.method](apiListItem.path)
.auth(ROLES.t1_analyst, 'changeme')
.auth(ROLE.t1_analyst, 'changeme')
.set('kbn-xsrf', 'xxx')
.send(apiListItem.body)
.expect(403, {

View file

@ -8,12 +8,14 @@
import { getRegistryUrl as getRegistryUrlFromIngest } from '@kbn/fleet-plugin/server';
import { FtrProviderContext } from '../ftr_provider_context';
import { isRegistryEnabled, getRegistryUrlFromTestEnv } from '../registry';
import { ROLE } from '../services/roles_users';
export default function endpointAPIIntegrationTests(providerContext: FtrProviderContext) {
const { loadTestFile, getService } = providerContext;
describe('Endpoint plugin', function () {
const ingestManager = getService('ingestManager');
const rolesUsersProvider = getService('rolesUsersProvider');
const log = getService('log');
@ -24,9 +26,22 @@ export default function endpointAPIIntegrationTests(providerContext: FtrProvider
const registryUrl = getRegistryUrlFromTestEnv() ?? getRegistryUrlFromIngest();
log.info(`Package registry URL for tests: ${registryUrl}`);
const roles = Object.values(ROLE);
before(async () => {
await ingestManager.setup();
// create role/user
for (const role of roles) {
await rolesUsersProvider.createRole({ predefinedRole: role });
await rolesUsersProvider.createUser({ name: role, roles: [role] });
}
});
after(async () => {
// delete role/user
await rolesUsersProvider.deleteUsers(roles);
await rolesUsersProvider.deleteRoles(roles);
});
loadTestFile(require.resolve('./resolver'));
loadTestFile(require.resolve('./metadata'));
loadTestFile(require.resolve('./policy'));

View file

@ -7,6 +7,7 @@
import { services as xPackAPIServices } from '../../api_integration/services';
import { ResolverGeneratorProvider } from './resolver';
import { RolesUsersProvider } from './roles_users';
import { EndpointTestResources } from '../../security_solution_endpoint/services/endpoint';
import { EndpointPolicyTestResourcesProvider } from '../../security_solution_endpoint/services/endpoint_policy';
import { EndpointArtifactsTestResources } from '../../security_solution_endpoint/services/endpoint_artifacts';
@ -17,4 +18,5 @@ export const services = {
endpointTestResources: EndpointTestResources,
endpointPolicyTestResources: EndpointPolicyTestResourcesProvider,
endpointArtifactTestResources: EndpointArtifactsTestResources,
rolesUsersProvider: RolesUsersProvider,
};

View file

@ -0,0 +1,104 @@
/*
* 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 type { Role } from '@kbn/security-plugin/common';
import { getT1Analyst } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/t1_analyst';
import { getT2Analyst } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/t2_analyst';
import { getHunter } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/hunter';
import { getThreadIntelligenceAnalyst } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/thread_intelligence_analyst';
import { getSocManager } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/soc_manager';
import { getPlatformEngineer } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/platform_engineer';
import { getEndpointOperationsAnalyst } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/endpoint_operations_analyst';
import { getEndpointSecurityPolicyManager } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/endpoint_security_policy_manager';
import { FtrProviderContext } from '../ftr_provider_context';
export enum ROLE {
t1_analyst = 't1Analyst',
t2_analyst = 't2Analyst',
analyst_hunter = 'hunter',
thread_intelligence_analyst = 'threadIntelligenceAnalyst',
detections_engineer = 'detectionsEngineer',
soc_manager = 'socManager',
platform_engineer = 'platformEngineer',
endpoint_operations_analyst = 'endpointOperationsAnalyst',
endpoint_security_policy_manager = 'endpointSecurityPolicyManager',
}
const rolesMapping: { [id: string]: Omit<Role, 'name'> } = {
t1Analyst: getT1Analyst(),
t2Analyst: getT2Analyst(),
hunter: getHunter(),
threadIntelligenceAnalyst: getThreadIntelligenceAnalyst(),
socManager: getSocManager(),
platformEngineer: getPlatformEngineer(),
endpointOperationsAnalyst: getEndpointOperationsAnalyst(),
endpointSecurityPolicyManager: getEndpointSecurityPolicyManager(),
};
export function RolesUsersProvider({ getService }: FtrProviderContext) {
const security = getService('security');
return {
/**
* Creates an user with specific values
* @param user
*/
async createUser(user: { name: string; roles: string[]; password?: string }): Promise<void> {
const { name, roles, password } = user;
await security.user.create(name, { roles, password: password ?? 'changeme' });
},
/**
* Deletes specified users by username
* @param names[]
*/
async deleteUsers(names: string[]): Promise<void> {
for (const name of names) {
await security.user.delete(name);
}
},
/**
* Creates a role using predefined role config if defined or a custom one. It also allows define extra privileges.
* @param options
*/
async createRole(options: {
predefinedRole?: ROLE;
extraPrivileges?: string[];
customRole?: { roleName: string; extraPrivileges: string[] };
}): Promise<void> {
const { predefinedRole, customRole, extraPrivileges } = options;
if (predefinedRole) {
const roleConfig = rolesMapping[predefinedRole];
if (extraPrivileges) {
roleConfig.kibana[0].feature.siem = [
...roleConfig.kibana[0].feature.siem,
...extraPrivileges,
];
}
await security.role.create(predefinedRole, rolesMapping[predefinedRole]);
}
if (customRole) {
await security.role.create(customRole.roleName, {
permissions: { feature: { siem: [...customRole.extraPrivileges] } },
});
}
},
/**
* Deletes specified roles by name
* @param roles[]
*/
async deleteRoles(roles: string[]): Promise<void> {
for (const role of roles) {
await security.role.delete(role);
}
},
};
}