[Security Solution] Move endpointdetails into its own middleware function (#107632)

This commit is contained in:
Esteban Beltran 2021-08-09 15:17:08 +02:00 committed by GitHub
parent e7a05c0110
commit 417d093a29
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -94,7 +94,6 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory<EndpointState
};
return [indexPattern];
}
// eslint-disable-next-line complexity
return (store) => (next) => async (action) => {
next(action);
@ -108,270 +107,12 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory<EndpointState
isOnEndpointPage(getState()) &&
hasSelectedEndpoint(getState()) !== true
) {
const { page_index: pageIndex, page_size: pageSize } = uiQueryParams(getState());
let endpointResponse;
try {
const decodedQuery: Query = searchBarQuery(getState());
endpointResponse = await coreStart.http.post<HostResultList>(HOST_METADATA_LIST_ROUTE, {
body: JSON.stringify({
paging_properties: [{ page_index: pageIndex }, { page_size: pageSize }],
filters: { kql: decodedQuery.query },
}),
});
endpointResponse.request_page_index = Number(pageIndex);
dispatch({
type: 'serverReturnedEndpointList',
payload: endpointResponse,
});
loadEndpointsPendingActions(store);
try {
const endpointsTotalCount = await endpointsTotal(coreStart.http);
dispatch({
type: 'serverReturnedEndpointsTotal',
payload: endpointsTotalCount,
});
} catch (error) {
dispatch({
type: 'serverFailedToReturnEndpointsTotal',
payload: error,
});
}
try {
const agentsWithEndpoint = await sendGetFleetAgentsWithEndpoint(coreStart.http);
dispatch({
type: 'serverReturnedAgenstWithEndpointsTotal',
payload: agentsWithEndpoint.total,
});
} catch (error) {
dispatch({
type: 'serverFailedToReturnAgenstWithEndpointsTotal',
payload: error,
});
}
try {
const ingestPolicies = await getAgentAndPoliciesForEndpointsList(
coreStart.http,
endpointResponse.hosts,
nonExistingPolicies(getState())
);
if (ingestPolicies?.packagePolicy !== undefined) {
dispatch({
type: 'serverReturnedEndpointNonExistingPolicies',
payload: ingestPolicies.packagePolicy,
});
}
if (ingestPolicies?.agentPolicy !== undefined) {
dispatch({
type: 'serverReturnedEndpointAgentPolicies',
payload: ingestPolicies.agentPolicy,
});
}
} catch (error) {
// TODO should handle the error instead of logging it to the browser
// Also this is an anti-pattern we shouldn't use
// Ignore Errors, since this should not hinder the user's ability to use the UI
logError(error);
}
} catch (error) {
dispatch({
type: 'serverFailedToReturnEndpointList',
payload: error,
});
}
// get index pattern and fields for search bar
if (patterns(getState()).length === 0) {
try {
const indexPatterns = await fetchIndexPatterns();
if (indexPatterns !== undefined) {
dispatch({
type: 'serverReturnedMetadataPatterns',
payload: indexPatterns,
});
}
} catch (error) {
dispatch({
type: 'serverFailedToReturnMetadataPatterns',
payload: error,
});
}
}
// No endpoints, so we should check to see if there are policies for onboarding
if (endpointResponse && endpointResponse.hosts.length === 0) {
const http = coreStart.http;
// The original query to the list could have had an invalid param (ex. invalid page_size),
// so we check first if endpoints actually do exist before pulling in data for the onboarding
// messages.
if (await doEndpointsExist(http)) {
return;
}
dispatch({
type: 'serverReturnedEndpointExistValue',
payload: false,
});
try {
const policyDataResponse: GetPolicyListResponse = await sendGetEndpointSpecificPackagePolicies(
http,
{
query: {
perPage: 50, // Since this is an oboarding flow, we'll cap at 50 policies.
page: 1,
},
}
);
dispatch({
type: 'serverReturnedPoliciesForOnboarding',
payload: {
policyItems: policyDataResponse.items,
},
});
} catch (error) {
dispatch({
type: 'serverFailedToReturnPoliciesForOnboarding',
payload: error.body ?? error,
});
return;
}
} else {
dispatch({
type: 'serverCancelledPolicyItemsLoading',
});
dispatch({
type: 'serverReturnedEndpointExistValue',
payload: true,
});
}
endpointDetailsListMiddleware({ coreStart, store, fetchIndexPatterns });
}
// Endpoint Details
if (action.type === 'userChangedUrl' && hasSelectedEndpoint(getState()) === true) {
dispatch({
type: 'serverCancelledPolicyItemsLoading',
});
// If user navigated directly to a endpoint details page, load the endpoint list
if (listData(getState()).length === 0) {
const { page_index: pageIndex, page_size: pageSize } = uiQueryParams(getState());
try {
const response = await coreStart.http.post(HOST_METADATA_LIST_ROUTE, {
body: JSON.stringify({
paging_properties: [{ page_index: pageIndex }, { page_size: pageSize }],
}),
});
response.request_page_index = Number(pageIndex);
dispatch({
type: 'serverReturnedEndpointList',
payload: response,
});
try {
const ingestPolicies = await getAgentAndPoliciesForEndpointsList(
coreStart.http,
response.hosts,
nonExistingPolicies(getState())
);
if (ingestPolicies?.packagePolicy !== undefined) {
dispatch({
type: 'serverReturnedEndpointNonExistingPolicies',
payload: ingestPolicies.packagePolicy,
});
}
if (ingestPolicies?.agentPolicy !== undefined) {
dispatch({
type: 'serverReturnedEndpointAgentPolicies',
payload: ingestPolicies.agentPolicy,
});
}
} catch (error) {
// TODO should handle the error instead of logging it to the browser
// Also this is an anti-pattern we shouldn't use
// Ignore Errors, since this should not hinder the user's ability to use the UI
logError(error);
}
} catch (error) {
dispatch({
type: 'serverFailedToReturnEndpointList',
payload: error,
});
}
} else {
dispatch({
type: 'serverCancelledEndpointListLoading',
});
}
// call the endpoint details api
const { selected_endpoint: selectedEndpoint } = uiQueryParams(getState());
try {
const response = await coreStart.http.get<HostInfo>(
resolvePathVariables(HOST_METADATA_GET_ROUTE, { id: selectedEndpoint as string })
);
dispatch({
type: 'serverReturnedEndpointDetails',
payload: response,
});
try {
const ingestPolicies = await getAgentAndPoliciesForEndpointsList(
coreStart.http,
[response],
nonExistingPolicies(getState())
);
if (ingestPolicies !== undefined) {
dispatch({
type: 'serverReturnedEndpointNonExistingPolicies',
payload: ingestPolicies.packagePolicy,
});
}
if (ingestPolicies?.agentPolicy !== undefined) {
dispatch({
type: 'serverReturnedEndpointAgentPolicies',
payload: ingestPolicies.agentPolicy,
});
}
} catch (error) {
// TODO should handle the error instead of logging it to the browser
// Also this is an anti-pattern we shouldn't use
// Ignore Errors, since this should not hinder the user's ability to use the UI
logError(error);
}
} catch (error) {
dispatch({
type: 'serverFailedToReturnEndpointDetails',
payload: error,
});
}
loadEndpointsPendingActions(store);
// call the policy response api
try {
const policyResponse = await coreStart.http.get(BASE_POLICY_RESPONSE_ROUTE, {
query: { agentId: selectedEndpoint },
});
dispatch({
type: 'serverReturnedEndpointPolicyResponse',
payload: policyResponse,
});
} catch (error) {
dispatch({
type: 'serverFailedToReturnEndpointPolicyResponse',
payload: error,
});
}
endpointDetailsMiddleware({ store, coreStart });
}
if (
@ -379,32 +120,7 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory<EndpointState
hasSelectedEndpoint(getState()) === true &&
getIsOnEndpointDetailsActivityLog(getState())
) {
// call the activity log api
dispatch({
type: 'endpointDetailsActivityLogChanged',
// ts error to be fixed when AsyncResourceState is refactored (#830)
// @ts-expect-error
payload: createLoadingResourceState<ActivityLog>(getActivityLogData(getState())),
});
try {
const { page, pageSize } = getActivityLogDataPaging(getState());
const route = resolvePathVariables(ENDPOINT_ACTION_LOG_ROUTE, {
agent_id: selectedAgent(getState()),
});
const activityLog = await coreStart.http.get<ActivityLog>(route, {
query: { page, page_size: pageSize },
});
dispatch({
type: 'endpointDetailsActivityLogChanged',
payload: createLoadedResourceState<ActivityLog>(activityLog),
});
} catch (error) {
dispatch({
type: 'endpointDetailsActivityLogChanged',
payload: createFailedResourceState<ActivityLog>(error.body ?? error),
});
}
endpointDetailsActivityLogChangedMiddleware({ store, coreStart });
}
// page activity log API
@ -412,91 +128,7 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory<EndpointState
action.type === 'endpointDetailsActivityLogUpdatePaging' &&
hasSelectedEndpoint(getState())
) {
try {
const { disabled, page, pageSize, startDate, endDate } = getActivityLogDataPaging(
getState()
);
// don't page when paging is disabled or when date ranges are invalid
if (disabled) {
return;
}
if (getIsInvalidDateRange({ startDate, endDate })) {
dispatch({
type: 'endpointDetailsActivityLogUpdateIsInvalidDateRange',
payload: {
isInvalidDateRange: true,
},
});
return;
}
dispatch({
type: 'endpointDetailsActivityLogUpdateIsInvalidDateRange',
payload: {
isInvalidDateRange: false,
},
});
dispatch({
type: 'endpointDetailsActivityLogChanged',
// ts error to be fixed when AsyncResourceState is refactored (#830)
// @ts-expect-error
payload: createLoadingResourceState<ActivityLog>(getActivityLogData(getState())),
});
const route = resolvePathVariables(ENDPOINT_ACTION_LOG_ROUTE, {
agent_id: selectedAgent(getState()),
});
const activityLog = await coreStart.http.get<ActivityLog>(route, {
query: {
page,
page_size: pageSize,
start_date: startDate,
end_date: endDate,
},
});
const lastLoadedLogData = getLastLoadedActivityLogData(getState());
if (lastLoadedLogData !== undefined) {
const updatedLogDataItems = ([
...new Set([...lastLoadedLogData.data, ...activityLog.data]),
] as ActivityLog['data']).sort((a, b) =>
new Date(b.item.data['@timestamp']) > new Date(a.item.data['@timestamp']) ? 1 : -1
);
const updatedLogData = {
page: activityLog.page,
pageSize: activityLog.pageSize,
startDate: activityLog.startDate,
endDate: activityLog.endDate,
data: activityLog.page === 1 ? activityLog.data : updatedLogDataItems,
};
dispatch({
type: 'endpointDetailsActivityLogChanged',
payload: createLoadedResourceState<ActivityLog>(updatedLogData),
});
if (!activityLog.data.length) {
dispatch({
type: 'endpointDetailsActivityLogUpdatePaging',
payload: {
disabled: true,
page: activityLog.page > 1 ? activityLog.page - 1 : 1,
pageSize: activityLog.pageSize,
startDate: activityLog.startDate,
endDate: activityLog.endDate,
},
});
}
} else {
dispatch({
type: 'endpointDetailsActivityLogChanged',
payload: createLoadedResourceState<ActivityLog>(activityLog),
});
}
} catch (error) {
dispatch({
type: 'endpointDetailsActivityLogChanged',
payload: createFailedResourceState<ActivityLog>(error.body ?? error),
});
}
endpointDetailsActivityLogPagingMiddleware({ store, coreStart });
}
// Isolate Host
@ -730,6 +362,416 @@ const loadEndpointsPendingActions = async ({
}
};
async function endpointDetailsListMiddleware({
store,
coreStart,
fetchIndexPatterns,
}: {
store: ImmutableMiddlewareAPI<EndpointState, AppAction>;
coreStart: CoreStart;
fetchIndexPatterns: () => Promise<IIndexPattern[]>;
}) {
const { getState, dispatch } = store;
const { page_index: pageIndex, page_size: pageSize } = uiQueryParams(getState());
let endpointResponse;
try {
const decodedQuery: Query = searchBarQuery(getState());
endpointResponse = await coreStart.http.post<HostResultList>(HOST_METADATA_LIST_ROUTE, {
body: JSON.stringify({
paging_properties: [{ page_index: pageIndex }, { page_size: pageSize }],
filters: { kql: decodedQuery.query },
}),
});
endpointResponse.request_page_index = Number(pageIndex);
dispatch({
type: 'serverReturnedEndpointList',
payload: endpointResponse,
});
loadEndpointsPendingActions(store);
try {
const endpointsTotalCount = await endpointsTotal(coreStart.http);
dispatch({
type: 'serverReturnedEndpointsTotal',
payload: endpointsTotalCount,
});
} catch (error) {
dispatch({
type: 'serverFailedToReturnEndpointsTotal',
payload: error,
});
}
try {
const agentsWithEndpoint = await sendGetFleetAgentsWithEndpoint(coreStart.http);
dispatch({
type: 'serverReturnedAgenstWithEndpointsTotal',
payload: agentsWithEndpoint.total,
});
} catch (error) {
dispatch({
type: 'serverFailedToReturnAgenstWithEndpointsTotal',
payload: error,
});
}
try {
const ingestPolicies = await getAgentAndPoliciesForEndpointsList(
coreStart.http,
endpointResponse.hosts,
nonExistingPolicies(getState())
);
if (ingestPolicies?.packagePolicy !== undefined) {
dispatch({
type: 'serverReturnedEndpointNonExistingPolicies',
payload: ingestPolicies.packagePolicy,
});
}
if (ingestPolicies?.agentPolicy !== undefined) {
dispatch({
type: 'serverReturnedEndpointAgentPolicies',
payload: ingestPolicies.agentPolicy,
});
}
} catch (error) {
// TODO should handle the error instead of logging it to the browser
// Also this is an anti-pattern we shouldn't use
// Ignore Errors, since this should not hinder the user's ability to use the UI
logError(error);
}
} catch (error) {
dispatch({
type: 'serverFailedToReturnEndpointList',
payload: error,
});
}
// get index pattern and fields for search bar
if (patterns(getState()).length === 0) {
try {
const indexPatterns = await fetchIndexPatterns();
if (indexPatterns !== undefined) {
dispatch({
type: 'serverReturnedMetadataPatterns',
payload: indexPatterns,
});
}
} catch (error) {
dispatch({
type: 'serverFailedToReturnMetadataPatterns',
payload: error,
});
}
}
// No endpoints, so we should check to see if there are policies for onboarding
if (endpointResponse && endpointResponse.hosts.length === 0) {
const http = coreStart.http;
// The original query to the list could have had an invalid param (ex. invalid page_size),
// so we check first if endpoints actually do exist before pulling in data for the onboarding
// messages.
if (await doEndpointsExist(http)) {
return;
}
dispatch({
type: 'serverReturnedEndpointExistValue',
payload: false,
});
try {
const policyDataResponse: GetPolicyListResponse = await sendGetEndpointSpecificPackagePolicies(
http,
{
query: {
perPage: 50, // Since this is an oboarding flow, we'll cap at 50 policies.
page: 1,
},
}
);
dispatch({
type: 'serverReturnedPoliciesForOnboarding',
payload: {
policyItems: policyDataResponse.items,
},
});
} catch (error) {
dispatch({
type: 'serverFailedToReturnPoliciesForOnboarding',
payload: error.body ?? error,
});
}
} else {
dispatch({
type: 'serverCancelledPolicyItemsLoading',
});
dispatch({
type: 'serverReturnedEndpointExistValue',
payload: true,
});
}
}
async function endpointDetailsActivityLogPagingMiddleware({
store,
coreStart,
}: {
store: ImmutableMiddlewareAPI<EndpointState, AppAction>;
coreStart: CoreStart;
}) {
const { getState, dispatch } = store;
try {
const { disabled, page, pageSize, startDate, endDate } = getActivityLogDataPaging(getState());
// don't page when paging is disabled or when date ranges are invalid
if (disabled) {
return;
}
if (getIsInvalidDateRange({ startDate, endDate })) {
dispatch({
type: 'endpointDetailsActivityLogUpdateIsInvalidDateRange',
payload: {
isInvalidDateRange: true,
},
});
return;
}
dispatch({
type: 'endpointDetailsActivityLogUpdateIsInvalidDateRange',
payload: {
isInvalidDateRange: false,
},
});
dispatch({
type: 'endpointDetailsActivityLogChanged',
// ts error to be fixed when AsyncResourceState is refactored (#830)
// @ts-expect-error
payload: createLoadingResourceState<ActivityLog>(getActivityLogData(getState())),
});
const route = resolvePathVariables(ENDPOINT_ACTION_LOG_ROUTE, {
agent_id: selectedAgent(getState()),
});
const activityLog = await coreStart.http.get<ActivityLog>(route, {
query: {
page,
page_size: pageSize,
start_date: startDate,
end_date: endDate,
},
});
const lastLoadedLogData = getLastLoadedActivityLogData(getState());
if (lastLoadedLogData !== undefined) {
const updatedLogDataItems = ([
...new Set([...lastLoadedLogData.data, ...activityLog.data]),
] as ActivityLog['data']).sort((a, b) =>
new Date(b.item.data['@timestamp']) > new Date(a.item.data['@timestamp']) ? 1 : -1
);
const updatedLogData = {
page: activityLog.page,
pageSize: activityLog.pageSize,
startDate: activityLog.startDate,
endDate: activityLog.endDate,
data: activityLog.page === 1 ? activityLog.data : updatedLogDataItems,
};
dispatch({
type: 'endpointDetailsActivityLogChanged',
payload: createLoadedResourceState<ActivityLog>(updatedLogData),
});
if (!activityLog.data.length) {
dispatch({
type: 'endpointDetailsActivityLogUpdatePaging',
payload: {
disabled: true,
page: activityLog.page > 1 ? activityLog.page - 1 : 1,
pageSize: activityLog.pageSize,
startDate: activityLog.startDate,
endDate: activityLog.endDate,
},
});
}
} else {
dispatch({
type: 'endpointDetailsActivityLogChanged',
payload: createLoadedResourceState<ActivityLog>(activityLog),
});
}
} catch (error) {
dispatch({
type: 'endpointDetailsActivityLogChanged',
payload: createFailedResourceState<ActivityLog>(error.body ?? error),
});
}
}
async function endpointDetailsMiddleware({
store,
coreStart,
}: {
store: ImmutableMiddlewareAPI<EndpointState, AppAction>;
coreStart: CoreStart;
}) {
const { getState, dispatch } = store;
dispatch({
type: 'serverCancelledPolicyItemsLoading',
});
// If user navigated directly to a endpoint details page, load the endpoint list
if (listData(getState()).length === 0) {
const { page_index: pageIndex, page_size: pageSize } = uiQueryParams(getState());
try {
const response = await coreStart.http.post(HOST_METADATA_LIST_ROUTE, {
body: JSON.stringify({
paging_properties: [{ page_index: pageIndex }, { page_size: pageSize }],
}),
});
response.request_page_index = Number(pageIndex);
dispatch({
type: 'serverReturnedEndpointList',
payload: response,
});
try {
const ingestPolicies = await getAgentAndPoliciesForEndpointsList(
coreStart.http,
response.hosts,
nonExistingPolicies(getState())
);
if (ingestPolicies?.packagePolicy !== undefined) {
dispatch({
type: 'serverReturnedEndpointNonExistingPolicies',
payload: ingestPolicies.packagePolicy,
});
}
if (ingestPolicies?.agentPolicy !== undefined) {
dispatch({
type: 'serverReturnedEndpointAgentPolicies',
payload: ingestPolicies.agentPolicy,
});
}
} catch (error) {
// TODO should handle the error instead of logging it to the browser
// Also this is an anti-pattern we shouldn't use
// Ignore Errors, since this should not hinder the user's ability to use the UI
logError(error);
}
} catch (error) {
dispatch({
type: 'serverFailedToReturnEndpointList',
payload: error,
});
}
} else {
dispatch({
type: 'serverCancelledEndpointListLoading',
});
}
// call the endpoint details api
const { selected_endpoint: selectedEndpoint } = uiQueryParams(getState());
try {
const response = await coreStart.http.get<HostInfo>(
resolvePathVariables(HOST_METADATA_GET_ROUTE, { id: selectedEndpoint as string })
);
dispatch({
type: 'serverReturnedEndpointDetails',
payload: response,
});
try {
const ingestPolicies = await getAgentAndPoliciesForEndpointsList(
coreStart.http,
[response],
nonExistingPolicies(getState())
);
if (ingestPolicies !== undefined) {
dispatch({
type: 'serverReturnedEndpointNonExistingPolicies',
payload: ingestPolicies.packagePolicy,
});
}
if (ingestPolicies?.agentPolicy !== undefined) {
dispatch({
type: 'serverReturnedEndpointAgentPolicies',
payload: ingestPolicies.agentPolicy,
});
}
} catch (error) {
// TODO should handle the error instead of logging it to the browser
// Also this is an anti-pattern we shouldn't use
// Ignore Errors, since this should not hinder the user's ability to use the UI
logError(error);
}
} catch (error) {
dispatch({
type: 'serverFailedToReturnEndpointDetails',
payload: error,
});
}
loadEndpointsPendingActions(store);
// call the policy response api
try {
const policyResponse = await coreStart.http.get(BASE_POLICY_RESPONSE_ROUTE, {
query: { agentId: selectedEndpoint },
});
dispatch({
type: 'serverReturnedEndpointPolicyResponse',
payload: policyResponse,
});
} catch (error) {
dispatch({
type: 'serverFailedToReturnEndpointPolicyResponse',
payload: error,
});
}
}
async function endpointDetailsActivityLogChangedMiddleware({
store,
coreStart,
}: {
store: ImmutableMiddlewareAPI<EndpointState, AppAction>;
coreStart: CoreStart;
}) {
const { getState, dispatch } = store;
// call the activity log api
dispatch({
type: 'endpointDetailsActivityLogChanged',
// ts error to be fixed when AsyncResourceState is refactored (#830)
// @ts-expect-error
payload: createLoadingResourceState<ActivityLog>(getActivityLogData(getState())),
});
try {
const { page, pageSize } = getActivityLogDataPaging(getState());
const route = resolvePathVariables(ENDPOINT_ACTION_LOG_ROUTE, {
agent_id: selectedAgent(getState()),
});
const activityLog = await coreStart.http.get<ActivityLog>(route, {
query: { page, page_size: pageSize },
});
dispatch({
type: 'endpointDetailsActivityLogChanged',
payload: createLoadedResourceState<ActivityLog>(activityLog),
});
} catch (error) {
dispatch({
type: 'endpointDetailsActivityLogChanged',
payload: createFailedResourceState<ActivityLog>(error.body ?? error),
});
}
}
export async function handleLoadMetadataTransformStats(http: HttpStart, store: EndpointPageStore) {
const { getState, dispatch } = store;