[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:
Frank Hassanabad 2019-05-21 18:44:36 -06:00 committed by GitHub
parent 51e68cbda0
commit 371efd9bb1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 276 additions and 19 deletions

View 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;

View file

@ -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'));
});
}

View file

@ -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'),

View file

@ -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';

View file

@ -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,
});
};
}

View file

@ -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"