[Osquery] Fix more bugs (#143370)

This commit is contained in:
Tomasz Ciecierski 2022-11-03 16:59:33 +01:00 committed by GitHub
parent aa31c35f43
commit fc62b82013
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 228 additions and 66 deletions

View file

@ -17,7 +17,7 @@ import { ArchiverMethod, runKbnArchiverScript } from '../../tasks/archiver';
import { preparePack } from '../../tasks/packs';
import { addIntegration, closeModalIfVisible } from '../../tasks/integrations';
import { DEFAULT_POLICY } from '../../screens/fleet';
import { getSavedQueriesDropdown } from '../../screens/live_query';
import { getIdFormField, getSavedQueriesDropdown } from '../../screens/live_query';
import { ROLES } from '../../test';
import { getRandomInt } from '../../tasks/helpers';
@ -47,6 +47,104 @@ describe('ALL - Packs', () => {
runKbnArchiverScript(ArchiverMethod.UNLOAD, 'ecs_mapping_3');
});
it('Check if result type is correct', () => {
cy.contains('Packs').click();
findAndClickButton('Add pack');
findFormFieldByRowsLabelAndType('Name', 'ResultType');
findAndClickButton('Add query');
cy.contains('Attach next query');
getIdFormField().type('Query1');
inputQuery('select * from uptime;');
cy.wait(500); // wait for the validation to trigger - cypress is way faster than users ;)
cy.react('EuiFlyoutFooter').react('EuiButton').contains('Save').click();
findAndClickButton('Add query');
cy.contains('Attach next query');
getIdFormField().type('Query2');
inputQuery('select * from uptime;');
cy.getBySel('resultsTypeField').click();
cy.contains('Differential').click();
cy.wait(500); // wait for the validation to trigger - cypress is way faster than users ;)
cy.react('EuiFlyoutFooter').react('EuiButton').contains('Save').click();
findAndClickButton('Add query');
cy.contains('Attach next query');
getIdFormField().type('Query3');
inputQuery('select * from uptime;');
cy.getBySel('resultsTypeField').click();
cy.contains('Differential (Ignore removals)').click();
cy.wait(500); // wait for the validation to trigger - cypress is way faster than users ;)
cy.react('EuiFlyoutFooter').react('EuiButton').contains('Save').click();
findAndClickButton('Save pack');
cy.react('ScheduledQueryNameComponent', {
props: {
name: 'ResultType',
},
}).click();
findAndClickButton('Edit');
cy.contains('Query1');
cy.contains('Query2');
cy.contains('Query3');
cy.react('CustomItemAction', {
props: { index: 0, item: { id: 'Query1' } },
}).click();
cy.getBySel('resultsTypeField').contains('Snapshot').click();
cy.contains('Differential').click();
cy.react('EuiFlyoutFooter').react('EuiButton').contains('Save').click();
cy.react('CustomItemAction', {
props: { index: 0, item: { id: 'Query2' } },
}).click();
cy.getBySel('resultsTypeField').contains('Differential').click();
cy.contains('Differential (Ignore removals)').click();
cy.react('EuiFlyoutFooter').react('EuiButton').contains('Save').click();
cy.react('CustomItemAction', {
props: { index: 0, item: { id: 'Query3' } },
}).click();
cy.getBySel('resultsTypeField').contains('(Ignore removals)').click();
cy.contains('Snapshot').click();
cy.react('EuiFlyoutFooter').react('EuiButton').contains('Save').click();
findFormFieldByRowsLabelAndType(
'Scheduled agent policies (optional)',
'fleet server {downArrow} {enter}'
);
findAndClickButton('Update pack');
closeModalIfVisible();
cy.contains(
'Create packs to organize sets of queries and to schedule queries for agent policies.'
);
const queries = {
Query1: {
interval: 3600,
query: 'select * from uptime;',
removed: true,
snapshot: false,
},
Query2: {
interval: 3600,
query: 'select * from uptime;',
removed: false,
snapshot: false,
},
Query3: {
interval: 3600,
query: 'select * from uptime;',
},
};
cy.request('/internal/osquery/fleet_wrapper/package_policies').then((response) => {
const item = response.body.items.find(
(policy: { policy_id: string }) => policy.policy_id === 'fleet-server-policy'
);
expect(item.inputs[0].config.osquery.value.packs.ResultType.queries).to.deep.equal(queries);
});
});
it('should add a pack from a saved query', () => {
cy.contains('Packs').click();
findAndClickButton('Add pack');

View file

@ -17,3 +17,8 @@ export const getSavedQueriesDropdown = () =>
cy.react('EuiComboBox', {
props: { placeholder: 'Search for a query to run, or write a new query below' },
});
export const getIdFormField = () =>
cy.react('EuiFormRow', {
props: { label: 'ID' },
});

View file

@ -14,7 +14,7 @@ import {
EuiFlexItem,
EuiText,
} from '@elastic/eui';
import { useController } from 'react-hook-form';
import { useController, useFormState } from 'react-hook-form';
import { FormattedMessage } from '@kbn/i18n-react';
import deepEqual from 'fast-deep-equal';
import { i18n } from '@kbn/i18n';
@ -57,18 +57,20 @@ interface ResultsTypeFieldProps {
const ResultsTypeFieldComponent: React.FC<ResultsTypeFieldProps> = ({ euiFieldProps = {} }) => {
const [selectedOption, setSelectedOption] = useState(SNAPSHOT_OPTION.value);
const { defaultValues } = useFormState();
const {
field: { onChange: onSnapshotChange, value: snapshotValue },
} = useController({
name: 'snapshot',
defaultValue: true,
defaultValue: defaultValues?.snapshot,
});
const {
field: { onChange: onRemovedChange, value: removedValue },
} = useController({
name: 'removed',
defaultValue: false,
defaultValue: defaultValues?.removed,
});
const handleChange = useCallback(
@ -142,6 +144,7 @@ const ResultsTypeFieldComponent: React.FC<ResultsTypeFieldProps> = ({ euiFieldPr
fullWidth
>
<EuiSuperSelect
data-test-subj={'resultsTypeField'}
options={FIELD_OPTIONS}
fullWidth
valueOfSelected={selectedOption}

View file

@ -39,6 +39,7 @@ const ViewResultsInLensActionComponent: React.FC<ViewResultsInLensActionProps> =
mode,
}) => {
const lensService = useKibana().services.lens;
const isLensAvailable = lensService?.canUseEditor();
const { data: logsDataView } = useLogsDataView({ skip: !actionId, checkOnly: true });
const handleClick = useCallback(
@ -68,6 +69,10 @@ const ViewResultsInLensActionComponent: React.FC<ViewResultsInLensActionProps> =
const isDisabled = useMemo(() => !actionId || !logsDataView, [actionId, logsDataView]);
if (!isLensAvailable) {
return null;
}
if (buttonType === ViewResultsActionButtonType.button) {
return (
<EuiButtonEmpty size="xs" iconType="lensApp" onClick={handleClick} isDisabled={isDisabled}>

View file

@ -101,8 +101,7 @@ const LiveQueryQueryFieldComponent: React.FC<LiveQueryQueryFieldProps> = ({
() =>
!(
permissions.writeLiveQueries ||
permissions.runSavedQueries ||
permissions.readSavedQueries
(permissions.runSavedQueries && permissions.readSavedQueries)
),
[permissions.readSavedQueries, permissions.runSavedQueries, permissions.writeLiveQueries]
);

View file

@ -49,6 +49,17 @@ const EMPTY_ARRAY: PackQueryStatusItem[] = [];
const StyledEuiBasicTable = styled(EuiBasicTable)`
.euiTableRow.euiTableRow-isExpandedRow > td > div {
padding: 0;
border: 1px solid #d3dae6;
}
div.euiDataGrid__virtualized::-webkit-scrollbar {
display: none;
}
.euiDataGrid > div {
.euiDataGrid__scrollOverlay {
box-shadow: none;
}
border-left: 0px;
border-right: 0px;
}
`;

View file

@ -97,12 +97,8 @@ const QueriesFieldComponent: React.FC<QueriesFieldProps> = ({ euiFieldProps }) =
draft.ecs_mapping = updatedQuery.ecs_mapping;
}
if (updatedQuery.snapshot === false) {
draft.snapshot = updatedQuery.snapshot;
if (updatedQuery.removed !== undefined) {
draft.removed = updatedQuery.removed;
}
}
draft.snapshot = updatedQuery.snapshot;
draft.removed = updatedQuery.removed;
return draft;
})

View file

@ -588,10 +588,10 @@ const OsqueryColumnFieldComponent: React.FC<OsqueryColumnFieldProps> = ({
rowHeight={32}
isClearable
singleSelection={isSingleSelection ? SINGLE_SELECTION : false}
options={(resultTypeField.value === 'field' && euiFieldProps.options) || EMPTY_ARRAY}
idAria={idAria}
helpText={selectedOptions[0]?.value?.description}
{...euiFieldProps}
options={(resultTypeField.value === 'field' && euiFieldProps.options) || EMPTY_ARRAY}
/>
</EuiFlexItem>
</EuiFlexGroup>

View file

@ -28,6 +28,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
import React, { createContext, useEffect, useState, useCallback, useContext, useMemo } from 'react';
import type { ECSMapping } from '@kbn/osquery-io-ts-types';
import { pagePathGetters } from '@kbn/fleet-plugin/public';
import styled from 'styled-components';
import { AddToTimelineButton } from '../timelines/add_to_timeline_button';
import { useAllResults } from './use_all_results';
import type { ResultEdges } from '../../common/search_strategy';
@ -46,6 +47,10 @@ import { AddToCaseWrapper } from '../cases/add_to_cases';
const DataContext = createContext<ResultEdges>([]);
const StyledEuiDataGrid = styled(EuiDataGrid)`
max-height: 500px;
`;
export interface ResultsTableComponentProps {
actionId: string;
selectedAgent?: string;
@ -419,7 +424,7 @@ const ResultsTableComponent: React.FC<ResultsTableComponentProps> = ({
</EuiPanel>
) : (
<DataContext.Provider value={allResultsData?.edges}>
<EuiDataGrid
<StyledEuiDataGrid
data-test-subj="osqueryResultsTable"
aria-label="Osquery results"
columns={columns}
@ -429,7 +434,6 @@ const ResultsTableComponent: React.FC<ResultsTableComponentProps> = ({
leadingControlColumns={leadingControlColumns}
sorting={tableSorting}
pagination={tablePagination}
height="500px"
toolbarVisibility={toolbarVisibility}
/>
</DataContext.Provider>

View file

@ -9,9 +9,16 @@ import { EuiTabbedContent, EuiNotificationBadge } from '@elastic/eui';
import React, { useMemo } from 'react';
import type { ECSMapping } from '@kbn/osquery-io-ts-types';
import styled from 'styled-components';
import { ResultsTable } from '../../../results/results_table';
import { ActionResultsSummary } from '../../../action_results/action_results_summary';
const StyledEuiTabbedContent = styled(EuiTabbedContent)`
div.euiTabs {
padding-left: 8px;
}
`;
interface ResultTabsProps {
actionId: string;
agentIds?: string[];
@ -64,7 +71,7 @@ const ResultTabsComponent: React.FC<ResultTabsProps> = ({
);
return (
<EuiTabbedContent
<StyledEuiTabbedContent
// TODO: extend the EuiTabbedContent component to support EuiTabs props
// bottomBorder={false}
tabs={tabs}

View file

@ -28,7 +28,7 @@ export const AddToTimelineButton = (props: AddToTimelineButtonProps) => {
const queryIds = isArray(value) ? value : [value];
const TimelineIconComponent = useCallback(
(timelineComponentProps) => (
<EuiButtonIcon iconType={'timelines'} {...timelineComponentProps} size="xs" {...iconProps} />
<EuiButtonIcon iconType="timelines" {...timelineComponentProps} size="xs" {...iconProps} />
),
[iconProps]
);

View file

@ -19,7 +19,7 @@ import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
import { OSQUERY_INTEGRATION_NAME } from '../../../common';
import { PLUGIN_ID } from '../../../common';
import { packSavedObjectType } from '../../../common/types';
import { convertPackQueriesToSO, convertSOQueriesToPack } from './utils';
import { convertPackQueriesToSO, convertSOQueriesToPackConfig } from './utils';
import { getInternalSavedObjectsClient } from '../utils';
import type { PackSavedObjectAttributes } from '../../common/types';
@ -144,10 +144,7 @@ export const createPackRoute = (router: IRouter, osqueryContext: OsqueryAppConte
}
set(draft, `inputs[0].config.osquery.value.packs.${packSO.attributes.name}`, {
queries: convertSOQueriesToPack(queries, {
removeMultiLines: true,
removeResultType: true,
}),
queries: convertSOQueriesToPackConfig(queries),
});
return draft;

View file

@ -20,7 +20,11 @@ import { OSQUERY_INTEGRATION_NAME } from '../../../common';
import { packSavedObjectType } from '../../../common/types';
import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
import { PLUGIN_ID } from '../../../common';
import { convertSOQueriesToPack, convertPackQueriesToSO } from './utils';
import {
convertSOQueriesToPack,
convertPackQueriesToSO,
convertSOQueriesToPackConfig,
} from './utils';
import { getInternalSavedObjectsClient } from '../utils';
import type { PackSavedObjectAttributes } from '../../common/types';
@ -283,10 +287,7 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte
draft,
`inputs[0].config.osquery.value.packs.${updatedPackSO.attributes.name}`,
{
queries: convertSOQueriesToPack(updatedPackSO.attributes.queries, {
removeMultiLines: true,
removeResultType: true,
}),
queries: convertSOQueriesToPackConfig(updatedPackSO.attributes.queries),
}
);
@ -316,9 +317,7 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte
draft,
`inputs[0].config.osquery.value.packs.${updatedPackSO.attributes.name}`,
{
queries: convertSOQueriesToPack(updatedPackSO.attributes.queries, {
removeResultType: true,
}),
queries: convertSOQueriesToPackConfig(updatedPackSO.attributes.queries),
}
);

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { convertSOQueriesToPack } from './utils';
import { convertSOQueriesToPack, convertSOQueriesToPackConfig } from './utils';
const getTestQueries = (additionalFields?: Record<string, unknown>, packName = 'default') => ({
[packName]: {
@ -31,12 +31,13 @@ const getTestQueries = (additionalFields?: Record<string, unknown>, packName = '
},
});
const oneLiner = {
const getOneLiner = (additionParams: Record<string, unknown>) => ({
default: {
interval: 3600,
query: `select u.username, p.pid, p.name, pos.local_address, pos.local_port, p.path, p.cmdline, pos.remote_address, pos.remote_port from processes as p join users as u on u.uid=p.uid join process_open_sockets as pos on pos.pid=p.pid where pos.remote_port !='0' limit 1000;`,
...additionParams,
},
};
});
describe('Pack utils', () => {
describe('convertSOQueriesToPack', () => {
@ -44,43 +45,59 @@ describe('Pack utils', () => {
const convertedQueries = convertSOQueriesToPack(getTestQueries());
expect(convertedQueries).toStrictEqual(getTestQueries());
});
test('converts to pack with converting query to single line', () => {
const convertedQueries = convertSOQueriesToPack(getTestQueries(), { removeMultiLines: true });
expect(convertedQueries).toStrictEqual({
...oneLiner,
});
});
test('converts to object with pack names after query.id', () => {
const convertedQueries = convertSOQueriesToPack(getTestQueries({ id: 'testId' }));
expect(convertedQueries).toStrictEqual(getTestQueries({}, 'testId'));
});
test('converts with results snapshot set false', () => {
test('converts with results snapshot set true and removed true', () => {
const convertedQueries = convertSOQueriesToPack(
getTestQueries({ snapshot: false, removed: true }),
{ removeResultType: true }
getTestQueries({ snapshot: true, removed: true })
);
expect(convertedQueries).toStrictEqual(getTestQueries({ removed: true, snapshot: false }));
});
test('converts with results snapshot set true and removed false', () => {
const convertedQueries = convertSOQueriesToPack(
getTestQueries({ snapshot: true, removed: true }),
{ removeResultType: true }
);
expect(convertedQueries).toStrictEqual(getTestQueries({}));
expect(convertedQueries).toStrictEqual(getTestQueries({ snapshot: true, removed: true }));
});
test('converts with results snapshot set true but removed false', () => {
const convertedQueries = convertSOQueriesToPack(
getTestQueries({ snapshot: true, removed: false }),
{ removeResultType: true }
getTestQueries({ snapshot: true, removed: false })
);
expect(convertedQueries).toStrictEqual(getTestQueries({}));
expect(convertedQueries).toStrictEqual(getTestQueries({ snapshot: true, removed: false }));
});
test('converts with both results set to false', () => {
const convertedQueries = convertSOQueriesToPack(
getTestQueries({ snapshot: false, removed: false }),
{ removeResultType: true }
getTestQueries({ snapshot: false, removed: false })
);
expect(convertedQueries).toStrictEqual(getTestQueries({ removed: false, snapshot: false }));
});
});
describe('convertSOQueriesToPackConfig', () => {
test('converts to pack with converting query to single line', () => {
const convertedQueries = convertSOQueriesToPackConfig(getTestQueries());
expect(convertedQueries).toStrictEqual(getOneLiner({}));
});
test('if snapshot true and removed true - return empty {}', () => {
const convertedQueries = convertSOQueriesToPackConfig(
getTestQueries({ snapshot: true, removed: true })
);
expect(convertedQueries).toStrictEqual(getOneLiner({}));
});
test('if snapshot true and removed false - return empty {}', () => {
const convertedQueries = convertSOQueriesToPackConfig(
getTestQueries({ snapshot: true, removed: false })
);
expect(convertedQueries).toStrictEqual(getOneLiner({}));
});
test('converts with results snapshot set false', () => {
const convertedQueries = convertSOQueriesToPackConfig(
getTestQueries({ snapshot: false, removed: true })
);
expect(convertedQueries).toStrictEqual(getOneLiner({ snapshot: false, removed: true }));
});
test('converts with both results set to false', () => {
const convertedQueries = convertSOQueriesToPackConfig(
getTestQueries({ snapshot: false, removed: false })
);
expect(convertedQueries).toStrictEqual(getOneLiner({ removed: false, snapshot: false }));
});
});
});

View file

@ -18,9 +18,7 @@ export const convertPackQueriesToSO = (queries) =>
const ecsMapping = value.ecs_mapping && convertECSMappingToArray(value.ecs_mapping);
acc.push({
id: key,
...pick(value, ['name', 'query', 'interval', 'platform', 'version']),
...(value.snapshot !== undefined ? { snapshot: value.snapshot } : {}),
...(value.removed !== undefined ? { removed: value.removed } : {}),
...pick(value, ['name', 'query', 'interval', 'platform', 'version', 'snapshot', 'removed']),
...(ecsMapping ? { ecs_mapping: ecsMapping } : {}),
});
@ -39,27 +37,50 @@ export const convertPackQueriesToSO = (queries) =>
export const convertSOQueriesToPack = (
// @ts-expect-error update types
queries,
options?: { removeMultiLines?: boolean; removeResultType?: boolean }
queries
) =>
reduce(
queries,
// eslint-disable-next-line @typescript-eslint/naming-convention
(acc, { id: queryId, ecs_mapping, query, platform, removed, snapshot, ...rest }, key) => {
const resultType = !snapshot ? { removed, snapshot } : {};
(acc, { id: queryId, ecs_mapping, query, platform, ...rest }, key) => {
const index = queryId ? queryId : key;
acc[index] = {
...rest,
query: options?.removeMultiLines ? removeMultilines(query) : query,
query,
...(!isEmpty(ecs_mapping)
? isArray(ecs_mapping)
? { ecs_mapping: convertECSMappingToObject(ecs_mapping) }
: { ecs_mapping }
: {}),
...(platform === DEFAULT_PLATFORM || platform === undefined ? {} : { platform }),
...(options?.removeResultType
? resultType
: { ...(snapshot ? { snapshot } : {}), ...(removed ? { removed } : {}) }),
};
return acc;
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
{} as Record<string, any>
);
export const convertSOQueriesToPackConfig = (
// @ts-expect-error update types
queries
) =>
reduce(
queries,
// eslint-disable-next-line @typescript-eslint/naming-convention
(acc, { id: queryId, ecs_mapping, query, platform, removed, snapshot, ...rest }, key) => {
const resultType = snapshot === false ? { removed, snapshot } : {};
const index = queryId ? queryId : key;
acc[index] = {
...rest,
query: removeMultilines(query),
...(!isEmpty(ecs_mapping)
? isArray(ecs_mapping)
? { ecs_mapping: convertECSMappingToObject(ecs_mapping) }
: { ecs_mapping }
: {}),
...(platform === DEFAULT_PLATFORM || platform === undefined ? {} : { platform }),
...resultType,
};
return acc;