Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.x] [Osquery] 7.14 bug squash (#105387) #106227

Merged
merged 1 commit into from
Jul 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,18 @@ const ActionResultsSummaryComponent: React.FC<ActionResultsSummaryProps> = ({
sortField: '@timestamp',
isLive,
});
if (expired) {
// @ts-expect-error update types
edges.forEach((edge) => {
if (!edge.fields.completed_at) {
edge.fields['error.keyword'] = edge.fields.error = [
i18n.translate('xpack.osquery.liveQueryActionResults.table.expiredErrorText', {
defaultMessage: 'The action request timed out.',
}),
];
}
});
}

const { data: logsResults } = useAllResults({
actionId,
Expand Down
3 changes: 1 addition & 2 deletions x-pack/plugins/osquery/public/agents/use_all_agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ export const useAllAgents = (
const { isLoading: agentsLoading, data: agentData } = useQuery<GetAgentsResponse>(
['agents', osqueryPolicies, searchValue, perPage],
() => {
const policyFragment = osqueryPolicies.map((p) => `policy_id:${p}`).join(' or ');
let kuery = `last_checkin_status: online and (${policyFragment})`;
let kuery = `${osqueryPolicies.map((p) => `policy_id:${p}`).join(' or ')}`;

if (searchValue) {
kuery += ` and (local_metadata.host.hostname:*${searchValue}* or local_metadata.elastic.agent.id:*${searchValue}*)`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ const SavedQueriesPageComponent = () => {
const { push } = useHistory();
const newQueryLinkProps = useRouterNavigate('saved_queries/new');
const [pageIndex, setPageIndex] = useState(0);
const [pageSize, setPageSize] = useState(10);
const [sortField, setSortField] = useState('updated_at');
const [pageSize, setPageSize] = useState(20);
const [sortField, setSortField] = useState('attributes.updated_at');
const [sortDirection, setSortDirection] = useState('desc');

const { data } = useSavedQueries({ isLive: true });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import { isArray } from 'lodash';
import uuid from 'uuid';
import { produce } from 'immer';

import { useMemo } from 'react';
import { useForm } from '../../shared_imports';
import { formSchema } from '../../scheduled_query_groups/queries/schema';
import { createFormSchema } from '../../scheduled_query_groups/queries/schema';
import { ScheduledQueryGroupFormData } from '../../scheduled_query_groups/queries/use_scheduled_query_group_query_form';
import { useSavedQueries } from '../use_saved_queries';

const SAVED_QUERY_FORM_ID = 'savedQueryForm';

Expand All @@ -20,11 +22,29 @@ interface UseSavedQueryFormProps {
handleSubmit: (payload: unknown) => Promise<void>;
}

export const useSavedQueryForm = ({ defaultValue, handleSubmit }: UseSavedQueryFormProps) =>
useForm({
export const useSavedQueryForm = ({ defaultValue, handleSubmit }: UseSavedQueryFormProps) => {
const { data } = useSavedQueries({});
const ids: string[] = useMemo<string[]>(
() => data?.savedObjects.map((obj) => obj.attributes.id) ?? [],
[data]
);
const idSet = useMemo<Set<string>>(() => {
const res = new Set<string>(ids);
// @ts-expect-error update types
if (defaultValue && defaultValue.id) res.delete(defaultValue.id);
return res;
}, [ids, defaultValue]);
const formSchema = useMemo<ReturnType<typeof createFormSchema>>(() => createFormSchema(idSet), [
idSet,
]);
return useForm({
id: SAVED_QUERY_FORM_ID + uuid.v4(),
schema: formSchema,
onSubmit: handleSubmit,
onSubmit: async (formData, isValid) => {
if (isValid) {
return handleSubmit(formData);
}
},
options: {
stripEmptyFields: false,
},
Expand Down Expand Up @@ -62,3 +82,4 @@ export const useSavedQueryForm = ({ defaultValue, handleSubmit }: UseSavedQueryF
};
},
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ export const useCreateSavedQuery = ({ withRedirect }: UseCreateSavedQueryProps)
throw new Error('CurrentUser is missing');
}

const conflictingEntries = await savedObjects.client.find({
type: savedQuerySavedObjectType,
// @ts-expect-error update types
search: payload.id,
searchFields: ['id'],
});
if (conflictingEntries.savedObjects.length) {
// @ts-expect-error update types
throw new Error(`Saved query with id ${payload.id} already exists.`);
}
return savedObjects.client.create(savedQuerySavedObjectType, {
// @ts-expect-error update types
...payload,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ export const useUpdateSavedQuery = ({ savedQueryId }: UseUpdateSavedQueryProps)
throw new Error('CurrentUser is missing');
}

const conflictingEntries = await savedObjects.client.find({
type: savedQuerySavedObjectType,
// @ts-expect-error update types
search: payload.id,
searchFields: ['id'],
});
if (conflictingEntries.savedObjects.length) {
// @ts-expect-error update types
throw new Error(`Saved query with id ${payload.id} already exists.`);
}

return savedObjects.client.update(savedQuerySavedObjectType, savedQueryId, {
// @ts-expect-error update types
...payload,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,20 @@ const QueriesFieldComponent: React.FC<QueriesFieldProps> = ({
field.value,
]);

const uniqueQueryIds = useMemo<string[]>(
() =>
field.value && field.value[0].streams.length
? field.value[0].streams.reduce((acc, stream) => {
if (stream.vars?.id.value) {
acc.push(stream.vars?.id.value);
}

return acc;
}, [] as string[])
: [],
[field.value]
);

return (
<>
<EuiFlexGroup justifyContent="flexEnd">
Expand Down Expand Up @@ -256,13 +270,15 @@ const QueriesFieldComponent: React.FC<QueriesFieldProps> = ({
{<OsqueryPackUploader onChange={handlePackUpload} />}
{showAddQueryFlyout && (
<QueryFlyout
uniqueQueryIds={uniqueQueryIds}
integrationPackageVersion={integrationPackageVersion}
onSave={handleAddQuery}
onClose={handleHideAddFlyout}
/>
)}
{showEditQueryFlyout != null && showEditQueryFlyout >= 0 && (
<QueryFlyout
uniqueQueryIds={uniqueQueryIds}
defaultValue={field.value[0].streams[showEditQueryFlyout]?.vars}
integrationPackageVersion={integrationPackageVersion}
onSave={handleEditQuery}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,23 @@ import { SavedQueriesDropdown } from '../../saved_queries/saved_queries_dropdown
const CommonUseField = getUseField({ component: Field });

interface QueryFlyoutProps {
uniqueQueryIds: string[];
defaultValue?: UseScheduledQueryGroupQueryFormProps['defaultValue'] | undefined;
integrationPackageVersion?: string | undefined;
onSave: (payload: OsqueryManagerPackagePolicyConfigRecord) => Promise<void>;
onClose: () => void;
}

const QueryFlyoutComponent: React.FC<QueryFlyoutProps> = ({
uniqueQueryIds,
defaultValue,
integrationPackageVersion,
onSave,
onClose,
}) => {
const [isEditMode] = useState(!!defaultValue);
const { form } = useScheduledQueryGroupQueryForm({
uniqueQueryIds,
defaultValue,
handleSubmit: (payload, isValid) =>
new Promise((resolve) => {
Expand All @@ -65,7 +68,7 @@ const QueryFlyoutComponent: React.FC<QueryFlyoutProps> = ({
}),
});

/* Platform and version fields are supported since osquer_manger@0.3.0 */
/* Platform and version fields are supported since osquery_manager@0.3.0 */
const isFieldSupported = useMemo(
() => (integrationPackageVersion ? satisfies(integrationPackageVersion, '>=0.3.0') : false),
[integrationPackageVersion]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,19 @@ import { FormattedMessage } from '@kbn/i18n/react';

import { FIELD_TYPES } from '../../shared_imports';

import { idFieldValidations, intervalFieldValidation, queryFieldValidation } from './validations';
import {
createIdFieldValidations,
intervalFieldValidation,
queryFieldValidation,
} from './validations';

export const formSchema = {
export const createFormSchema = (ids: Set<string>) => ({
id: {
type: FIELD_TYPES.TEXT,
label: i18n.translate('xpack.osquery.scheduledQueryGroup.queryFlyoutForm.idFieldLabel', {
defaultMessage: 'ID',
}),
validations: idFieldValidations.map((validator) => ({ validator })),
validations: createIdFieldValidations(ids).map((validator) => ({ validator })),
},
description: {
type: FIELD_TYPES.TEXT,
Expand Down Expand Up @@ -69,4 +73,4 @@ export const formSchema = {
) as unknown) as string,
validations: [],
},
};
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@
* 2.0.
*/

import { isArray } from 'lodash';
import { isArray, xor } from 'lodash';
import uuid from 'uuid';
import { produce } from 'immer';

import { useMemo } from 'react';
import { FormConfig, useForm } from '../../shared_imports';
import { OsqueryManagerPackagePolicyConfigRecord } from '../../../common/types';
import { formSchema } from './schema';
import { createFormSchema } from './schema';

const FORM_ID = 'editQueryFlyoutForm';

export interface UseScheduledQueryGroupQueryFormProps {
uniqueQueryIds: string[];
defaultValue?: OsqueryManagerPackagePolicyConfigRecord | undefined;
handleSubmit: FormConfig<
OsqueryManagerPackagePolicyConfigRecord,
Expand All @@ -32,12 +34,26 @@ export interface ScheduledQueryGroupFormData {
}

export const useScheduledQueryGroupQueryForm = ({
uniqueQueryIds,
defaultValue,
handleSubmit,
}: UseScheduledQueryGroupQueryFormProps) =>
useForm<OsqueryManagerPackagePolicyConfigRecord, ScheduledQueryGroupFormData>({
}: UseScheduledQueryGroupQueryFormProps) => {
const idSet = useMemo<Set<string>>(
() =>
new Set<string>(xor(uniqueQueryIds, defaultValue?.id.value ? [defaultValue.id.value] : [])),
[uniqueQueryIds, defaultValue]
);
const formSchema = useMemo<ReturnType<typeof createFormSchema>>(() => createFormSchema(idSet), [
idSet,
]);

return useForm<OsqueryManagerPackagePolicyConfigRecord, ScheduledQueryGroupFormData>({
id: FORM_ID + uuid.v4(),
onSubmit: handleSubmit,
onSubmit: async (formData, isValid) => {
if (isValid && handleSubmit) {
return handleSubmit(formData, isValid);
}
},
options: {
stripEmptyFields: false,
},
Expand Down Expand Up @@ -75,3 +91,4 @@ export const useScheduledQueryGroupQueryForm = ({
},
schema: formSchema,
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,28 @@ const idSchemaValidation: ValidationFunc<any, string, string> = ({ value }) => {
}
};

export const idFieldValidations = [
const createUniqueIdValidation = (ids: Set<string>) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const uniqueIdCheck: ValidationFunc<any, string, string> = ({ value }) => {
if (ids.has(value)) {
return {
message: i18n.translate('xpack.osquery.scheduledQueryGroup.queryFlyoutForm.uniqueIdError', {
defaultMessage: 'ID must be unique',
}),
};
}
};
return uniqueIdCheck;
};

export const createIdFieldValidations = (ids: Set<string>) => [
fieldValidators.emptyField(
i18n.translate('xpack.osquery.scheduledQueryGroup.queryFlyoutForm.emptyIdError', {
defaultMessage: 'ID is required',
})
),
idSchemaValidation,
createUniqueIdValidation(ids),
];

export const intervalFieldValidation: ValidationFunc<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const useScheduledQueryGroup = ({
() => http.get(packagePolicyRouteService.getInfoPath(scheduledQueryGroupId)),
{
keepPreviousData: true,
enabled: !skip,
enabled: !skip || !scheduledQueryGroupId,
select: (response) => response.item,
}
);
Expand Down
23 changes: 7 additions & 16 deletions x-pack/plugins/osquery/server/routes/usage/recorder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { usageMetricSavedObjectType } from '../../../common/types';

import {
CounterValue,
createMetricObjects,
getOrCreateMetricObject,
getRouteMetric,
incrementCount,
RouteString,
Expand Down Expand Up @@ -45,31 +45,22 @@ describe('Usage metric recorder', () => {
get.mockClear();
create.mockClear();
});
it('should seed route metrics objects', async () => {
it('should create metrics that do not exist', async () => {
get.mockRejectedValueOnce('stub value');
create.mockReturnValueOnce('stub value');
const result = await createMetricObjects(savedObjectsClient);
const result = await getOrCreateMetricObject(savedObjectsClient, 'live_query');
checkGetCalls(get.mock.calls);
checkCreateCalls(create.mock.calls);
expect(result).toBe(true);
expect(result).toBe('stub value');
});

it('should handle previously seeded objects properly', async () => {
it('should handle previously created objects properly', async () => {
get.mockReturnValueOnce('stub value');
create.mockRejectedValueOnce('stub value');
const result = await createMetricObjects(savedObjectsClient);
const result = await getOrCreateMetricObject(savedObjectsClient, 'live_query');
checkGetCalls(get.mock.calls);
checkCreateCalls(create.mock.calls, []);
expect(result).toBe(true);
});

it('should report failure to create the metrics object', async () => {
get.mockRejectedValueOnce('stub value');
create.mockRejectedValueOnce('stub value');
const result = await createMetricObjects(savedObjectsClient);
checkGetCalls(get.mock.calls);
checkCreateCalls(create.mock.calls);
expect(result).toBe(false);
expect(result).toBe('stub value');
});
});

Expand Down
Loading