From fc967e1ac93e80de07adec91b6a9f52af82aa979 Mon Sep 17 00:00:00 2001 From: Franco Charriol Date: Fri, 19 Mar 2021 16:12:17 -0300 Subject: [PATCH 01/14] Applied agents filter in agent stats and visualizations --- CHANGELOG.md | 1 + kibana.json | 2 +- public/app.js | 1 - public/assets/meta.json | 4 -- public/controllers/agent/agents-preview.js | 12 +++-- public/kibana-integrations/kibana-vis.js | 52 +++++++++++++++++++++- public/react-services/wz-agents.ts | 17 +++++++ public/react-services/wz-authentication.ts | 8 +++- public/redux/actions/appStateActions.js | 12 +++++ public/redux/reducers/appStateReducers.js | 8 ++++ public/utils/check-plugin-version.ts | 4 +- scripts/generate-build-version.js | 17 ------- server/controllers/wazuh-elastic.ts | 17 +++++-- server/routes/wazuh-elastic.ts | 3 ++ 14 files changed, 121 insertions(+), 37 deletions(-) delete mode 100644 public/assets/meta.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 17213d0fab..802c978bb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ All notable changes to the Wazuh app project will be documented in this file. - Improved validation and prevention for caching bundles in client side [#3063](https://github.com/wazuh/wazuh-kibana-app/pull/3063) - Fix some errors in Events table, action buttons style and urls disappeared [#3086](https://github.com/wazuh/wazuh-kibana-app/pull/3086) - Rollback of invalid rule configuration file [#3084](https://github.com/wazuh/wazuh-kibana-app/pull/3084) +- Filter only authorized agents in Agents stats and Visualizations [#]() ### Changed diff --git a/kibana.json b/kibana.json index 746c46b454..c813b45d0c 100644 --- a/kibana.json +++ b/kibana.json @@ -1,6 +1,6 @@ { "id": "wazuh", - "version": "4.1.2-4103", + "version": "4.1.2-4104", "kibanaVersion": "kibana", "configPath": [ "wazuh" diff --git a/public/app.js b/public/app.js index 5de55e581d..51a2c7eb26 100644 --- a/public/app.js +++ b/public/app.js @@ -105,7 +105,6 @@ app.run(function ($rootElement) { // Bind deleteExistentToken on Log out component. $(document).on('ready', function () { $('.euiHeaderSectionItem__button').on('mouseleave', function () { - console.log('onmouseleave') // opendistro $('span:contains(Log out)').on('click', function () { WzAuthentication.deleteExistentToken(); diff --git a/public/assets/meta.json b/public/assets/meta.json deleted file mode 100644 index e785e8483e..0000000000 --- a/public/assets/meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "version": "4.1.2", - "revision": "4201-2" -} \ No newline at end of file diff --git a/public/controllers/agent/agents-preview.js b/public/controllers/agent/agents-preview.js index b3dc9333a0..07da1445fb 100644 --- a/public/controllers/agent/agents-preview.js +++ b/public/controllers/agent/agents-preview.js @@ -20,7 +20,9 @@ import { WzRequest } from '../../react-services/wz-request'; import { ShareAgent } from '../../factories/share-agent'; import { formatUIDate } from '../../react-services/time-service'; import { ErrorHandler } from '../../react-services/error-handler'; -import { getDataPlugin, getUiSettings } from '../../kibana-services'; +import { getDataPlugin, getToasts } from '../../kibana-services'; +import { connect } from 'react-redux'; +import store from '../../redux/store'; export class AgentsPreviewController { /** @@ -193,12 +195,12 @@ export class AgentsPreviewController { try { const data = await this.genericReq.request( 'GET', - `/elastic/top/${this.firstUrlParam}/${this.secondUrlParam}/agent.name/${this.pattern}` + `/elastic/top/${this.firstUrlParam}/${this.secondUrlParam}/agent.name/${this.pattern}?agentsList=${store.getState().appStateReducers.allowedAgents.toString()}` ); this.mostActiveAgent.name = data.data.data; const info = await this.genericReq.request( 'GET', - `/elastic/top/${this.firstUrlParam}/${this.secondUrlParam}/agent.id/${this.pattern}` + `/elastic/top/${this.firstUrlParam}/${this.secondUrlParam}/agent.id/${this.pattern}?agentsList=${store.getState().appStateReducers.allowedAgents.toString()}` ); if (info.data.data === '' && this.mostActiveAgent.name !== '') { this.mostActiveAgent.id = '000'; @@ -206,7 +208,9 @@ export class AgentsPreviewController { this.mostActiveAgent.id = info.data.data; } return this.mostActiveAgent; - } catch (error) { } + } catch (error) { + getToasts().addDanger('An error occurred while trying to get the most active agent',{ toastMessage: error.message || error }); + } } /** diff --git a/public/kibana-integrations/kibana-vis.js b/public/kibana-integrations/kibana-vis.js index 10470b3dc4..52f412e29e 100644 --- a/public/kibana-integrations/kibana-vis.js +++ b/public/kibana-integrations/kibana-vis.js @@ -18,6 +18,7 @@ import { connect } from "react-redux"; import { LoadedVisualizations } from "../factories/loaded-visualizations"; import { RawVisualizations } from "../factories/raw-visualizations"; import { VisHandlers } from "../factories/vis-handlers"; +import { WzRequest } from '../react-services/wz-request'; import { TabVisualizations } from "../factories/tab-visualizations"; import store from "../redux/store"; import { updateMetric } from "../redux/actions/visualizationsActions"; @@ -197,6 +198,44 @@ class KibanaVis extends Component { } }; + getUserAgentsFilters = (pattern = "") => { + const agentsIds = this.props.allowedAgents; + + //check for empty agents array + if(agentsIds.length == 0){return {}} + + const usedPattern = pattern ? pattern : AppState.getCurrentPattern(); + const isMonitoringIndex = usedPattern.indexOf('monitoring') > -1; + const field = isMonitoringIndex ? 'id' : 'agent.id'; + return { + meta: { + index: usedPattern, + type: 'phrases', + key: field, + value: agentsIds.toString(), + params: agentsIds, + alias: null, + negate: false, + disabled: false + }, + query: { + bool: { + should: agentsIds.map(id => { + return { + match_phrase: { + [field]: id + } + }; + }), + minimum_should_match: 1 + } + }, + $state: { + store: 'appState' + } + } + } + myRender = async (raw) => { const timefilter = getDataPlugin().query.timefilter.timefilter; try { @@ -213,14 +252,22 @@ class KibanaVis extends Component { const filters = isAgentStatus ? [] : discoverList[1] || []; const query = !isAgentStatus ? discoverList[0] : {}; + const rawVis = raw ? raw.filter((item) => item && item.id === this.visID) : []; + let vizPattern; + try { + vizPattern = JSON.parse(rawVis[0].attributes.kibanaSavedObjectMeta.searchSourceJSON).index; + } catch (ex) { + console.warning(`kibana-vis exception: ${ex.message || ex}`); + } + const agentsFilters = this.getUserAgentsFilters(vizPattern); + Object.keys(agentsFilters).length !== 0 ? filters.push(agentsFilters) : null; + const visInput = { timeRange, filters, query }; - const rawVis = raw ? raw.filter((item) => item && item.id === this.visID) : []; - if (rawVis.length && discoverList.length) { // There are pending updates from the discover (which is the one who owns the true app state) @@ -417,6 +464,7 @@ class KibanaVis extends Component { const mapStateToProps = (state) => { return { state: state.visualizationsReducers, + allowedAgents: state.appStateReducers.allowedAgents }; }; diff --git a/public/react-services/wz-agents.ts b/public/react-services/wz-agents.ts index d1d3fa3939..c26940b527 100644 --- a/public/react-services/wz-agents.ts +++ b/public/react-services/wz-agents.ts @@ -9,8 +9,12 @@ * * Find more information about this on the LICENSE file. */ +import { ErrorToastOptions } from 'kibana/public'; import { WAZUH_AGENTS_OS_TYPE } from '../../common/constants'; +import { getToasts } from '../kibana-services'; import { UnsupportedComponents } from '../utils/components-os-support'; +import IApiResponse from './interfaces/api-response.interface'; +import { WzRequest } from './wz-request'; export function getAgentOSType(agent){ if(agent?.os?.uname?.toLowerCase().includes(WAZUH_AGENTS_OS_TYPE.LINUX)){ @@ -30,3 +34,16 @@ export function hasAgentSupportModule(agent, component){ const agentOSType = getAgentOSType(agent); return !(UnsupportedComponents[agentOSType].includes(component)); }; + + +export async function getAuthorizedAgents() { + const agentsList: IApiResponse<{id: string}> = await WzRequest.apiReq('GET', `/agents`, {}) + .catch(error => { + getToasts().addError(error, {title: `Error getting user authorized agents`} as ErrorToastOptions); + return Promise.reject(); + }); + + const allowedAgents = agentsList ? agentsList.data.data.affected_items.map((agent) => agent.id) : [] + + return allowedAgents; +} \ No newline at end of file diff --git a/public/react-services/wz-authentication.ts b/public/react-services/wz-authentication.ts index 56f572fa14..334db2abcc 100644 --- a/public/react-services/wz-authentication.ts +++ b/public/react-services/wz-authentication.ts @@ -14,10 +14,10 @@ import { WzRequest } from './wz-request'; import { AppState } from './app-state'; import jwtDecode from 'jwt-decode'; import store from '../redux/store'; -import { updateUserPermissions, updateUserRoles, updateWithUserLogged } from '../redux/actions/appStateActions'; +import { updateUserPermissions, updateUserRoles, updateWithUserLogged, updateAllowedAgents } from '../redux/actions/appStateActions'; import { WAZUH_ROLE_ADMINISTRATOR_ID, WAZUH_ROLE_ADMINISTRATOR_NAME } from '../../common/constants'; import { getToasts } from '../kibana-services'; - +import { getAuthorizedAgents } from '../react-services/wz-agents'; export class WzAuthentication{ private static async login(force=false){ @@ -49,6 +49,10 @@ export class WzAuthentication{ // Decode token and get expiration time const jwtPayload = jwtDecode(token); + //Get allowed agents for the current user + const allowedAgents = await getAuthorizedAgents(); + store.dispatch(updateAllowedAgents(allowedAgents)); + // Get user Policies const userPolicies = await WzAuthentication.getUserPolicies(); // Dispatch actions to set permissions and roles diff --git a/public/redux/actions/appStateActions.js b/public/redux/actions/appStateActions.js index b25b0a75c7..d20556863b 100644 --- a/public/redux/actions/appStateActions.js +++ b/public/redux/actions/appStateActions.js @@ -180,3 +180,15 @@ export const updateWithUserLogged = (withUserLogged) => { withUserLogged, }; }; + + +/** + * Updates allowedAgents in the appState store + * @param GET_ALLOWED_AGENTS + */ +export const updateAllowedAgents = data => { + return { + type: 'GET_ALLOWED_AGENTS', + allowedAgents: data + }; +}; \ No newline at end of file diff --git a/public/redux/reducers/appStateReducers.js b/public/redux/reducers/appStateReducers.js index cfc4e3e77f..0e3700bc4f 100644 --- a/public/redux/reducers/appStateReducers.js +++ b/public/redux/reducers/appStateReducers.js @@ -29,6 +29,7 @@ const initialState = { contextConfigServer: 'manager', }, withUserLogged: false, + allowedAgents: [], }; const appStateReducers = (state = initialState, action) => { @@ -138,6 +139,13 @@ const appStateReducers = (state = initialState, action) => { withUserLogged: action.withUserLogged, }; } + + if (action.type === 'GET_ALLOWED_AGENTS') { + return { + ...state, + allowedAgents: action.allowedAgents + }; + } return state; }; diff --git a/public/utils/check-plugin-version.ts b/public/utils/check-plugin-version.ts index fdcbddbb99..36184fb6b5 100644 --- a/public/utils/check-plugin-version.ts +++ b/public/utils/check-plugin-version.ts @@ -12,7 +12,7 @@ import { GenericRequest } from '../react-services/generic-request'; import { AxiosResponse } from 'axios'; import _ from 'lodash'; -import meta from '../assets/meta.json'; +import { version as wzVersion, revision as wzRevision} from '../../package.json'; import { getCookies } from '../kibana-services'; type TAppInfo = { @@ -39,7 +39,7 @@ export const checkPluginVersion = async () => { }; const checkClientAppVersion = (appInfo: TAppInfo) => { - if (appInfo['app-version'] !== meta.version || appInfo.revision !== meta.revision) { + if (appInfo['app-version'] !== wzVersion || appInfo.revision !== wzRevision) { clearBrowserInfo(appInfo); } else { const storeAppInfo = localStorage.getItem('appInfo'); diff --git a/scripts/generate-build-version.js b/scripts/generate-build-version.js index c162f81f15..a03dd975fe 100644 --- a/scripts/generate-build-version.js +++ b/scripts/generate-build-version.js @@ -2,23 +2,6 @@ const fs = require('fs'); const packageJson = require('../package.json'); const { version, revision } = packageJson; -const jsonData = { - version, - revision, -}; - -const jsonContent = JSON.stringify(jsonData, null, 2); - -//write meta.json with last version -fs.writeFile('./public/assets/meta.json', jsonContent, 'utf8', function (err) { - if (err) { - console.log('An error occurred while writing JSON Object to meta.json'); - return console.log(err); - } - - console.log('meta.json file has been saved with latest version number'); -}); - //replaces kibana.json with last fs.readFile('kibana.json', 'utf-8', function (err, data) { if (err) { diff --git a/server/controllers/wazuh-elastic.ts b/server/controllers/wazuh-elastic.ts index b172d93af3..322644eb74 100644 --- a/server/controllers/wazuh-elastic.ts +++ b/server/controllers/wazuh-elastic.ts @@ -185,7 +185,7 @@ export class WazuhElasticCtrl { * @param {Object} response * @returns {Array} fields or ErrorResponse */ - async getFieldTop(context: RequestHandlerContext, request: KibanaRequest<{mode: string, cluster: string, field: string, pattern: string}>, response: KibanaResponseFactory) { + async getFieldTop(context: RequestHandlerContext, request: KibanaRequest<{mode: string, cluster: string, field: string, pattern: string}, {agentsList: string}>, response: KibanaResponseFactory) { try { // Top field payload let payload = { @@ -198,7 +198,16 @@ export class WazuhElasticCtrl { 'agent.id': '000' } }, - filter: { range: { timestamp: {} } } + filter: [ + { + range: { timestamp: {} } + }, + { + terms: { + 'agent.id': request.query.agentsList.split(',') + } + } + ] } }, aggs: { @@ -215,8 +224,8 @@ export class WazuhElasticCtrl { // Set up time interval, default to Last 24h const timeGTE = 'now-1d'; const timeLT = 'now'; - payload.query.bool.filter.range['timestamp']['gte'] = timeGTE; - payload.query.bool.filter.range['timestamp']['lt'] = timeLT; + payload.query.bool.filter[0].range['timestamp']['gte'] = timeGTE; + payload.query.bool.filter[0].range['timestamp']['lt'] = timeLT; // Set up match for default cluster name payload.query.bool.must.push( diff --git a/server/routes/wazuh-elastic.ts b/server/routes/wazuh-elastic.ts index afbef2a800..d8db26a0d6 100644 --- a/server/routes/wazuh-elastic.ts +++ b/server/routes/wazuh-elastic.ts @@ -91,6 +91,9 @@ export function WazuhElasticRoutes(router: IRouter) { cluster: schema.string(), field: schema.string(), pattern: schema.string(), + }), + query: schema.object({ + agentsList: schema.string(), }) } }, From bc535063c991f694bd5a35bdf89df9ccab00056e Mon Sep 17 00:00:00 2001 From: Franco Charriol Date: Fri, 19 Mar 2021 16:19:46 -0300 Subject: [PATCH 02/14] Update changelog --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 802c978bb9..d8f009b3d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,10 @@ All notable changes to the Wazuh app project will be documented in this file. - Improved validation and prevention for caching bundles in client side [#3063](https://github.com/wazuh/wazuh-kibana-app/pull/3063) - Fix some errors in Events table, action buttons style and urls disappeared [#3086](https://github.com/wazuh/wazuh-kibana-app/pull/3086) - Rollback of invalid rule configuration file [#3084](https://github.com/wazuh/wazuh-kibana-app/pull/3084) -- Filter only authorized agents in Agents stats and Visualizations [#]() +- Filter only authorized agents in Agents stats and Visualizations [#3088](https://github.com/wazuh/wazuh-kibana-app/pull/3088) + +### Known Issues +- The Security Alerts table in Security Events shows alerts for all agents even when the user doesn't have permission to see all agents. ### Changed From 27f9bd4de9614c90fcee884e845edf4c22849e24 Mon Sep 17 00:00:00 2001 From: Franco Charriol Date: Fri, 19 Mar 2021 16:24:16 -0300 Subject: [PATCH 03/14] Fixed typo in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8f009b3d6..331dab7632 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ All notable changes to the Wazuh app project will be documented in this file. ### Fixed -- Fixed unexpected behaviour in Roles mapping [#3028](https://github.com/wazuh/wazuh-kibana-app/pull/3028) +- Fixed unexpected behavior in Roles mapping [#3028](https://github.com/wazuh/wazuh-kibana-app/pull/3028) - Improve toast message when selecting a default API [#3049](https://github.com/wazuh/wazuh-kibana-app/pull/3049) - Fix rule filter is no applied when you click on a rule id in other module.[#3057](https://github.com/wazuh/wazuh-kibana-app/pull/3057) - Fixed bug changing master node configuration [#3062](https://github.com/wazuh/wazuh-kibana-app/pull/3062) From 67054a0b73ec30da593825e57552d4aabf2cd595 Mon Sep 17 00:00:00 2001 From: Franco Charriol Date: Fri, 19 Mar 2021 17:28:29 -0300 Subject: [PATCH 04/14] Control to avoid infinite refresh on check version --- public/controllers/agent/agents-preview.js | 2 +- public/utils/check-plugin-version.ts | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/public/controllers/agent/agents-preview.js b/public/controllers/agent/agents-preview.js index 07da1445fb..7983a31c50 100644 --- a/public/controllers/agent/agents-preview.js +++ b/public/controllers/agent/agents-preview.js @@ -209,7 +209,7 @@ export class AgentsPreviewController { } return this.mostActiveAgent; } catch (error) { - getToasts().addDanger('An error occurred while trying to get the most active agent',{ toastMessage: error.message || error }); + getToasts().addDanger({title: 'An error occurred while trying to get the most active agent', text: error.message || error }); } } diff --git a/public/utils/check-plugin-version.ts b/public/utils/check-plugin-version.ts index 36184fb6b5..e62401a086 100644 --- a/public/utils/check-plugin-version.ts +++ b/public/utils/check-plugin-version.ts @@ -13,7 +13,7 @@ import { GenericRequest } from '../react-services/generic-request'; import { AxiosResponse } from 'axios'; import _ from 'lodash'; import { version as wzVersion, revision as wzRevision} from '../../package.json'; -import { getCookies } from '../kibana-services'; +import { getCookies, getToasts } from '../kibana-services'; type TAppInfo = { revision: string; @@ -40,8 +40,13 @@ export const checkPluginVersion = async () => { const checkClientAppVersion = (appInfo: TAppInfo) => { if (appInfo['app-version'] !== wzVersion || appInfo.revision !== wzRevision) { - clearBrowserInfo(appInfo); + if( window.history.state != 'refreshed') { + clearBrowserInfo(appInfo); + } else { + getToasts().addDanger({title: 'Conflict with the Wazuh app version', text: 'The version of the Wazuh app in your browser does not correspond with the app version installed in Kibana.\nPlease clear your browser cache.\nIf the error persist please restart Kibana too.'}) + } } else { + if( window.history.state != 'refreshed') window.history.replaceState('', 'wazuh'); const storeAppInfo = localStorage.getItem('appInfo'); !storeAppInfo && updateAppInfo(appInfo); } @@ -63,6 +68,8 @@ function clearBrowserInfo(appInfo: TAppInfo) { //update localStorage updateAppInfo(appInfo); + //replace status to avoid infinite refresh + window.history.replaceState('refreshed', 'wazuh'); // delete browser cache and hard reload window.location.reload(true); } From 209d75e17b2356690949b07a6c2214b163135a40 Mon Sep 17 00:00:00 2001 From: CPAlejandro Date: Tue, 6 Apr 2021 08:14:14 +0200 Subject: [PATCH 05/14] New hoc and hook --- .../common/hocs/withAllowedAgents.tsx | 19 +++++++ .../common/hooks/useAllowedAgents.ts | 21 ++++++++ .../filter-authorization-agents.ts | 52 +++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 public/components/common/hocs/withAllowedAgents.tsx create mode 100644 public/components/common/hooks/useAllowedAgents.ts create mode 100644 public/react-services/filter-authorization-agents.ts diff --git a/public/components/common/hocs/withAllowedAgents.tsx b/public/components/common/hocs/withAllowedAgents.tsx new file mode 100644 index 0000000000..b8355e9f14 --- /dev/null +++ b/public/components/common/hocs/withAllowedAgents.tsx @@ -0,0 +1,19 @@ +/* + * Wazuh app - React HOCs to manage allowed agents + * Copyright (C) 2015-2021 Wazuh, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Find more information about this on the LICENSE file. + */ + +import React from "react"; +import { useAllowedAgents } from '../hooks'; + +export const withAllowedAgents = (WrappedComponent) => (props) => { + const {allowedAgents, filterAllowedAgents} = useAllowedAgents(); + return +} diff --git a/public/components/common/hooks/useAllowedAgents.ts b/public/components/common/hooks/useAllowedAgents.ts new file mode 100644 index 0000000000..a7aa1fa5f6 --- /dev/null +++ b/public/components/common/hooks/useAllowedAgents.ts @@ -0,0 +1,21 @@ +/* + * Wazuh app - React hooks to manage allowed users + * Copyright (C) 2015-2021 Wazuh, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Find more information about this on the LICENSE file. + */ + +import { useSelector } from 'react-redux'; +import { getFilterWithAuthorizedAgents } from '../../../react-services/filter-authorization-agents'; + +// It returns user allowed agents +export const useAllowedAgents = () => { + const allowedAgents = useSelector(state => state.appStateReducers.allowedAgents); + const filterAllowedAgents = getFilterWithAuthorizedAgents(allowedAgents); + return {allowedAgents, filterAllowedAgents}; +} diff --git a/public/react-services/filter-authorization-agents.ts b/public/react-services/filter-authorization-agents.ts new file mode 100644 index 0000000000..4ddb403502 --- /dev/null +++ b/public/react-services/filter-authorization-agents.ts @@ -0,0 +1,52 @@ +/* +* Wazuh app - React component for get authorized agents. +* Copyright (C) 2015-2021 Wazuh, Inc. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* Find more information about this on the LICENSE file. +*/ +import { AppState } from '../react-services/app-state'; + + + +export function getFilterWithAuthorizedAgents(agentsIds) { + if(!agentsIds) + return ; + //check for empty agents array + if(agentsIds.length == 0){return } + + const usedPattern = AppState.getCurrentPattern(); + const isMonitoringIndex = usedPattern.indexOf('monitoring') > -1; + const field = isMonitoringIndex ? 'id' : 'agent.id'; + return { + meta: { + index: usedPattern, + type: 'phrases', + key: field, + value: agentsIds.toString(), + params: agentsIds, + alias: null, + negate: false, + disabled: false + }, + query: { + bool: { + should: agentsIds.map(id => { + return { + match_phrase: { + [field]: id + } + }; + }), + minimum_should_match: 1 + } + }, + $state: { + store: 'appState' + } + } + } From 28f1ddd1f3f7f848c711983dbdc5e2a3bffed296 Mon Sep 17 00:00:00 2001 From: CPAlejandro Date: Tue, 6 Apr 2021 16:10:48 +0200 Subject: [PATCH 06/14] Fixed filter in Events Table --- public/components/common/hocs/index.ts | 4 +- public/components/common/hooks/index.ts | 4 +- .../common/hooks/useAllowedAgents.ts | 2 + .../common/modules/discover/discover.tsx | 1232 ++++++++--------- .../components/overview/metrics/metrics.tsx | 10 +- .../visualize/components/security-alerts.tsx | 62 +- public/components/visualize/wz-visualize.js | 6 +- .../discover/build_services.ts | 1 - public/kibana-integrations/kibana-discover.js | 3 + 9 files changed, 671 insertions(+), 653 deletions(-) diff --git a/public/components/common/hocs/index.ts b/public/components/common/hocs/index.ts index 13848fcad4..dbf3e3d8f5 100644 --- a/public/components/common/hocs/index.ts +++ b/public/components/common/hocs/index.ts @@ -29,4 +29,6 @@ export { withButtonOpenOnClick } from './withButtonOpenOnClick'; export { withAgentSupportModule } from './withAgentSupportModule'; -export { withUserLogged } from './withUserLogged'; \ No newline at end of file +export { withUserLogged } from './withUserLogged'; + +export { withAllowedAgents } from './withAllowedAgents'; \ No newline at end of file diff --git a/public/components/common/hooks/index.ts b/public/components/common/hooks/index.ts index 6efd53b4e9..877100cb0b 100644 --- a/public/components/common/hooks/index.ts +++ b/public/components/common/hooks/index.ts @@ -26,4 +26,6 @@ export { useUserPermissions, useUserPermissionsRequirements, useUserPermissionsP export { useUserRoles, useUserRolesRequirements, useUserRolesPrivate } from './useUserRoles'; -export { useRefreshAngularDiscover } from './useResfreshAngularDiscover' \ No newline at end of file +export { useRefreshAngularDiscover } from './useResfreshAngularDiscover' + +export { useAllowedAgents } from './useAllowedAgents' \ No newline at end of file diff --git a/public/components/common/hooks/useAllowedAgents.ts b/public/components/common/hooks/useAllowedAgents.ts index a7aa1fa5f6..c6cdc3abb0 100644 --- a/public/components/common/hooks/useAllowedAgents.ts +++ b/public/components/common/hooks/useAllowedAgents.ts @@ -10,8 +10,10 @@ * Find more information about this on the LICENSE file. */ +import React from "react"; import { useSelector } from 'react-redux'; import { getFilterWithAuthorizedAgents } from '../../../react-services/filter-authorization-agents'; +import { withReduxProvider } from '../hocs'; // It returns user allowed agents export const useAllowedAgents = () => { diff --git a/public/components/common/modules/discover/discover.tsx b/public/components/common/modules/discover/discover.tsx index 9e670fb48c..c223c2e23c 100644 --- a/public/components/common/modules/discover/discover.tsx +++ b/public/components/common/modules/discover/discover.tsx @@ -1,15 +1,15 @@ -/* - * Wazuh app - Integrity monitoring table component - * Copyright (C) 2015-2021 Wazuh, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * Find more information about this on the LICENSE file. - */ - + /* +* Wazuh app - Integrity monitoring table component +* Copyright (C) 2015-2021 Wazuh, Inc. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* Find more information about this on the LICENSE file. +*/ + import React, { Component, } from 'react'; import './discover.scss'; import { FilterManager, Filter } from '../../../../../../../src/plugins/data/public/' @@ -26,620 +26,620 @@ import { withReduxProvider } from '../../../common/hocs'; import { connect } from 'react-redux'; import { compose } from 'redux'; import _ from 'lodash'; - + import { - EuiBasicTable, - EuiLoadingContent, - EuiTableSortingType, - EuiFlexItem, - EuiFlexGroup, - Direction, - EuiOverlayMask, - EuiSpacer, - EuiCallOut, - EuiIcon, - EuiButtonIcon, - EuiButtonEmpty, - EuiToolTip + EuiBasicTable, + EuiLoadingContent, + EuiTableSortingType, + EuiFlexItem, + EuiFlexGroup, + Direction, + EuiOverlayMask, + EuiSpacer, + EuiCallOut, + EuiIcon, + EuiButtonIcon, + EuiButtonEmpty, + EuiToolTip } from '@elastic/eui'; import { - IIndexPattern, - TimeRange, - Query, - buildPhraseFilter, - getEsQueryConfig, - buildEsQuery, - IFieldType + IIndexPattern, + TimeRange, + Query, + buildPhraseFilter, + getEsQueryConfig, + buildEsQuery, + IFieldType } from '../../../../../../../src/plugins/data/common'; import { getDataPlugin, getToasts, getUiSettings } from '../../../../kibana-services'; - + const mapStateToProps = state => ({ - currentAgentData: state.appStateReducers.currentAgentData + currentAgentData: state.appStateReducers.currentAgentData }); - + export const Discover = compose( - withReduxProvider, - connect(mapStateToProps) + withReduxProvider, + connect(mapStateToProps) )(class Discover extends Component { - _isMount!: boolean; - timefilter: { - getTime(): TimeRange - setTime(time: TimeRange): void - _history: { history: { items: { from: string, to: string }[] } } - }; - - KibanaServices: { [key: string]: any }; - state: { - sort: object - selectedTechnique: string, - showMitreFlyout: boolean, - alerts: { _source: {}, _id: string }[] - total: number - pageIndex: number - pageSize: number - sortField: string - sortDirection: Direction - isLoading: boolean - requestFilters: object - requestSize: number - requestOffset: number - query: { language: "kuery" | "lucene", query: string } - itemIdToExpandedRowMap: any - dateRange: TimeRange - elasticQuery: object - columns: string[] - hover: string - }; - indexPattern!: IIndexPattern - props!: { - implicitFilters: object[], - initialFilters: object[], - query?: { language: "kuery" | "lucene", query: string } - type?: any, - updateTotalHits: Function, - includeFilters?: string, - initialColumns: string[], - shareFilterManager: FilterManager, - refreshAngularDiscover?: number - } - constructor(props) { - super(props); - this.KibanaServices = getDataPlugin(); - this.timefilter = this.KibanaServices.query.timefilter.timefilter; - this.state = { - sort: {}, - selectedTechnique: "", - showMitreFlyout: false, - alerts: [], - total: 0, - pageIndex: 0, - pageSize: 10, - sortField: 'timestamp', - sortDirection: 'desc', - isLoading: false, - requestFilters: {}, - requestSize: 500, - requestOffset: 0, - itemIdToExpandedRowMap: {}, - dateRange: this.timefilter.getTime(), - query: props.query || { language: "kuery", query: "" }, - elasticQuery: {}, - columns: [], - hover: "" - } - - this.wazuhConfig = new WazuhConfig(); - this.nameEquivalences = { - "agent.id": "Agent", - "agent.name": "Agent name", - "syscheck.event": "Action", - "rule.id": "Rule ID", - "rule.description": "Description", - "rule.level": "Level", - "rule.mitre.id": "Technique(s)", - "rule.mitre.tactic": "Tactic(s)", - "rule.pci_dss": "PCI DSS", - "rule.gdpr": "GDPR", - "rule.nist_800_53": "NIST 800-53", - "rule.tsc": "TSC", - "rule.hipaa": "HIPAA", - } - - this.hideCreateCustomLabel.bind(this); - this.onQuerySubmit.bind(this); - this.onFiltersUpdated.bind(this); - this.hideCreateCustomLabel() - } - - showToast = (color, title, time) => { - getToasts().add({ - color: color, - title: title, - toastLifeTimeMs: time, - }); - }; - - async componentDidMount() { - this._isMount = true; - try { - this.setState({columns: this.getColumns()}) //initial columns - await this.getIndexPattern(); - await this.getAlerts(); - } catch (err) { - console.log(err); - } - } - - componentWillUnmount() { - this._isMount = false; - } - - async componentDidUpdate(prevProps, prevState) { - if (!this._isMount) { return; } - if((!prevProps.currentAgentData.id && this.props.currentAgentData.id) || (prevProps.currentAgentData.id && !this.props.currentAgentData.id) || prevProps.currentAgentData.id !== this.props.currentAgentData.id){ - this.setState({ columns: this.getColumns() }); // Updates the columns to be rendered if you change the selected agent to none or vice versa - return; - } - if(!_.isEqual(this.props.query,prevProps.query)){ - this.setState({ query: {...this.props.query}}); - return; - }; - if((this.props.currentAgentData.id !== prevProps.currentAgentData.id) - || (!_.isEqual(this.state.query, prevState.query)) - || (!_.isEqual(this.state.dateRange, prevState.dateRange)) - || (this.props.refreshAngularDiscover !== prevProps.refreshAngularDiscover) - ){ - this.setState({ pageIndex: 0 , tsUpdated: Date.now()}); - return; - }; - if(['pageIndex', 'pageSize', 'sortField', 'sortDirection'].some(field => this.state[field] !== prevState[field]) || (this.state.tsUpdated !== prevState.tsUpdated)){ - try { - await this.getAlerts(); - } catch (err) { - console.log(err); - }; - }; - } - - getColumns () { - if(this.props.currentAgentData.id){ - return this.props.initialColumns.filter(column => !['agent.id', 'agent.name'].includes(column)); - }else{ - const columns = [...this.props.initialColumns]; - columns.splice(2, 0, 'agent.id'); - columns.splice(3, 0, 'agent.name'); - return columns; - } - } - - async getIndexPattern () { - this.indexPattern = {...await this.KibanaServices.indexPatterns.get(AppState.getCurrentPattern())}; - const fields:IFieldType[] = []; - Object.keys(this.indexPattern.fields).forEach(item => { - if (isNaN(item)) { - fields.push(this.indexPattern.fields[item]); - } else if (this.props.includeFilters && this.indexPattern.fields[item].name.includes(this.props.includeFilters)) { - fields.unshift(this.indexPattern.fields[item]); - } else { - fields.push(this.indexPattern.fields[item]); - } - }) - this.indexPattern.fields = fields; - } - - hideCreateCustomLabel = () => { - try { - const button = document.querySelector(".wz-discover #addFilterPopover > div > button > span > span"); - if (!button) return setTimeout(this.hideCreateCustomLabel, 100); - const findAndHide = () => { - const switcher = document.querySelector("#filterEditorCustomLabelSwitch") - if (!switcher) return setTimeout(findAndHide, 100); - switcher.parentElement.style.display = "none" - } - button.onclick = findAndHide; - } catch (error) { } - } - - filtersAsArray(filters) { - const keys = Object.keys(filters); - const result: {}[] = []; - for (var i = 0; i < keys.length; i++) { - const item = {}; - item[keys[i]] = filters[keys[i]]; - result.push(item); - } - return result; - } - - toggleDetails = item => { - const itemIdToExpandedRowMap = { ...this.state.itemIdToExpandedRowMap }; - if (itemIdToExpandedRowMap[item._id]) { - delete itemIdToExpandedRowMap[item._id]; - this.setState({ itemIdToExpandedRowMap }); - } else { - const newItemIdToExpandedRowMap = {}; - newItemIdToExpandedRowMap[item._id] = ( - (
this.addFilter(filter)} addFilterOut={(filter) => this.addFilterOut(filter)} toggleColumn={(id) => this.addColumn(id)} />
) - ); - this.setState({ itemIdToExpandedRowMap: newItemIdToExpandedRowMap }); - } - }; - - buildFilter() { - const dateParse = ds => /\d+-\d+-\d+T\d+:\d+:\d+.\d+Z/.test(ds) ? DateMatch.parse(ds).toDate().getTime() : ds; - const { query } = this.state; - const { hideManagerAlerts } = this.wazuhConfig.getConfig(); - const extraFilters = []; - if (hideManagerAlerts) extraFilters.push({ - meta: { - alias: null, - disabled: false, - key: 'agent.id', - negate: true, - params: { query: '000' }, - type: 'phrase', - index: this.indexPattern.title - }, - query: { match_phrase: { 'agent.id': '000' } }, - $state: { store: 'appState' } - }); - - const filters = this.props.shareFilterManager ? this.props.shareFilterManager.filters : []; - const previousFilters = this.KibanaServices && this.KibanaServices.query.filterManager.filters ? this.KibanaServices.query.filterManager.filters : []; + _isMount!: boolean; + timefilter: { + getTime(): TimeRange + setTime(time: TimeRange): void + _history: { history: { items: { from: string, to: string }[] } } + }; + + KibanaServices: { [key: string]: any }; + state: { + sort: object + selectedTechnique: string, + showMitreFlyout: boolean, + alerts: { _source: {}, _id: string }[] + total: number + pageIndex: number + pageSize: number + sortField: string + sortDirection: Direction + isLoading: boolean + requestFilters: object + requestSize: number + requestOffset: number + query: { language: "kuery" | "lucene", query: string } + itemIdToExpandedRowMap: any + dateRange: TimeRange + elasticQuery: object + columns: string[] + hover: string + }; + indexPattern!: IIndexPattern + props!: { + implicitFilters: object[], + initialFilters: object[], + query?: { language: "kuery" | "lucene", query: string } + type?: any, + updateTotalHits: Function, + includeFilters?: string, + initialColumns: string[], + shareFilterManager: FilterManager, + shareFilterManagerWithUserAuthorized: FilterManager, + refreshAngularDiscover?: number + } + constructor(props) { + super(props); + this.KibanaServices = getDataPlugin(); + this.timefilter = this.KibanaServices.query.timefilter.timefilter; + this.state = { + sort: {}, + selectedTechnique: "", + showMitreFlyout: false, + alerts: [], + total: 0, + pageIndex: 0, + pageSize: 10, + sortField: 'timestamp', + sortDirection: 'desc', + isLoading: false, + requestFilters: {}, + requestSize: 500, + requestOffset: 0, + itemIdToExpandedRowMap: {}, + dateRange: this.timefilter.getTime(), + query: props.query || { language: "kuery", query: "" }, + elasticQuery: {}, + columns: [], + hover: "" + } + + this.wazuhConfig = new WazuhConfig(); + this.nameEquivalences = { + "agent.id": "Agent", + "agent.name": "Agent name", + "syscheck.event": "Action", + "rule.id": "Rule ID", + "rule.description": "Description", + "rule.level": "Level", + "rule.mitre.id": "Technique(s)", + "rule.mitre.tactic": "Tactic(s)", + "rule.pci_dss": "PCI DSS", + "rule.gdpr": "GDPR", + "rule.nist_800_53": "NIST 800-53", + "rule.tsc": "TSC", + "rule.hipaa": "HIPAA", + } + + this.hideCreateCustomLabel.bind(this); + this.onQuerySubmit.bind(this); + this.onFiltersUpdated.bind(this); + this.hideCreateCustomLabel() + } + + showToast = (color, title, time) => { + getToasts().add({ + color: color, + title: title, + toastLifeTimeMs: time, + }); + }; + + async componentDidMount() { + this._isMount = true; + try { + this.setState({columns: this.getColumns()}) //initial columns + await this.getIndexPattern(); + await this.getAlerts(); + } catch (err) { + console.log(err); + } + } + + componentWillUnmount() { + this._isMount = false; + } + + async componentDidUpdate(prevProps, prevState) { + if (!this._isMount) { return; } + if((!prevProps.currentAgentData.id && this.props.currentAgentData.id) || (prevProps.currentAgentData.id && !this.props.currentAgentData.id) || prevProps.currentAgentData.id !== this.props.currentAgentData.id){ + this.setState({ columns: this.getColumns() }); // Updates the columns to be rendered if you change the selected agent to none or vice versa + return; + } + if(!_.isEqual(this.props.query,prevProps.query)){ + this.setState({ query: {...this.props.query}}); + return; + }; + if((this.props.currentAgentData.id !== prevProps.currentAgentData.id) + || (!_.isEqual(this.state.query, prevState.query)) + || (!_.isEqual(this.state.dateRange, prevState.dateRange)) + || (this.props.refreshAngularDiscover !== prevProps.refreshAngularDiscover) + ){ + this.setState({ pageIndex: 0 , tsUpdated: Date.now()}); + return; + }; + if(['pageIndex', 'pageSize', 'sortField', 'sortDirection'].some(field => this.state[field] !== prevState[field]) || (this.state.tsUpdated !== prevState.tsUpdated)){ + try { + await this.getAlerts(); + } catch (err) { + console.log(err); + }; + }; + } + + getColumns () { + if(this.props.currentAgentData.id){ + return this.props.initialColumns.filter(column => !['agent.id', 'agent.name'].includes(column)); + }else{ + const columns = [...this.props.initialColumns]; + columns.splice(2, 0, 'agent.id'); + columns.splice(3, 0, 'agent.name'); + return columns; + } + } + + async getIndexPattern () { + this.indexPattern = {...await this.KibanaServices.indexPatterns.get(AppState.getCurrentPattern())}; + const fields:IFieldType[] = []; + Object.keys(this.indexPattern.fields).forEach(item => { + if (isNaN(item)) { + fields.push(this.indexPattern.fields[item]); + } else if (this.props.includeFilters && this.indexPattern.fields[item].name.includes(this.props.includeFilters)) { + fields.unshift(this.indexPattern.fields[item]); + } else { + fields.push(this.indexPattern.fields[item]); + } + }) + this.indexPattern.fields = fields; + } + + hideCreateCustomLabel = () => { + try { + const button = document.querySelector(".wz-discover #addFilterPopover > div > button > span > span"); + if (!button) return setTimeout(this.hideCreateCustomLabel, 100); + const findAndHide = () => { + const switcher = document.querySelector("#filterEditorCustomLabelSwitch") + if (!switcher) return setTimeout(findAndHide, 100); + switcher.parentElement.style.display = "none" + } + button.onclick = findAndHide; + } catch (error) { } + } + + filtersAsArray(filters) { + const keys = Object.keys(filters); + const result: {}[] = []; + for (var i = 0; i < keys.length; i++) { + const item = {}; + item[keys[i]] = filters[keys[i]]; + result.push(item); + } + return result; + } + + toggleDetails = item => { + const itemIdToExpandedRowMap = { ...this.state.itemIdToExpandedRowMap }; + if (itemIdToExpandedRowMap[item._id]) { + delete itemIdToExpandedRowMap[item._id]; + this.setState({ itemIdToExpandedRowMap }); + } else { + const newItemIdToExpandedRowMap = {}; + newItemIdToExpandedRowMap[item._id] = ( + (
this.addFilter(filter)} addFilterOut={(filter) => this.addFilterOut(filter)} toggleColumn={(id) => this.addColumn(id)} />
) + ); + this.setState({ itemIdToExpandedRowMap: newItemIdToExpandedRowMap }); + } + }; + + buildFilter() { + const dateParse = ds => /\d+-\d+-\d+T\d+:\d+:\d+.\d+Z/.test(ds) ? DateMatch.parse(ds).toDate().getTime() : ds; + const { query } = this.state; + const { hideManagerAlerts } = this.wazuhConfig.getConfig(); + const extraFilters = []; + if (hideManagerAlerts) extraFilters.push({ + meta: { + alias: null, + disabled: false, + key: 'agent.id', + negate: true, + params: { query: '000' }, + type: 'phrase', + index: this.indexPattern.title + }, + query: { match_phrase: { 'agent.id': '000' } }, + $state: { store: 'appState' } + }); + + const filters = this.props.shareFilterManager ? this.props.shareFilterManager.filters : []; + const previousFilters = this.KibanaServices && this.KibanaServices.query.filterManager.filters ? this.KibanaServices.query.filterManager.filters : []; + const CustomFilterWith = this.props.shareFilterManagerWithUserAuthorized; + const elasticQuery = + buildEsQuery( + undefined, + query, + [...previousFilters, ...filters, ...extraFilters, ...CustomFilterWith], + getEsQueryConfig(getUiSettings()) + ); + + const { sortField, sortDirection } = this.state; + + const range = { + range: { + timestamp: { + gte: dateParse(this.timefilter.getTime().from), + lte: dateParse(this.timefilter.getTime().to), + format: 'epoch_millis' + } + } + } + elasticQuery.bool.must.push(range); + + if(this.props.implicitFilters){ + this.props.implicitFilters.map(impicitFilter => elasticQuery.bool.must.push({ + match: impicitFilter + })); + }; + if(this.props.currentAgentData.id){ + elasticQuery.bool.must.push({ + match: {"agent.id": this.props.currentAgentData.id} + }); + }; + return { + query: elasticQuery, + size: this.state.pageSize, + from: this.state.pageIndex*this.state.pageSize, + ...(sortField ? {sort: { [sortField]: { "order": sortDirection } }}: {}) + }; + } + + async getAlerts() { + if (!this.indexPattern || this.state.isLoading) return; + //compare filters so we only make a request into Elasticsearch if needed + const newFilters = this.buildFilter(); + try { + this.setState({ isLoading: true}); + const alerts = await GenericRequest.request( + 'POST', + `/elastic/alerts`, + { + index: this.indexPattern.title, + body: newFilters + } + ); + if (this._isMount) { + this.setState({ alerts: alerts.data.hits.hits, total: alerts.data.hits.total.value, isLoading: false, requestFilters: newFilters}); + this.props.updateTotalHits(alerts.data.hits.total.value); + } + } catch (err) { + if (this._isMount) { + this.setState({ alerts: [], total: 0, isLoading: false, requestFilters: newFilters}); + this.props.updateTotalHits(0); + } + } + } + + removeColumn(id) { + if (this.state.columns.length < 2) { + this.showToast('warning', "At least one column must be selected", 3000); + return; + } + const columns = this.state.columns; + columns.splice(columns.findIndex(v => v === id), 1); + this.setState(columns) + } + + addColumn(id) { + if (this.state.columns.length > 11) { + this.showToast('warning', 'The maximum number of columns is 10', 3000); + return; + } + if (this.state.columns.find(element => element === id)) { + this.removeColumn(id); + return; + } + const columns = this.state.columns; + columns.push(id); + this.setState(columns) + } + + + columns = () => { + var columnsList = [...this.state.columns]; + const columns = columnsList.map((item) => { + if (item === "icon") { + return { + width: "25px", + isExpander: true, + render: item => { + return ( + + ) + }, + } + } + if (item === "timestamp") { + return { + field: 'timestamp', + name: 'Time', + width: '10%', + sortable: true, + render: time => { + return {formatUIDate(time)} + }, + } + } + let width = false; + let link = false; + const arrayCompilance = ["rule.pci_dss", "rule.gdpr", "rule.nist_800_53", "rule.tsc", "rule.hipaa"]; - const elasticQuery = - buildEsQuery( - undefined, - query, - [...previousFilters, ...filters, ...extraFilters], - getEsQueryConfig(getUiSettings()) - ); - - const { sortField, sortDirection } = this.state; - - const range = { - range: { - timestamp: { - gte: dateParse(this.timefilter.getTime().from), - lte: dateParse(this.timefilter.getTime().to), - format: 'epoch_millis' - } - } - } - elasticQuery.bool.must.push(range); - - if(this.props.implicitFilters){ - this.props.implicitFilters.map(impicitFilter => elasticQuery.bool.must.push({ - match: impicitFilter - })); - }; - if(this.props.currentAgentData.id){ - elasticQuery.bool.must.push({ - match: {"agent.id": this.props.currentAgentData.id} - }); - }; - return { - query: elasticQuery, - size: this.state.pageSize, - from: this.state.pageIndex*this.state.pageSize, - ...(sortField ? {sort: { [sortField]: { "order": sortDirection } }}: {}) - }; - } - - async getAlerts() { - if (!this.indexPattern || this.state.isLoading) return; - //compare filters so we only make a request into Elasticsearch if needed - const newFilters = this.buildFilter(); - try { - this.setState({ isLoading: true}); - const alerts = await GenericRequest.request( - 'POST', - `/elastic/alerts`, - { - index: this.indexPattern.title, - body: newFilters - } - ); - if (this._isMount) { - this.setState({ alerts: alerts.data.hits.hits, total: alerts.data.hits.total.value, isLoading: false, requestFilters: newFilters}); - this.props.updateTotalHits(alerts.data.hits.total.value); - } - } catch (err) { - if (this._isMount) { - this.setState({ alerts: [], total: 0, isLoading: false, requestFilters: newFilters}); - this.props.updateTotalHits(0); - } - } - } - - removeColumn(id) { - if (this.state.columns.length < 2) { - this.showToast('warning', "At least one column must be selected", 3000); - return; - } - const columns = this.state.columns; - columns.splice(columns.findIndex(v => v === id), 1); - this.setState(columns) - } - - addColumn(id) { - if (this.state.columns.length > 11) { - this.showToast('warning', 'The maximum number of columns is 10', 3000); - return; - } - if (this.state.columns.find(element => element === id)) { - this.removeColumn(id); - return; - } - const columns = this.state.columns; - columns.push(id); - this.setState(columns) - } - - - columns = () => { - var columnsList = [...this.state.columns]; - const columns = columnsList.map((item) => { - if (item === "icon") { - return { - width: "25px", - isExpander: true, - render: item => { - return ( - - ) - }, - } - } - if (item === "timestamp") { - return { - field: 'timestamp', - name: 'Time', - width: '10%', - sortable: true, - render: time => { - return {formatUIDate(time)} - }, - } - } - let width = false; - let link = false; - const arrayCompilance = ["rule.pci_dss", "rule.gdpr", "rule.nist_800_53", "rule.tsc", "rule.hipaa"]; - - if(item === 'agent.id') { - link = (ev,x) => {AppNavigate.navigateToModule(ev,'agents', {"tab": "welcome", "agent": x } )}; - width = '8%'; - } - if(item === 'agent.name') { - width = '12%'; - } - if(item === 'rule.level') { - width = '7%'; - } - if(item === 'rule.id') { - link = (ev,x) => AppNavigate.navigateToModule(ev,'manager', {tab:'rules', redirectRule: x}); - width = '9%'; - } - if (item === 'rule.description' && columnsList.indexOf('syscheck.event') === -1) { - width = '30%'; - } - if(item === 'syscheck.event') { - width = '15%'; - } - if (item === 'rule.mitre.id') { - link = (ev, x) => { this.setState({ showMitreFlyout: true, selectedTechnique: x }) }; - } - if(arrayCompilance.indexOf(item) !== -1) { - width = '30%'; - } - - let column = { - field: item, - name: ( { this.setState({ hover: item }) }} - onMouseLeave={() => { this.setState({ hover: "" }) }} - style={{ display: "inline-flex" }}>{this.nameEquivalences[item] || item} {this.state.hover === item && - - { this.removeColumn(item); e.stopPropagation(); }} - iconType="cross" - aria-label="Filter" - iconSize="s" - /> - } - ), - sortable: true - } - - if (width) { - column.width = width; - } - if (link && item !== 'rule.mitre.id' || (item === 'rule.mitre.id' && this.props.shareFilterManager)) { - column.render = itemValue => { - return - {(item === 'agent.id' && itemValue === '000') && - {itemValue} - || item === 'rule.mitre.id' && Array.isArray(itemValue) && - itemValue.map(currentItem => { ev.stopPropagation(); }} - onMouseDown={(ev) => { ev.stopPropagation(); link(ev, currentItem) }}> - {currentItem} - ) - || - { ev.stopPropagation(); }} - onMouseDown={(ev) => { ev.stopPropagation(); link(ev, itemValue) }}> - {itemValue} - - } - - } - } - - return column; - }) - return columns; - } - - onTableChange = ({ page = {}, sort = {} }) => { - const { index: pageIndex, size: pageSize } = page; - const { field: sortField, direction: sortDirection } = sort; - - this.setState({ - pageIndex, - pageSize, - sortField, - sortDirection, - }); - }; - - getFiltersAsObject(filters) { - var result = {}; - for (var i = 0; i < filters.length; i++) { - result = { ...result, ...filters[i] } - } - return result; - } - - /** - * Adds a new negated filter with format { "filter_key" : "filter_value" }, e.g. {"agent.id": "001"} - * @param filter + if(item === 'agent.id') { + link = (ev,x) => {AppNavigate.navigateToModule(ev,'agents', {"tab": "welcome", "agent": x } )}; + width = '8%'; + } + if(item === 'agent.name') { + width = '12%'; + } + if(item === 'rule.level') { + width = '7%'; + } + if(item === 'rule.id') { + link = (ev,x) => AppNavigate.navigateToModule(ev,'manager', {tab:'rules', redirectRule: x}); + width = '9%'; + } + if (item === 'rule.description' && columnsList.indexOf('syscheck.event') === -1) { + width = '30%'; + } + if(item === 'syscheck.event') { + width = '15%'; + } + if (item === 'rule.mitre.id') { + link = (ev, x) => { this.setState({ showMitreFlyout: true, selectedTechnique: x }) }; + } + if(arrayCompilance.indexOf(item) !== -1) { + width = '30%'; + } + + let column = { + field: item, + name: ( { this.setState({ hover: item }) }} + onMouseLeave={() => { this.setState({ hover: "" }) }} + style={{ display: "inline-flex" }}>{this.nameEquivalences[item] || item} {this.state.hover === item && + + { this.removeColumn(item); e.stopPropagation(); }} + iconType="cross" + aria-label="Filter" + iconSize="s" + /> + } + ), + sortable: true + } + + if (width) { + column.width = width; + } + if (link && item !== 'rule.mitre.id' || (item === 'rule.mitre.id' && this.props.shareFilterManager)) { + column.render = itemValue => { + return + {(item === 'agent.id' && itemValue === '000') && + {itemValue} + || item === 'rule.mitre.id' && Array.isArray(itemValue) && + itemValue.map(currentItem => { ev.stopPropagation(); }} + onMouseDown={(ev) => { ev.stopPropagation(); link(ev, currentItem) }}> + {currentItem} + ) + || + { ev.stopPropagation(); }} + onMouseDown={(ev) => { ev.stopPropagation(); link(ev, itemValue) }}> + {itemValue} + + } + + } + } + + return column; + }) + return columns; + } + + onTableChange = ({ page = {}, sort = {} }) => { + const { index: pageIndex, size: pageSize } = page; + const { field: sortField, direction: sortDirection } = sort; + + this.setState({ + pageIndex, + pageSize, + sortField, + sortDirection, + }); + }; + + getFiltersAsObject(filters) { + var result = {}; + for (var i = 0; i < filters.length; i++) { + result = { ...result, ...filters[i] } + } + return result; + } + + /** + * Adds a new negated filter with format { "filter_key" : "filter_value" }, e.g. {"agent.id": "001"} + * @param filter + */ + addFilterOut(filter) { + const filterManager = this.props.shareFilterManager; + const key = Object.keys(filter)[0]; + const value = filter[key]; + const valuesArray = Array.isArray(value) ? [...value] : [value]; + valuesArray.map((item) => { + const formattedFilter = buildPhraseFilter({ name: key, type: "string" }, item, this.indexPattern); + formattedFilter.meta.negate = true; + + filterManager.addFilters(formattedFilter); + }) + this.setState({ pageIndex: 0 , tsUpdated: Date.now()}); + } + + /** + * Adds a new filter with format { "filter_key" : "filter_value" }, e.g. {"agent.id": "001"} + * @param filter */ - addFilterOut(filter) { - const filterManager = this.props.shareFilterManager; - const key = Object.keys(filter)[0]; - const value = filter[key]; - const valuesArray = Array.isArray(value) ? [...value] : [value]; - valuesArray.map((item) => { - const formattedFilter = buildPhraseFilter({ name: key, type: "string" }, item, this.indexPattern); - formattedFilter.meta.negate = true; - - filterManager.addFilters(formattedFilter); - }) - this.setState({ pageIndex: 0 , tsUpdated: Date.now()}); - } - - /** - * Adds a new filter with format { "filter_key" : "filter_value" }, e.g. {"agent.id": "001"} - * @param filter - */ - addFilter(filter) { - const filterManager = this.props.shareFilterManager; - const key = Object.keys(filter)[0]; - const value = filter[key]; - const valuesArray = Array.isArray(value) ? [...value] : [value]; - valuesArray.map((item) => { - const formattedFilter = buildPhraseFilter({ name: key, type: "string" }, item, this.indexPattern); - if (formattedFilter.meta.key === 'manager.name' || formattedFilter.meta.key === 'cluster.name') { - formattedFilter.meta["removable"] = false; - } - filterManager.addFilters(formattedFilter); - }) - this.setState({ pageIndex: 0 , tsUpdated: Date.now()}); - } - - onQuerySubmit = (payload: { dateRange: TimeRange, query: Query | undefined }) => { - this.setState({...payload, tsUpdated: Date.now()}); - } - - onFiltersUpdated = (filters: Filter[]) => { - this.setState({ pageIndex: 0 , tsUpdated: Date.now()}); - } - - closeMitreFlyout = () => { - this.setState({showMitreFlyout: false}); - } - + addFilter(filter) { + const filterManager = this.props.shareFilterManager; + const key = Object.keys(filter)[0]; + const value = filter[key]; + const valuesArray = Array.isArray(value) ? [...value] : [value]; + valuesArray.map((item) => { + const formattedFilter = buildPhraseFilter({ name: key, type: "string" }, item, this.indexPattern); + if (formattedFilter.meta.key === 'manager.name' || formattedFilter.meta.key === 'cluster.name') { + formattedFilter.meta["removable"] = false; + } + filterManager.addFilters(formattedFilter); + }) + this.setState({ pageIndex: 0 , tsUpdated: Date.now()}); + } + + onQuerySubmit = (payload: { dateRange: TimeRange, query: Query | undefined }) => { + this.setState({...payload, tsUpdated: Date.now()}); + } + + onFiltersUpdated = (filters: Filter[]) => { + this.setState({ pageIndex: 0 , tsUpdated: Date.now()}); + } + + closeMitreFlyout = () => { + this.setState({showMitreFlyout: false}); + } onMitreChangeFlyout = (showMitreFlyout: boolean) => { - this.setState({ showMitreFlyout }); - } - - openDiscover(e, techniqueID) { - AppNavigate.navigateToModule(e, 'overview', { "tab": 'mitre', "tabView": "discover", filters: { 'rule.mitre.id': techniqueID } }) - } - - openDashboard(e, techniqueID) { - AppNavigate.navigateToModule(e, 'overview', { "tab": 'mitre', "tabView": "dashboard", filters: { 'rule.mitre.id': techniqueID } }) - } - - render() { - if (this.state.isLoading) - return (
) - const { total, itemIdToExpandedRowMap, } = this.state; - const { query = this.state.query } = this.props; - const getRowProps = item => { - const { _id } = item; - return { - 'data-test-subj': `row-${_id}`, - className: 'customRowClass', - onClick: () => this.toggleDetails(item), - }; - }; - - const columns = this.columns(); - - const sorting: EuiTableSortingType<{}> = { - sort: { - //@ts-ignore - field: this.state.sortField, - direction: this.state.sortDirection, - } - }; - const pagination = { - pageIndex: this.state.pageIndex, - pageSize: this.state.pageSize, - totalItemCount: this.state.total > 10000 ? 10000 : this.state.total, - pageSizeOptions: [10, 25, 50], - }; - const noResultsText = `No results match for this search criteria`; - let flyout = this.state.showMitreFlyout ? - this.openDashboard(e, itemId)} - openDiscover={(e, itemId) => this.openDiscover(e, itemId)} - onChangeFlyout={this.onMitreChangeFlyout} - currentTechnique={this.state.selectedTechnique} /> - - : <>; - return ( -
- {this.props.kbnSearchBar && - } - {total - ? - - {this.state.alerts.length && ( - ({...alert._source, _id: alert._id}))} - className="module-discover-table" - itemId="_id" - itemIdToExpandedRowMap={itemIdToExpandedRowMap} - isExpandable={true} - columns={columns} - rowProps={getRowProps} - pagination={pagination} - sorting={sorting} - onChange={this.onTableChange} - /> - )} - - - : - - - - - - } - {flyout} -
); - } + this.setState({ showMitreFlyout }); + } + + openDiscover(e, techniqueID) { + AppNavigate.navigateToModule(e, 'overview', { "tab": 'mitre', "tabView": "discover", filters: { 'rule.mitre.id': techniqueID } }) + } + + openDashboard(e, techniqueID) { + AppNavigate.navigateToModule(e, 'overview', { "tab": 'mitre', "tabView": "dashboard", filters: { 'rule.mitre.id': techniqueID } }) + } + + render() { + if (this.state.isLoading) + return (
) + const { total, itemIdToExpandedRowMap, } = this.state; + const { query = this.state.query } = this.props; + const getRowProps = item => { + const { _id } = item; + return { + 'data-test-subj': `row-${_id}`, + className: 'customRowClass', + onClick: () => this.toggleDetails(item), + }; + }; + + const columns = this.columns(); + + const sorting: EuiTableSortingType<{}> = { + sort: { + //@ts-ignore + field: this.state.sortField, + direction: this.state.sortDirection, + } + }; + const pagination = { + pageIndex: this.state.pageIndex, + pageSize: this.state.pageSize, + totalItemCount: this.state.total > 10000 ? 10000 : this.state.total, + pageSizeOptions: [10, 25, 50], + }; + const noResultsText = `No results match for this search criteria`; + let flyout = this.state.showMitreFlyout ? + this.openDashboard(e, itemId)} + openDiscover={(e, itemId) => this.openDiscover(e, itemId)} + onChangeFlyout={this.onMitreChangeFlyout} + currentTechnique={this.state.selectedTechnique} /> + + : <>; + return ( +
+ {this.props.kbnSearchBar && + } + {total + ? + + {this.state.alerts.length && ( + ({...alert._source, _id: alert._id}))} + className="module-discover-table" + itemId="_id" + itemIdToExpandedRowMap={itemIdToExpandedRowMap} + isExpandable={true} + columns={columns} + rowProps={getRowProps} + pagination={pagination} + sorting={sorting} + onChange={this.onTableChange} + /> + )} + + + : + + + + + + } + {flyout} +
); + } }) diff --git a/public/components/overview/metrics/metrics.tsx b/public/components/overview/metrics/metrics.tsx index 47ceea809c..709a554c8c 100644 --- a/public/components/overview/metrics/metrics.tsx +++ b/public/components/overview/metrics/metrics.tsx @@ -23,10 +23,9 @@ import { buildRangeFilter, buildPhrasesFilter,buildPhraseFilter, buildExistsFilt import { getElasticAlerts, getIndexPattern } from '../mitre/lib'; import { ModulesHelper } from '../../common/modules/modules-helper' import { getDataPlugin } from '../../../kibana-services'; +import { withAllowedAgents } from '../../common/hocs/withAllowedAgents'; - - -export class Metrics extends Component { +export const Metrics = withAllowedAgents(class Metrics extends Component { _isMount = false; timefilter: { getTime(): any @@ -137,7 +136,8 @@ export class Metrics extends Component { const filterParams = {}; filterParams["time"] = this.timefilter.getTime(); filterParams["query"] = searchBarQuery; - filterParams["filters"] = this.filterManager.getFilters(); + filterParams["filters"] = this.filterManager.getFilters(); + this.props.filterAllowedAgents && filterParams["filters"].push(this.props.filterAllowedAgents); this.setState({filterParams, loading: true}); const newOnClick = {}; @@ -310,5 +310,5 @@ export class Metrics extends Component { ) } -} +}) diff --git a/public/components/visualize/components/security-alerts.tsx b/public/components/visualize/components/security-alerts.tsx index 5a10439714..57b61fc468 100644 --- a/public/components/visualize/components/security-alerts.tsx +++ b/public/components/visualize/components/security-alerts.tsx @@ -1,33 +1,41 @@ /* - * Wazuh app - React component for Visualize. - * Copyright (C) 2015-2021 Wazuh, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * Find more information about this on the LICENSE file. - */ - +* Wazuh app - React component for Visualize. +* Copyright (C) 2015-2021 Wazuh, Inc. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* Find more information about this on the LICENSE file. +*/ import React from 'react'; import { useFilterManager, useQuery, useRefreshAngularDiscover } from '../../common/hooks'; import { Discover } from '../../common/modules/discover'; - +import { getFilterWithAuthorizedAgents } from '../../../react-services/filter-authorization-agents'; +import { useSelector } from 'react-redux'; +import { withReduxProvider } from '../../common/hocs'; +import { useAllowedAgents } from '../../common/hooks/useAllowedAgents' + export const SecurityAlerts = () => { - const [query] = useQuery(); - const filterManager = useFilterManager(); - const refreshAngularDiscover = useRefreshAngularDiscover(); - - return ( - { }} - refreshAngularDiscover={refreshAngularDiscover} - /> - ) + const [query] = useQuery(); + const filterManager = useFilterManager(); + const copyOfFilterManager = filterManager + const refreshAngularDiscover = useRefreshAngularDiscover(); + + const customFilterWithAllowedAgents = []; + const {allowedAgents, filterAllowedAgents} = useAllowedAgents(); + filterAllowedAgents && customFilterWithAllowedAgents.push(filterAllowedAgents); + return ( + { }} + refreshAngularDiscover={refreshAngularDiscover} + /> + ) } \ No newline at end of file diff --git a/public/components/visualize/wz-visualize.js b/public/components/visualize/wz-visualize.js index b43ab30c6b..5cf22181d4 100644 --- a/public/components/visualize/wz-visualize.js +++ b/public/components/visualize/wz-visualize.js @@ -36,10 +36,12 @@ import { PatternHandler } from '../../react-services/pattern-handler'; import { getToasts } from '../../kibana-services'; import { SecurityAlerts } from './components'; import { toMountPoint } from '../../../../../src/plugins/kibana_react/public'; +import { withReduxProvider } from '../common/hocs'; + const visHandler = new VisHandlers(); -export class WzVisualize extends Component { +export const WzVisualize = withReduxProvider(class WzVisualize extends Component { _isMount = false; constructor(props) { super(props); @@ -332,4 +334,4 @@ export class WzVisualize extends Component { ); } -} +}) diff --git a/public/kibana-integrations/discover/build_services.ts b/public/kibana-integrations/discover/build_services.ts index ea30560f18..1ef9711c49 100644 --- a/public/kibana-integrations/discover/build_services.ts +++ b/public/kibana-integrations/discover/build_services.ts @@ -87,7 +87,6 @@ export async function buildServices( overlays: core.overlays, }; const savedObjectService = createSavedSearchesLoader(services); */ - return { addBasePath: core.http.basePath.prepend, capabilities: core.application.capabilities, diff --git a/public/kibana-integrations/kibana-discover.js b/public/kibana-integrations/kibana-discover.js index a87f0013b3..423b189292 100644 --- a/public/kibana-integrations/kibana-discover.js +++ b/public/kibana-integrations/kibana-discover.js @@ -95,6 +95,7 @@ import { AppState } from '../react-services/app-state'; import { createFixedScroll } from './discover/application/angular/directives/fixed_scroll'; import './discover/application/index.scss'; +import { getFilterWithAuthorizedAgents } from '../react-services/filter-authorization-agents'; const fetchStatuses = { UNINITIALIZED: 'uninitialized', @@ -528,6 +529,8 @@ function discoverController( { next: () => { $scope.filters = filterManager.filters; + const customFilterAllowedAgents = getFilterWithAuthorizedAgents(store.getState().appStateReducers.allowedAgents); + $scope.filters.push(customFilterAllowedAgents); // Wazuh. Hides the alerts of the '000' agent if it is in the configuration const buildFilters = () => { const { hideManagerAlerts } = wazuhConfig.getConfig(); From e204f4ff248b93abaaebb1db38475472c20170f1 Mon Sep 17 00:00:00 2001 From: CPAlejandro Date: Wed, 7 Apr 2021 18:10:47 +0200 Subject: [PATCH 07/14] Changes that solved bugs and upgrades --- public/components/common/hooks/index.ts | 4 +- .../common/modules/discover/discover.tsx | 27 ++++--- .../visualize/components/security-alerts.tsx | 3 - public/kibana-integrations/kibana-discover.js | 2 +- public/kibana-integrations/kibana-vis.js | 2 + .../filter-authorization-agents.ts | 70 +++++++++---------- public/react-services/wz-agents.ts | 15 ++-- public/redux/actions/appStateActions.js | 6 +- 8 files changed, 61 insertions(+), 68 deletions(-) diff --git a/public/components/common/hooks/index.ts b/public/components/common/hooks/index.ts index 6bc5377e82..63537e4229 100644 --- a/public/components/common/hooks/index.ts +++ b/public/components/common/hooks/index.ts @@ -26,8 +26,8 @@ export { useUserPermissions, useUserPermissionsRequirements, useUserPermissionsP export { useUserRoles, useUserRolesRequirements, useUserRolesPrivate } from './useUserRoles'; -export { useRefreshAngularDiscover } from './useResfreshAngularDiscover' +export { useRefreshAngularDiscover } from './useResfreshAngularDiscover'; -export { useAllowedAgents } from './useAllowedAgents' +export { useAllowedAgents } from './useAllowedAgents'; export { useApiRequest } from './useApiRequest'; diff --git a/public/components/common/modules/discover/discover.tsx b/public/components/common/modules/discover/discover.tsx index 1125bb0b8f..de931bdd99 100644 --- a/public/components/common/modules/discover/discover.tsx +++ b/public/components/common/modules/discover/discover.tsx @@ -1,15 +1,14 @@ - /* -* Wazuh app - Integrity monitoring table component -* Copyright (C) 2015-2021 Wazuh, Inc. -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* Find more information about this on the LICENSE file. -*/ - +/* + * Wazuh app - Integrity monitoring table component + * Copyright (C) 2015-2021 Wazuh, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Find more information about this on the LICENSE file. +*/ import React, { Component, } from 'react'; import './discover.scss'; import { FilterManager, Filter } from '../../../../../../../src/plugins/data/public/' @@ -26,7 +25,7 @@ import { withReduxProvider } from '../../../common/hocs'; import { connect } from 'react-redux'; import { compose } from 'redux'; import _ from 'lodash'; - + import { EuiBasicTable, EuiLoadingContent, @@ -291,7 +290,7 @@ export const Discover = compose( const filters = this.props.shareFilterManager ? this.props.shareFilterManager.filters : []; const previousFilters = this.KibanaServices && this.KibanaServices.query.filterManager.filters ? this.KibanaServices.query.filterManager.filters : []; - const CustomFilterWith = this.props.shareFilterManagerWithUserAuthorized; + const CustomFilterWith = this.props.shareFilterManagerWithUserAuthorized; const elasticQuery = buildEsQuery( undefined, diff --git a/public/components/visualize/components/security-alerts.tsx b/public/components/visualize/components/security-alerts.tsx index 57b61fc468..0f7a0ff0d4 100644 --- a/public/components/visualize/components/security-alerts.tsx +++ b/public/components/visualize/components/security-alerts.tsx @@ -12,9 +12,6 @@ import React from 'react'; import { useFilterManager, useQuery, useRefreshAngularDiscover } from '../../common/hooks'; import { Discover } from '../../common/modules/discover'; -import { getFilterWithAuthorizedAgents } from '../../../react-services/filter-authorization-agents'; -import { useSelector } from 'react-redux'; -import { withReduxProvider } from '../../common/hocs'; import { useAllowedAgents } from '../../common/hooks/useAllowedAgents' export const SecurityAlerts = () => { diff --git a/public/kibana-integrations/kibana-discover.js b/public/kibana-integrations/kibana-discover.js index 423b189292..694ed3190a 100644 --- a/public/kibana-integrations/kibana-discover.js +++ b/public/kibana-integrations/kibana-discover.js @@ -530,7 +530,7 @@ function discoverController( next: () => { $scope.filters = filterManager.filters; const customFilterAllowedAgents = getFilterWithAuthorizedAgents(store.getState().appStateReducers.allowedAgents); - $scope.filters.push(customFilterAllowedAgents); + customFilterAllowedAgents && $scope.filters.push(customFilterAllowedAgents); // Wazuh. Hides the alerts of the '000' agent if it is in the configuration const buildFilters = () => { const { hideManagerAlerts } = wazuhConfig.getConfig(); diff --git a/public/kibana-integrations/kibana-vis.js b/public/kibana-integrations/kibana-vis.js index 415523b6b4..f7b9e82a19 100644 --- a/public/kibana-integrations/kibana-vis.js +++ b/public/kibana-integrations/kibana-vis.js @@ -293,6 +293,7 @@ class KibanaVis extends Component { this.visHandler.handler.data$.subscribe(this.renderComplete()); this.visHandlers.addItem(this.visHandler); this.setSearchSource(discoverList); + Object.keys(agentsFilters).length !== 0 ? filters.pop() : null; } else if (this.rendered && !this.deadField) { // There's a visualization object -> just update its filters @@ -304,6 +305,7 @@ class KibanaVis extends Component { this.$rootScope.rendered = "true"; this.visHandler.updateInput(visInput); this.setSearchSource(discoverList); + Object.keys(agentsFilters).length !== 0 ? filters.pop() : null; } if (this.state.visRefreshingIndex) this.setState({ visRefreshingIndex: false }); } diff --git a/public/react-services/filter-authorization-agents.ts b/public/react-services/filter-authorization-agents.ts index 4ddb403502..a64adac255 100644 --- a/public/react-services/filter-authorization-agents.ts +++ b/public/react-services/filter-authorization-agents.ts @@ -11,42 +11,38 @@ */ import { AppState } from '../react-services/app-state'; - - export function getFilterWithAuthorizedAgents(agentsIds) { - if(!agentsIds) - return ; //check for empty agents array - if(agentsIds.length == 0){return } - - const usedPattern = AppState.getCurrentPattern(); - const isMonitoringIndex = usedPattern.indexOf('monitoring') > -1; - const field = isMonitoringIndex ? 'id' : 'agent.id'; - return { - meta: { - index: usedPattern, - type: 'phrases', - key: field, - value: agentsIds.toString(), - params: agentsIds, - alias: null, - negate: false, - disabled: false - }, - query: { - bool: { - should: agentsIds.map(id => { - return { - match_phrase: { - [field]: id - } - }; - }), - minimum_should_match: 1 - } - }, - $state: { - store: 'appState' - } - } - } + if(!agentsIds || agentsIds.length == 0){return } + + const usedPattern = AppState.getCurrentPattern(); + const isMonitoringIndex = usedPattern.indexOf('monitoring') > -1; + const field = isMonitoringIndex ? 'id' : 'agent.id'; + return { + meta: { + index: usedPattern, + type: 'phrases', + key: field, + value: agentsIds.toString(), + params: agentsIds, + alias: null, + negate: false, + disabled: false + }, + query: { + bool: { + should: agentsIds.map(id => { + return { + match_phrase: { + [field]: id + } + }; + }), + minimum_should_match: 1 + } + }, + $state: { + store: 'appState' + } + } +} diff --git a/public/react-services/wz-agents.ts b/public/react-services/wz-agents.ts index c26940b527..9d041d913a 100644 --- a/public/react-services/wz-agents.ts +++ b/public/react-services/wz-agents.ts @@ -35,15 +35,14 @@ export function hasAgentSupportModule(agent, component){ return !(UnsupportedComponents[agentOSType].includes(component)); }; - export async function getAuthorizedAgents() { - const agentsList: IApiResponse<{id: string}> = await WzRequest.apiReq('GET', `/agents`, {}) - .catch(error => { + try{ + const agentsList: IApiResponse<{id: string}> = await WzRequest.apiReq('GET', `/agents`, {}); + const allowedAgents = agentsList ? agentsList.data.data.affected_items.map((agent) => agent.id) : [] + return allowedAgents; + }catch(error) { getToasts().addError(error, {title: `Error getting user authorized agents`} as ErrorToastOptions); return Promise.reject(); - }); - - const allowedAgents = agentsList ? agentsList.data.data.affected_items.map((agent) => agent.id) : [] + }; +} - return allowedAgents; -} \ No newline at end of file diff --git a/public/redux/actions/appStateActions.js b/public/redux/actions/appStateActions.js index c78b8ae5ac..d38da02f05 100644 --- a/public/redux/actions/appStateActions.js +++ b/public/redux/actions/appStateActions.js @@ -217,11 +217,11 @@ export const updateWithUserLogged = (withUserLogged) => { /** * Updates allowedAgents in the appState store - * @param GET_ALLOWED_AGENTS + * @param allowedAgents */ -export const updateAllowedAgents = data => { +export const updateAllowedAgents = allowedAgents => { return { type: 'GET_ALLOWED_AGENTS', - allowedAgents: data + allowedAgents }; }; \ No newline at end of file From be56e9128b518cb029cac0f401f37d18b35f44ad Mon Sep 17 00:00:00 2001 From: Franco Charriol Date: Wed, 7 Apr 2021 16:04:23 -0300 Subject: [PATCH 08/14] fix: Solved repeated filters --- public/kibana-integrations/kibana-vis.js | 34 +++++++++++++----------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/public/kibana-integrations/kibana-vis.js b/public/kibana-integrations/kibana-vis.js index f7b9e82a19..eea452e320 100644 --- a/public/kibana-integrations/kibana-vis.js +++ b/public/kibana-integrations/kibana-vis.js @@ -34,6 +34,8 @@ import { } from "@elastic/eui"; import { getAngularModule, getToasts, getVisualizationsPlugin, getSavedObjects, getDataPlugin, getChrome, getOverlays } from '../kibana-services'; import { KnownFields } from "../utils/known-fields"; +import { concat } from 'lodash'; +import { AppState } from '../react-services/app-state'; class KibanaVis extends Component { _isMounted = false; @@ -253,22 +255,24 @@ class KibanaVis extends Component { const query = !isAgentStatus ? discoverList[0] : {}; const rawVis = raw ? raw.filter((item) => item && item.id === this.visID) : []; - let vizPattern; - try { - vizPattern = JSON.parse(rawVis[0].attributes.kibanaSavedObjectMeta.searchSourceJSON).index; - } catch (ex) { - console.warning(`kibana-vis exception: ${ex.message || ex}`); - } - const agentsFilters = this.getUserAgentsFilters(vizPattern); - Object.keys(agentsFilters).length !== 0 ? filters.push(agentsFilters) : null; - - const visInput = { - timeRange, - filters, - query - }; if (rawVis.length && discoverList.length) { + let vizPattern; + try { + vizPattern = JSON.parse(rawVis[0].attributes.kibanaSavedObjectMeta.searchSourceJSON).index; + } catch (ex) { + console.warn(`kibana-vis exception: ${ex.message || ex}`); + } + const agentsFilters = this.getUserAgentsFilters(vizPattern); + Object.keys(agentsFilters).length ? concat(filters, [agentsFilters]) : null; + + + const visInput = { + timeRange, + filters, + query + }; + // There are pending updates from the discover (which is the one who owns the true app state) if (!this.visualization && !this.rendered && !this.renderInProgress) { @@ -293,7 +297,6 @@ class KibanaVis extends Component { this.visHandler.handler.data$.subscribe(this.renderComplete()); this.visHandlers.addItem(this.visHandler); this.setSearchSource(discoverList); - Object.keys(agentsFilters).length !== 0 ? filters.pop() : null; } else if (this.rendered && !this.deadField) { // There's a visualization object -> just update its filters @@ -305,7 +308,6 @@ class KibanaVis extends Component { this.$rootScope.rendered = "true"; this.visHandler.updateInput(visInput); this.setSearchSource(discoverList); - Object.keys(agentsFilters).length !== 0 ? filters.pop() : null; } if (this.state.visRefreshingIndex) this.setState({ visRefreshingIndex: false }); } From 3d7368b13ea7138e2ea2e6138ed45ce460352aa1 Mon Sep 17 00:00:00 2001 From: Franco Charriol Date: Wed, 7 Apr 2021 16:06:48 -0300 Subject: [PATCH 09/14] fix: Solved repeated filters in kibana discover --- public/kibana-integrations/kibana-discover.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/kibana-integrations/kibana-discover.js b/public/kibana-integrations/kibana-discover.js index 694ed3190a..98d8ee6c23 100644 --- a/public/kibana-integrations/kibana-discover.js +++ b/public/kibana-integrations/kibana-discover.js @@ -530,7 +530,7 @@ function discoverController( next: () => { $scope.filters = filterManager.filters; const customFilterAllowedAgents = getFilterWithAuthorizedAgents(store.getState().appStateReducers.allowedAgents); - customFilterAllowedAgents && $scope.filters.push(customFilterAllowedAgents); + customFilterAllowedAgents && _.concat($scope.filters, [customFilterAllowedAgents]); // Wazuh. Hides the alerts of the '000' agent if it is in the configuration const buildFilters = () => { const { hideManagerAlerts } = wazuhConfig.getConfig(); From ae42f417a14ed7ea7e8e2f1a10980d3099f8522f Mon Sep 17 00:00:00 2001 From: Franco Charriol Date: Wed, 7 Apr 2021 17:44:07 -0300 Subject: [PATCH 10/14] Separate agents filters in reports --- common/constants.ts | 2 + public/kibana-integrations/kibana-discover.js | 2 +- public/kibana-integrations/kibana-vis.js | 54 +++------------- .../filter-authorization-agents.ts | 8 ++- server/controllers/wazuh-reporting.ts | 34 +++++++++-- server/lib/reporting/printer.ts | 61 +++++++++++++++++++ 6 files changed, 107 insertions(+), 54 deletions(-) diff --git a/common/constants.ts b/common/constants.ts index a163e007bf..5d291d90a5 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -208,3 +208,5 @@ export enum WAZUH_MODULES_ID{ VIRUSTOTAL = 'virustotal', GDPR = 'gdpr' } + +export const AUTHORIZED_AGENTS = 'authorized-agents'; diff --git a/public/kibana-integrations/kibana-discover.js b/public/kibana-integrations/kibana-discover.js index 98d8ee6c23..ebab814bcd 100644 --- a/public/kibana-integrations/kibana-discover.js +++ b/public/kibana-integrations/kibana-discover.js @@ -530,7 +530,7 @@ function discoverController( next: () => { $scope.filters = filterManager.filters; const customFilterAllowedAgents = getFilterWithAuthorizedAgents(store.getState().appStateReducers.allowedAgents); - customFilterAllowedAgents && _.concat($scope.filters, [customFilterAllowedAgents]); + $scope.filters = customFilterAllowedAgents ? _.union($scope.filters, [customFilterAllowedAgents]) : $scope.filters; // Wazuh. Hides the alerts of the '000' agent if it is in the configuration const buildFilters = () => { const { hideManagerAlerts } = wazuhConfig.getConfig(); diff --git a/public/kibana-integrations/kibana-vis.js b/public/kibana-integrations/kibana-vis.js index eea452e320..188cc6a644 100644 --- a/public/kibana-integrations/kibana-vis.js +++ b/public/kibana-integrations/kibana-vis.js @@ -34,8 +34,10 @@ import { } from "@elastic/eui"; import { getAngularModule, getToasts, getVisualizationsPlugin, getSavedObjects, getDataPlugin, getChrome, getOverlays } from '../kibana-services'; import { KnownFields } from "../utils/known-fields"; -import { concat } from 'lodash'; -import { AppState } from '../react-services/app-state'; +import { union } from 'lodash'; +import { getFilterWithAuthorizedAgents } from '../react-services/filter-authorization-agents'; +import { AUTHORIZED_AGENTS } from '../../common/constants'; + class KibanaVis extends Component { _isMounted = false; @@ -200,44 +202,6 @@ class KibanaVis extends Component { } }; - getUserAgentsFilters = (pattern = "") => { - const agentsIds = this.props.allowedAgents; - - //check for empty agents array - if(agentsIds.length == 0){return {}} - - const usedPattern = pattern ? pattern : AppState.getCurrentPattern(); - const isMonitoringIndex = usedPattern.indexOf('monitoring') > -1; - const field = isMonitoringIndex ? 'id' : 'agent.id'; - return { - meta: { - index: usedPattern, - type: 'phrases', - key: field, - value: agentsIds.toString(), - params: agentsIds, - alias: null, - negate: false, - disabled: false - }, - query: { - bool: { - should: agentsIds.map(id => { - return { - match_phrase: { - [field]: id - } - }; - }), - minimum_should_match: 1 - } - }, - $state: { - store: 'appState' - } - } - } - myRender = async (raw) => { const timefilter = getDataPlugin().query.timefilter.timefilter; try { @@ -251,7 +215,7 @@ class KibanaVis extends Component { isAgentStatus && timeFilterSeconds < 900 ? { from: "now-15m", to: "now", mode: "quick" } : timefilter.getTime(); - const filters = isAgentStatus ? [] : discoverList[1] || []; + let filters = isAgentStatus ? [] : discoverList[1] || []; const query = !isAgentStatus ? discoverList[0] : {}; const rawVis = raw ? raw.filter((item) => item && item.id === this.visID) : []; @@ -263,9 +227,11 @@ class KibanaVis extends Component { } catch (ex) { console.warn(`kibana-vis exception: ${ex.message || ex}`); } - const agentsFilters = this.getUserAgentsFilters(vizPattern); - Object.keys(agentsFilters).length ? concat(filters, [agentsFilters]) : null; - + + if (!filters.find((filter) => filter.meta.controlledBy === AUTHORIZED_AGENTS)) { + const agentsFilters = getFilterWithAuthorizedAgents(this.props.allowedAgents, vizPattern); + filters = Object.keys(agentsFilters).length ? union(filters, [agentsFilters]) : filters; + } const visInput = { timeRange, diff --git a/public/react-services/filter-authorization-agents.ts b/public/react-services/filter-authorization-agents.ts index a64adac255..0b4df9b784 100644 --- a/public/react-services/filter-authorization-agents.ts +++ b/public/react-services/filter-authorization-agents.ts @@ -10,12 +10,13 @@ * Find more information about this on the LICENSE file. */ import { AppState } from '../react-services/app-state'; +import { AUTHORIZED_AGENTS } from '../../common/constants'; -export function getFilterWithAuthorizedAgents(agentsIds) { +export function getFilterWithAuthorizedAgents(agentsIds, pattern) { //check for empty agents array if(!agentsIds || agentsIds.length == 0){return } - const usedPattern = AppState.getCurrentPattern(); + const usedPattern = pattern ? pattern : AppState.getCurrentPattern(); const isMonitoringIndex = usedPattern.indexOf('monitoring') > -1; const field = isMonitoringIndex ? 'id' : 'agent.id'; return { @@ -27,7 +28,8 @@ export function getFilterWithAuthorizedAgents(agentsIds) { params: agentsIds, alias: null, negate: false, - disabled: false + disabled: false, + controlledBy: AUTHORIZED_AGENTS, }, query: { bool: { diff --git a/server/controllers/wazuh-reporting.ts b/server/controllers/wazuh-reporting.ts index e028423af0..0397e6791b 100644 --- a/server/controllers/wazuh-reporting.ts +++ b/server/controllers/wazuh-reporting.ts @@ -32,7 +32,7 @@ import { KibanaRequest, RequestHandlerContext, KibanaResponseFactory } from 'src import { ReportPrinter } from '../lib/reporting/printer'; import { log } from '../lib/logger'; -import { WAZUH_ALERTS_PATTERN, WAZUH_DATA_DOWNLOADS_DIRECTORY_PATH, WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH } from '../../common/constants'; +import { WAZUH_ALERTS_PATTERN, WAZUH_DATA_DOWNLOADS_DIRECTORY_PATH, WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, AUTHORIZED_AGENTS } from '../../common/constants'; import { createDirectoryIfNotExists, createDataDirectoryIfNotExists } from '../lib/filesystem'; @@ -44,7 +44,7 @@ export class WazuhReportingCtrl { * @param {String} filters E.g: cluster.name: wazuh AND rule.groups: vulnerability * @param {String} searchBar search term */ - private sanitizeKibanaFilters(filters: any, searchBar?: string): string { + private sanitizeKibanaFilters(filters: any, searchBar?: string): [string, string] { log('reporting:sanitizeKibanaFilters', `Started to sanitize filters`, 'info'); log( 'reporting:sanitizeKibanaFilters', @@ -53,7 +53,19 @@ export class WazuhReportingCtrl { ); let str = ''; + const agentsFilter: any = []; + + //separate agents filter + filters = filters.filter((filter) => { + if (filter.meta.controlledBy === AUTHORIZED_AGENTS) { + agentsFilter.push(filter); + return false; + } + return filter; + }); + const len = filters.length; + for (let i = 0; i < len; i++) { const { negate, key, value, params, type } = filters[i].meta; str += `${negate ? 'NOT ' : ''}`; @@ -71,8 +83,12 @@ export class WazuhReportingCtrl { if (searchBar) { str += ' AND ' + searchBar; } - log('reporting:sanitizeKibanaFilters', `str: ${str}`, 'debug'); - return str; + + const agentsFilterStr = agentsFilter.map((filter) => filter.meta.value).join(','); + + log('reporting:sanitizeKibanaFilters', `str: ${str}, agentsFilterStr: ${agentsFilterStr}`, 'debug'); + + return [str, agentsFilterStr]; } /** @@ -1153,7 +1169,7 @@ export class WazuhReportingCtrl { await this.renderHeader(context, printer, section, moduleID, agents, apiId); - const sanitizedFilters = filters ? this.sanitizeKibanaFilters(filters, searchBar) : false; + const [sanitizedFilters, agentsFilter] = filters ? this.sanitizeKibanaFilters(filters, searchBar) : [false, false]; if (time && sanitizedFilters) { printer.addTimeRangeAndFilters(from, to, sanitizedFilters, browserTimezone); @@ -1180,7 +1196,13 @@ export class WazuhReportingCtrl { printer.addTables(tables); }; - await printer.print(path.join(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, userID, name)); + //add authorized agents + if (agentsFilter) { + printer.addAgentsFilters(agentsFilter); + } + + + await printer.print(path.join(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, userID, name)); return response.ok({ body: { diff --git a/server/lib/reporting/printer.ts b/server/lib/reporting/printer.ts index fceebad0a4..517ee3fbcc 100644 --- a/server/lib/reporting/printer.ts +++ b/server/lib/reporting/printer.ts @@ -527,6 +527,66 @@ export class ReportPrinter{ return this.addContent(title).addNewLine(); } + addAgentsFilters(agents){ + log( + 'reporting:addAgentsFilters', + `Started to render the authorized agents filters`, + 'info' + ); + log( + 'reporting:addAgentsFilters', + `agents: ${agents}`, + 'debug' + ); + + this.addNewLine(); + + this.addContent({ + text: + 'NOTE: This report only includes the authorized agents of the user who generated the report', + style: { fontSize: 10, color: COLORS.PRIMARY }, + margin: [0, 0, 0, 5] + }); + this.addContent({ + fontSize: 8, + table: { + widths: ['*'], + body: [ + [ + { + columns: [ + { + svg: filterIconRaw, + width: 10, + height: 10, + margin: [40, 6, 0, 0] + }, + { + text: `Agent IDs: ${agents}` || '-', + margin: [43, 0, 0, 0], + style: { fontSize: 8, color: '#333' } + } + ] + } + ] + ] + }, + margin: [-40, 0, -40, 0], + layout: { + fillColor: () => null, + hLineWidth: () => 0, + vLineWidth: () => 0 + } + }); + + this.addContent({ text: '\n' }); + log( + 'reporting:addAgentsFilters', + 'Time range and filters rendered', + 'debug' + ); + } + async print(path: string){ const document = this._printer.createPdfKitDocument({...pageConfiguration, content: this._content}); await document.pipe( @@ -534,4 +594,5 @@ export class ReportPrinter{ ); document.end(); } + } \ No newline at end of file From 5c2a95485029ccef51c1b9f205a54a9b77818e9e Mon Sep 17 00:00:00 2001 From: CPAlejandro Date: Thu, 8 Apr 2021 11:22:14 +0200 Subject: [PATCH 11/14] Fixed bug that don't return more that 500 agents --- public/react-services/wz-agents.ts | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/public/react-services/wz-agents.ts b/public/react-services/wz-agents.ts index 9d041d913a..8f00eb6c27 100644 --- a/public/react-services/wz-agents.ts +++ b/public/react-services/wz-agents.ts @@ -37,9 +37,29 @@ export function hasAgentSupportModule(agent, component){ export async function getAuthorizedAgents() { try{ - const agentsList: IApiResponse<{id: string}> = await WzRequest.apiReq('GET', `/agents`, {}); - const allowedAgents = agentsList ? agentsList.data.data.affected_items.map((agent) => agent.id) : [] - return allowedAgents; + const params = { limit: 500 }; + const output: IApiResponse<{id: string}> = await WzRequest.apiReq('GET', `/agents`, {}); + const totalItems = (((output || {}).data || {}).data || {}).total_affected_items; + let itemsArray = []; + if (totalItems && output.data && output.data.data && totalItems > 500) { + params.offset = 0; + itemsArray.push(...output.data.data.affected_items); + while (itemsArray.length < totalItems && params.offset < totalItems) { + params.offset += params.limit; + const tmpData: IApiResponse<{id: string}> = await WzRequest.apiReq( + 'GET', + `/agents`, + { params: params }, + ); + itemsArray.push(...tmpData.data.data.affected_items); + } + const allowedAgents = itemsArray ? itemsArray.map((agent) => agent.id) : []; + return allowedAgents; + } + else{ + const allowedAgents = output ? output.data.data.affected_items.map((agent) => agent.id) : [] + return allowedAgents; + } }catch(error) { getToasts().addError(error, {title: `Error getting user authorized agents`} as ErrorToastOptions); return Promise.reject(); From bc517d04868b4d0498e9f00587b3bc5485fab9ba Mon Sep 17 00:00:00 2001 From: Franco Charriol Date: Thu, 8 Apr 2021 18:55:32 -0300 Subject: [PATCH 12/14] Added validation for agent:read policies --- public/kibana-integrations/kibana-discover.js | 5 +- public/kibana-integrations/kibana-vis.js | 2 +- .../react-services/wz-authentication.test.ts | 127 ++++++++++++++++++ public/react-services/wz-authentication.ts | 31 ++++- 4 files changed, 159 insertions(+), 6 deletions(-) create mode 100644 public/react-services/wz-authentication.test.ts diff --git a/public/kibana-integrations/kibana-discover.js b/public/kibana-integrations/kibana-discover.js index ebab814bcd..9058c4b4e1 100644 --- a/public/kibana-integrations/kibana-discover.js +++ b/public/kibana-integrations/kibana-discover.js @@ -528,9 +528,10 @@ function discoverController( searchBarChanges, { next: () => { - $scope.filters = filterManager.filters; const customFilterAllowedAgents = getFilterWithAuthorizedAgents(store.getState().appStateReducers.allowedAgents); - $scope.filters = customFilterAllowedAgents ? _.union($scope.filters, [customFilterAllowedAgents]) : $scope.filters; + filterManager.filters = customFilterAllowedAgents ? _.union(filterManager.filters, [customFilterAllowedAgents]) : filterManager.filters; + $scope.filters = filterManager.filters; + // Wazuh. Hides the alerts of the '000' agent if it is in the configuration const buildFilters = () => { const { hideManagerAlerts } = wazuhConfig.getConfig(); diff --git a/public/kibana-integrations/kibana-vis.js b/public/kibana-integrations/kibana-vis.js index 188cc6a644..096e937868 100644 --- a/public/kibana-integrations/kibana-vis.js +++ b/public/kibana-integrations/kibana-vis.js @@ -230,7 +230,7 @@ class KibanaVis extends Component { if (!filters.find((filter) => filter.meta.controlledBy === AUTHORIZED_AGENTS)) { const agentsFilters = getFilterWithAuthorizedAgents(this.props.allowedAgents, vizPattern); - filters = Object.keys(agentsFilters).length ? union(filters, [agentsFilters]) : filters; + filters = agentsFilters ? union(filters, [agentsFilters]) : filters; } const visInput = { diff --git a/public/react-services/wz-authentication.test.ts b/public/react-services/wz-authentication.test.ts new file mode 100644 index 0000000000..29d0af8207 --- /dev/null +++ b/public/react-services/wz-authentication.test.ts @@ -0,0 +1,127 @@ +import { WzAuthentication } from './wz-authentication'; + +describe('Wazuh Authentication', () => { + describe('User has agent permissions', () => { + + describe('Given a user without agent:read permission', () => { + it('Should return true', () => { + const policies = {}; + const result = WzAuthentication.userHasAgentsPermissions(policies); + expect(result).toBeTruthy(); + }); + }); + + describe('Given a user with read permission for all ids and groups', () => { + it('Should return false', () => { + const policies = { + 'agent:read': { + 'agent:id:*': 'allow', + 'agent:group:*': 'allow', + } + }; + const result = WzAuthentication.userHasAgentsPermissions(policies); + expect(result).not.toBeTruthy(); + }); + }); + + describe('Given a user with read permission for all ids only', () => { + it('Should return false', () => { + const policies = { + 'agent:read': { + 'agent:id:*': 'allow', + } + }; + const result = WzAuthentication.userHasAgentsPermissions(policies); + expect(result).not.toBeTruthy(); + }); + }); + + describe('Given a user with read permission for all groups only', () => { + it('Should return false', () => { + const policies = { + 'agent:read': { + 'agent:id:*': 'allow', + } + }; + const result = WzAuthentication.userHasAgentsPermissions(policies); + expect(result).not.toBeTruthy(); + }); + }); + + describe('Given a user with read permission for all ids and some ids too', () => { + it('Should return false', () => { + const policies = { + 'agent:read': { + 'agent:id:*': 'allow', + 'agent:id:001': 'allow', + } + }; + const result = WzAuthentication.userHasAgentsPermissions(policies); + expect(result).not.toBeTruthy(); + }); + }); + + describe('Given a user with read permission for all ids and some groups too', () => { + it('Should return false', () => { + const policies = { + 'agent:read': { + 'agent:id:*': 'allow', + 'agent:group:default': 'allow', + } + }; + const result = WzAuthentication.userHasAgentsPermissions(policies); + expect(result).not.toBeTruthy(); + }); + }); + + describe('Given a user with read permission for all ids but deny some ids too', () => { + it('Should return true', () => { + const policies = { + 'agent:read': { + 'agent:id:*': 'allow', + 'agent:id:001': 'deny', + } + }; + const result = WzAuthentication.userHasAgentsPermissions(policies); + expect(result).toBeTruthy(); + }); + }); + + describe('Given a user with read permission for all groups ids but deny some groups too', () => { + it('Should return true', () => { + const policies = { + 'agent:read': { + 'agent:groups:*': 'allow', + 'agent:group:default': 'deny', + } + }; + const result = WzAuthentication.userHasAgentsPermissions(policies); + expect(result).toBeTruthy(); + }); + }); + + describe('Given a user with read permission for some ids', () => { + it('Should return true', () => { + const policies = { + 'agent:read': { + 'agent:id:001': 'allow', + } + }; + const result = WzAuthentication.userHasAgentsPermissions(policies); + expect(result).toBeTruthy(); + }); + }); + + describe('Given a user with read permission for some groups', () => { + it('Should return true', () => { + const policies = { + 'agent:read': { + 'agent:groups:default': 'allow', + } + }; + const result = WzAuthentication.userHasAgentsPermissions(policies); + expect(result).toBeTruthy(); + }); + }); + }); +}); diff --git a/public/react-services/wz-authentication.ts b/public/react-services/wz-authentication.ts index 334db2abcc..c366ea69b8 100644 --- a/public/react-services/wz-authentication.ts +++ b/public/react-services/wz-authentication.ts @@ -49,12 +49,18 @@ export class WzAuthentication{ // Decode token and get expiration time const jwtPayload = jwtDecode(token); + // Get user Policies + const userPolicies = await WzAuthentication.getUserPolicies(); + //Get allowed agents for the current user - const allowedAgents = await getAuthorizedAgents(); + let allowedAgents: any = []; + if (WzAuthentication.userHasAgentsPermissions(userPolicies)) { + allowedAgents = await getAuthorizedAgents(); + allowedAgents = allowedAgents.length ? allowedAgents : ['-1']; // users without read:agent police should not view info about any agent + } store.dispatch(updateAllowedAgents(allowedAgents)); - // Get user Policies - const userPolicies = await WzAuthentication.getUserPolicies(); + // Dispatch actions to set permissions and roles store.dispatch(updateUserPermissions(userPolicies)); store.dispatch(updateUserRoles(WzAuthentication.mapUserRolesIDToAdministratorRole(jwtPayload.rbac_roles || []))); @@ -98,4 +104,23 @@ export class WzAuthentication{ throw error; } } + + /** + * This function returns true only if the user has some police that need be filtered. + * Returns false if the user has permission for all agents. + * Returns true if the user has no one police for agent:read. + * @param policies + * @returns boolean + */ + static userHasAgentsPermissions(policies) { + const agentReadPolicies = policies['agent:read']; + if (agentReadPolicies) { + const allIds = agentReadPolicies['agent:id:*'] == 'allow'; + const allGroups = agentReadPolicies['agent:group:*'] == 'allow'; + const denyAgents = Object.keys(agentReadPolicies).filter(k => !k.includes('*') && agentReadPolicies[k] == 'deny').length; + return !((allIds || allGroups) && !denyAgents); + } + // users without read:agent police should not view info about any agent + return true; + } } From dcda72fcd909d8e97819263e66089e3df7f26176 Mon Sep 17 00:00:00 2001 From: Franco Charriol Date: Thu, 8 Apr 2021 20:07:38 -0300 Subject: [PATCH 13/14] Filtered agents filter from KbnSearchBar --- .../common/modules/discover/discover.tsx | 9 ++++----- .../components/kbn-search-bar/kbn-search-bar.tsx | 16 ++++++++++++++-- .../wz-search-bar/wz-search-badges.tsx | 2 +- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/public/components/common/modules/discover/discover.tsx b/public/components/common/modules/discover/discover.tsx index de931bdd99..84eb2cd075 100644 --- a/public/components/common/modules/discover/discover.tsx +++ b/public/components/common/modules/discover/discover.tsx @@ -99,7 +99,7 @@ export const Discover = compose( includeFilters?: string, initialColumns: string[], shareFilterManager: FilterManager, - shareFilterManagerWithUserAuthorized: FilterManager, + shareFilterManagerWithUserAuthorized: Filter[], refreshAngularDiscover?: number } constructor(props) { @@ -288,14 +288,13 @@ export const Discover = compose( $state: { store: 'appState' } }); - const filters = this.props.shareFilterManager ? this.props.shareFilterManager.filters : []; - const previousFilters = this.KibanaServices && this.KibanaServices.query.filterManager.filters ? this.KibanaServices.query.filterManager.filters : []; - const CustomFilterWith = this.props.shareFilterManagerWithUserAuthorized; + const filters = this.props.shareFilterManager ? this.props.shareFilterManager.getFilters() : []; + const previousFilters = this.KibanaServices && this.KibanaServices.query.filterManager.getFilters() || []; const elasticQuery = buildEsQuery( undefined, query, - [...previousFilters, ...filters, ...extraFilters, ...CustomFilterWith], + _.union(previousFilters, filters,extraFilters, this.props.shareFilterManagerWithUserAuthorized || []), getEsQueryConfig(getUiSettings()) ); diff --git a/public/components/kbn-search-bar/kbn-search-bar.tsx b/public/components/kbn-search-bar/kbn-search-bar.tsx index 13e69711f8..7eb9fb1933 100644 --- a/public/components/kbn-search-bar/kbn-search-bar.tsx +++ b/public/components/kbn-search-bar/kbn-search-bar.tsx @@ -18,6 +18,7 @@ import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/p import { withKibanaContext, withKibanaContextExtendsProps } from '../common/hocs'; import { storage } from './lib'; import { getDataPlugin, getCore } from '../../kibana-services'; +import { AUTHORIZED_AGENTS } from '../../../common/constants'; export interface IKbnSearchBarProps { appName?: string; @@ -37,6 +38,12 @@ const KbnSearchBar: React.FunctionComponent = ( ...KibanaServices, query: { ...KibanaServices.query, filterManager }, }; + + const getFilters = () => { + const filters = filterManager ? filterManager.getFilters() : []; + return filters.filter(filter => filter.meta.controlledBy !== AUTHORIZED_AGENTS); + } + return ( = ( onQuerySubmit(payload, props)} @@ -75,7 +82,12 @@ const onQuerySubmit = (payload, props) => { const onFiltersUpdate = (filters, props) => { const { filterManager, onFiltersUpdated } = props; - filterManager.setFilters(filters); + + //add agents filters + const currentFilters = filterManager ? filterManager.getFilters() : []; + const agentsFilters = currentFilters.filter(filter => filter.meta.controlledBy === AUTHORIZED_AGENTS); + + filterManager.setFilters([...filters, ...agentsFilters]); onFiltersUpdated && onFiltersUpdated(filters); }; diff --git a/public/components/wz-search-bar/wz-search-badges.tsx b/public/components/wz-search-bar/wz-search-badges.tsx index 95f1c745ee..3699905ff6 100644 --- a/public/components/wz-search-bar/wz-search-badges.tsx +++ b/public/components/wz-search-bar/wz-search-badges.tsx @@ -72,7 +72,7 @@ export class WzSearchBadges extends Component { color="hollow" iconOnClick={() => this.onDeleteFilter(filter)}> - {`${filter.field}: ${filter.value}`} + {`${filter.field}: ${filter.value} puto`} From 4a7da1f4077edeee653e61cedf437e9eb48841c2 Mon Sep 17 00:00:00 2001 From: Franco Charriol Date: Fri, 9 Apr 2021 12:27:03 -0300 Subject: [PATCH 14/14] Resolved CR comments --- public/react-services/wz-authentication.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/react-services/wz-authentication.ts b/public/react-services/wz-authentication.ts index c366ea69b8..549753d5b2 100644 --- a/public/react-services/wz-authentication.ts +++ b/public/react-services/wz-authentication.ts @@ -117,7 +117,7 @@ export class WzAuthentication{ if (agentReadPolicies) { const allIds = agentReadPolicies['agent:id:*'] == 'allow'; const allGroups = agentReadPolicies['agent:group:*'] == 'allow'; - const denyAgents = Object.keys(agentReadPolicies).filter(k => !k.includes('*') && agentReadPolicies[k] == 'deny').length; + const denyAgents = Object.keys(agentReadPolicies).some(k => !k.includes('*') && agentReadPolicies[k] == 'deny'); return !((allIds || allGroups) && !denyAgents); } // users without read:agent police should not view info about any agent