mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[SIEM] Add feature controls and tests for feature controls (#36058)
## Summary Add feature controls and tests for feature controls * Copied the same pattern from infra * Reran integration and the product and everything appears to work the same as before * https://github.com/elastic/ingest-dev/issues/401 ### Checklist Use ~~strikethroughs~~ to remove checklist items you don't feel are applicable to this PR. ~~- [ ] This was checked for cross-browser compatibility, [including a check against IE11](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility)~~ ~~- [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md)~~ ~~- [ ] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials~~ - [x] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios ~~- [ ] This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist)~~ ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process) - [ ] This includes a feature addition or change that requires a release note and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)
This commit is contained in:
parent
51e68cbda0
commit
371efd9bb1
6 changed files with 276 additions and 19 deletions
243
x-pack/test/api_integration/apis/siem/feature_controls.ts
Normal file
243
x-pack/test/api_integration/apis/siem/feature_controls.ts
Normal file
|
@ -0,0 +1,243 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import gql from 'graphql-tag';
|
||||
import { SecurityService, SpacesService } from '../../../common/services';
|
||||
import { KbnTestProvider } from './types';
|
||||
|
||||
const introspectionQuery = gql`
|
||||
query Schema {
|
||||
__schema {
|
||||
queryType {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const featureControlsTests: KbnTestProvider = ({ getService }) => {
|
||||
const supertest = getService('supertestWithoutAuth');
|
||||
const security: SecurityService = getService('security');
|
||||
const spaces: SpacesService = getService('spaces');
|
||||
const clientFactory = getService('siemGraphQLClientFactory');
|
||||
|
||||
const expectGraphQL404 = (result: any) => {
|
||||
expect(result.response).to.be(undefined);
|
||||
expect(result.error).not.to.be(undefined);
|
||||
expect(result.error).to.have.property('networkError');
|
||||
expect(result.error.networkError).to.have.property('statusCode', 404);
|
||||
};
|
||||
|
||||
const expectGraphQLResponse = (result: any) => {
|
||||
expect(result.error).to.be(undefined);
|
||||
expect(result.response).to.have.property('data');
|
||||
expect(result.response.data).to.be.an('object');
|
||||
};
|
||||
|
||||
const expectGraphIQL404 = (result: any) => {
|
||||
expect(result.error).to.be(undefined);
|
||||
expect(result.response).not.to.be(undefined);
|
||||
expect(result.response).to.have.property('statusCode', 404);
|
||||
};
|
||||
|
||||
const expectGraphIQLResponse = (result: any) => {
|
||||
expect(result.error).to.be(undefined);
|
||||
expect(result.response).not.to.be(undefined);
|
||||
expect(result.response).to.have.property('statusCode', 200);
|
||||
};
|
||||
|
||||
const executeGraphQLQuery = async (username: string, password: string, spaceId?: string) => {
|
||||
const queryOptions = {
|
||||
query: introspectionQuery,
|
||||
};
|
||||
|
||||
const basePath = spaceId ? `/s/${spaceId}` : '';
|
||||
|
||||
const client = clientFactory({ username, password, basePath });
|
||||
let error;
|
||||
let response;
|
||||
try {
|
||||
response = await client.query(queryOptions);
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
return {
|
||||
error,
|
||||
response,
|
||||
};
|
||||
};
|
||||
|
||||
const executeGraphIQLRequest = async (username: string, password: string, spaceId?: string) => {
|
||||
const basePath = spaceId ? `/s/${spaceId}` : '';
|
||||
|
||||
return supertest
|
||||
.get(`${basePath}/api/siem/graphql/graphiql`)
|
||||
.auth(username, password)
|
||||
.then((response: any) => ({ error: undefined, response }))
|
||||
.catch((error: any) => ({ error, response: undefined }));
|
||||
};
|
||||
|
||||
describe('feature controls', () => {
|
||||
it(`APIs can't be accessed by user with no privileges`, async () => {
|
||||
const username = 'logstash_read';
|
||||
const roleName = 'logstash_read';
|
||||
const password = `${username}-password`;
|
||||
try {
|
||||
await security.role.create(roleName, {});
|
||||
|
||||
await security.user.create(username, {
|
||||
password,
|
||||
roles: [roleName],
|
||||
full_name: 'a kibana user',
|
||||
});
|
||||
|
||||
const graphQLResult = await executeGraphQLQuery(username, password);
|
||||
expectGraphQL404(graphQLResult);
|
||||
|
||||
const graphQLIResult = await executeGraphIQLRequest(username, password);
|
||||
expectGraphIQL404(graphQLIResult);
|
||||
} finally {
|
||||
await security.role.delete(roleName);
|
||||
await security.user.delete(username);
|
||||
}
|
||||
});
|
||||
|
||||
it('APIs can be accessed user with global "all" privileges', async () => {
|
||||
const username = 'global_all';
|
||||
const roleName = 'global_all';
|
||||
const password = `${username}-password`;
|
||||
try {
|
||||
await security.role.create(roleName, {
|
||||
kibana: [
|
||||
{
|
||||
base: ['all'],
|
||||
spaces: ['*'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await security.user.create(username, {
|
||||
password,
|
||||
roles: [roleName],
|
||||
full_name: 'a kibana user',
|
||||
});
|
||||
|
||||
const graphQLResult = await executeGraphQLQuery(username, password);
|
||||
expectGraphQLResponse(graphQLResult);
|
||||
|
||||
const graphQLIResult = await executeGraphIQLRequest(username, password);
|
||||
expectGraphIQLResponse(graphQLIResult);
|
||||
} finally {
|
||||
await security.role.delete(roleName);
|
||||
await security.user.delete(username);
|
||||
}
|
||||
});
|
||||
|
||||
// this could be any role which doesn't have access to the siem feature
|
||||
it(`APIs can't be accessed by user with dashboard "all" privileges`, async () => {
|
||||
const username = 'dashboard_all';
|
||||
const roleName = 'dashboard_all';
|
||||
const password = `${username}-password`;
|
||||
try {
|
||||
await security.role.create(roleName, {
|
||||
kibana: [
|
||||
{
|
||||
feature: {
|
||||
dashboard: ['all'],
|
||||
},
|
||||
spaces: ['*'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await security.user.create(username, {
|
||||
password,
|
||||
roles: [roleName],
|
||||
full_name: 'a kibana user',
|
||||
});
|
||||
|
||||
const graphQLResult = await executeGraphQLQuery(username, password);
|
||||
expectGraphQL404(graphQLResult);
|
||||
|
||||
const graphQLIResult = await executeGraphIQLRequest(username, password);
|
||||
expectGraphIQL404(graphQLIResult);
|
||||
} finally {
|
||||
await security.role.delete(roleName);
|
||||
await security.user.delete(username);
|
||||
}
|
||||
});
|
||||
|
||||
describe('spaces', () => {
|
||||
// the following tests create a user_1 which has siem read access to space_1 and dashboard all access to space_2
|
||||
const space1Id = 'space_1';
|
||||
const space2Id = 'space_2';
|
||||
|
||||
const roleName = 'user_1';
|
||||
const username = 'user_1';
|
||||
const password = 'user_1-password';
|
||||
|
||||
before(async () => {
|
||||
await spaces.create({
|
||||
id: space1Id,
|
||||
name: space1Id,
|
||||
disabledFeatures: [],
|
||||
});
|
||||
await spaces.create({
|
||||
id: space2Id,
|
||||
name: space2Id,
|
||||
disabledFeatures: [],
|
||||
});
|
||||
await security.role.create(roleName, {
|
||||
kibana: [
|
||||
{
|
||||
feature: {
|
||||
siem: ['read'],
|
||||
},
|
||||
spaces: [space1Id],
|
||||
},
|
||||
{
|
||||
feature: {
|
||||
dashboard: ['all'],
|
||||
},
|
||||
spaces: [space2Id],
|
||||
},
|
||||
],
|
||||
});
|
||||
await security.user.create(username, {
|
||||
password,
|
||||
roles: [roleName],
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await spaces.delete(space1Id);
|
||||
await spaces.delete(space2Id);
|
||||
await security.role.delete(roleName);
|
||||
await security.user.delete(username);
|
||||
});
|
||||
|
||||
it('user_1 can access APIs in space_1', async () => {
|
||||
const graphQLResult = await executeGraphQLQuery(username, password, space1Id);
|
||||
expectGraphQLResponse(graphQLResult);
|
||||
|
||||
const graphQLIResult = await executeGraphIQLRequest(username, password, space1Id);
|
||||
expectGraphIQLResponse(graphQLIResult);
|
||||
});
|
||||
|
||||
it(`user_1 can't access APIs in space_2`, async () => {
|
||||
const graphQLResult = await executeGraphQLQuery(username, password, space2Id);
|
||||
expectGraphQL404(graphQLResult);
|
||||
|
||||
const graphQLIResult = await executeGraphIQLRequest(username, password, space2Id);
|
||||
expectGraphIQL404(graphQLIResult);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default featureControlsTests;
|
|
@ -22,5 +22,6 @@ export default function ({ loadTestFile }) {
|
|||
loadTestFile(require.resolve('./uncommon_processes'));
|
||||
loadTestFile(require.resolve('./users'));
|
||||
loadTestFile(require.resolve('./tls'));
|
||||
loadTestFile(require.resolve('./feature_controls'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -11,7 +11,8 @@ import {
|
|||
UsageAPIProvider,
|
||||
InfraOpsGraphQLClientProvider,
|
||||
InfraOpsGraphQLClientFactoryProvider,
|
||||
SiemGraphQLProvider,
|
||||
SiemGraphQLClientProvider,
|
||||
SiemGraphQLClientFactoryProvider,
|
||||
InfraOpsSourceConfigurationProvider,
|
||||
} from './services';
|
||||
|
||||
|
@ -41,7 +42,8 @@ export default async function ({ readConfigFile }) {
|
|||
esSupertestWithoutAuth: EsSupertestWithoutAuthProvider,
|
||||
infraOpsGraphQLClient: InfraOpsGraphQLClientProvider,
|
||||
infraOpsGraphQLClientFactory: InfraOpsGraphQLClientFactoryProvider,
|
||||
siemGraphQLClient: SiemGraphQLProvider,
|
||||
siemGraphQLClientFactory: SiemGraphQLClientFactoryProvider,
|
||||
siemGraphQLClient: SiemGraphQLClientProvider,
|
||||
infraOpsSourceConfiguration: InfraOpsSourceConfigurationProvider,
|
||||
es: EsProvider,
|
||||
esArchiver: kibanaCommonConfig.get('services.esArchiver'),
|
||||
|
|
|
@ -9,5 +9,5 @@ export { EsSupertestWithoutAuthProvider } from './es_supertest_without_auth';
|
|||
export { SupertestWithoutAuthProvider } from './supertest_without_auth';
|
||||
export { UsageAPIProvider } from './usage_api';
|
||||
export { InfraOpsGraphQLClientProvider, InfraOpsGraphQLClientFactoryProvider } from './infraops_graphql_client';
|
||||
export { SiemGraphQLProvider } from './siem_graphql_client';
|
||||
export { SiemGraphQLClientProvider, SiemGraphQLClientFactoryProvider } from './siem_graphql_client';
|
||||
export { InfraOpsSourceConfigurationProvider } from './infraops_source_configuration';
|
||||
|
|
|
@ -12,23 +12,34 @@ import { HttpLink } from 'apollo-link-http';
|
|||
|
||||
import introspectionQueryResultData from '../../../plugins/siem/public/graphql/introspection.json';
|
||||
|
||||
export function SiemGraphQLProvider({ getService }) {
|
||||
const config = getService('config');
|
||||
const kbnURL = formatUrl(config.get('servers.kibana'));
|
||||
export function SiemGraphQLClientProvider({ getService }) {
|
||||
return new SiemGraphQLClientFactoryProvider({ getService })();
|
||||
}
|
||||
|
||||
return new ApolloClient({
|
||||
cache: new InMemoryCache({
|
||||
fragmentMatcher: new IntrospectionFragmentMatcher({
|
||||
introspectionQueryResultData,
|
||||
}),
|
||||
}),
|
||||
link: new HttpLink({
|
||||
export function SiemGraphQLClientFactoryProvider({ getService }) {
|
||||
const config = getService('config');
|
||||
const [superUsername, superPassword] = config.get('servers.elasticsearch.auth').split(':');
|
||||
|
||||
return function ({ username = superUsername, password = superPassword, basePath = null } = {}) {
|
||||
const kbnURLWithoutAuth = formatUrl({ ...config.get('servers.kibana'), auth: false });
|
||||
|
||||
const httpLink = new HttpLink({
|
||||
credentials: 'same-origin',
|
||||
fetch,
|
||||
headers: {
|
||||
'kbn-xsrf': 'xxx',
|
||||
authorization: `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`,
|
||||
},
|
||||
uri: `${kbnURL}/api/siem/graphql`,
|
||||
}),
|
||||
});
|
||||
uri: `${kbnURLWithoutAuth}${basePath || ''}/api/siem/graphql`,
|
||||
});
|
||||
|
||||
return new ApolloClient({
|
||||
cache: new InMemoryCache({
|
||||
fragmentMatcher: new IntrospectionFragmentMatcher({
|
||||
introspectionQueryResultData,
|
||||
}),
|
||||
}),
|
||||
link: httpLink,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -8461,9 +8461,9 @@ core-js@^2.5.3, core-js@^2.5.7:
|
|||
integrity sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==
|
||||
|
||||
core-js@^2.6.5:
|
||||
version "2.6.7"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.7.tgz#c2b19af9b50ec49c356ee087d6155632a889e968"
|
||||
integrity sha512-ydmsQxDVH7lDpYoirXft8S83ddKKfdsrsmWhtyj7xafXVLbLhKOyfD7kAi2ueFfeP7m9rNavjW59O3hLLzzC5A==
|
||||
version "2.6.8"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.8.tgz#dc3a1e633a04267944e0cb850d3880f340248139"
|
||||
integrity sha512-RWlREFU74TEkdXzyl1bka66O3kYp8jeTXrvJZDzVVMH8AiHUSOFpL1yfhQJ+wHocAm1m+4971W1PPzfLuCv1vg==
|
||||
|
||||
core-util-is@1.0.2, core-util-is@~1.0.0:
|
||||
version "1.0.2"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue