Skip to content

[Security Solution][Endpoint][Admin][Policy List] GET endpoint package policy api#119545

Merged
parkiino merged 20 commits intoelastic:mainfrom
parkiino:task/get-endpoint-packagepolicy-api
Dec 14, 2021
Merged

[Security Solution][Endpoint][Admin][Policy List] GET endpoint package policy api#119545
parkiino merged 20 commits intoelastic:mainfrom
parkiino:task/get-endpoint-packagepolicy-api

Conversation

@parkiino
Copy link
Copy Markdown
Contributor

@parkiino parkiino commented Nov 23, 2021

Summary

  • Creates a new decoupled endpoint api: GET /api/endpoint/policy : which returns a list of package policies
  • Swaps out the original fleet api endpoint with the new endpoint api in the service: sendGetEndpointSpecificPackagePolicies
  • Unit tests

@parkiino parkiino added v8.0.0 release_note:skip Skip the PR/issue when compiling release notes Feature:Endpoint Elastic Endpoint feature Team:Defend Workflows “EDR Workflows” sub-team of Security Solution auto-backport Deprecated - use backport:version if exact versions are needed v8.1.0 labels Nov 23, 2021
@parkiino parkiino requested review from a team as code owners November 23, 2021 21:11
@parkiino parkiino requested a review from pzl November 23, 2021 21:11
@elasticmachine
Copy link
Copy Markdown
Contributor

Pinging @elastic/security-onboarding-and-lifecycle-mgt (Team:Onboarding and Lifecycle Mgt)

@elasticmachine
Copy link
Copy Markdown
Contributor

Pinging @elastic/esecurity-onboarding-and-lifecycle-mgt (Feature:Endpoint)

router.get(
{
path: BASE_POLICY_ROUTE,
validate: false,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to add the schema from fleet

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you include the schema validation prior to committing this?
The schema you want is GetPackagePoliciesSchema located here:

export const GetPackagePoliciesRequestSchema = {
query: ListWithKuerySchema,
};

I'm not srue if you can import directly from fleet/server/types. if not, then just export it out of fleet's server/index

Copy link
Copy Markdown
Contributor

@paul-tavares paul-tavares left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also please add some tests?

Its looking good. I left some comments. Let me know if you have any questions.

body: doc,
});
}
return response.notFound({ body: 'Failed to retrieve package policy list' });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is right. If the above query did not throw and it returned null then that implies that are none, right? on a list type of API when this is case, we should return the normal list response with an empty array for the items.

Comment on lines +72 to +79
const soClient = context.core.savedObjects.client;
const packagePolicyService = endpointAppContext.service.getPackagePolicyService();
const doc = await packagePolicyService.list(soClient, request.query);
if (doc) {
return response.ok({
body: doc,
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be wrapped in a try {} catch (error) {} and the catch of the error should then do this:

Suggested change
const soClient = context.core.savedObjects.client;
const packagePolicyService = endpointAppContext.service.getPackagePolicyService();
const doc = await packagePolicyService.list(soClient, request.query);
if (doc) {
return response.ok({
body: doc,
});
}
return response.notFound({ body: new EndpointError('Failed to retrieve package policy list', error) });

Which does a few thing:

  1. it returns back to the consumer of this API a body that is consistent with other errors (normally it reaches the UI code as an object containing the message property)
  2. it helps the kibana logs to see the original code that was encurred when we called the fleet service

router.get(
{
path: BASE_POLICY_ROUTE,
validate: false,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you include the schema validation prior to committing this?
The schema you want is GetPackagePoliciesSchema located here:

export const GetPackagePoliciesRequestSchema = {
query: ListWithKuerySchema,
};

I'm not srue if you can import directly from fleet/server/types. if not, then just export it out of fleet's server/index

return async (context, request, response) => {
const soClient = context.core.savedObjects.client;
const packagePolicyService = endpointAppContext.service.getPackagePolicyService();
const doc = await packagePolicyService.list(soClient, request.query);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doc is a bit misleading since we're getting a list of docs. maybe rename it to listResponse?
Also, we should put a type on this that matches the type of the data this API is meant to return (GetPackagePoliciesResponse). That will ensure that if the service return value is ever changed in fleet, that we catch that here and ensure that the API response is returning the expected structure.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm now wondering if we should create a new service to handle fleet "stuff". We can't just pass through the request.query here because we need to ensure that all calls to fleet for data is always filtered to retrieve only endpoint related data. so this request needs to ensure that we are requesting package policies whose package.name is endpoint. This additional filter needs to be included in the call or added to any kuery that came in on the Request.: ex:

kuery = `${request.query.kuery ? `(${request.query.kuery}) AND ` :  ''}(${fleet_package_policy_so_type}.package.name: endpoint)`

because of this, I'm thinking we want to create our own service (ex. FleetDataService)

cc/ @pzl

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in my poc this route will be called in the existing sendGetEndpointSpecificPackagePolicies which is in public/management/services/policies.ts which does add an additional filter for endpoint specific policies. Although I guess if people tried using the api directly without using that service, it doesn't pass along the endpoint filtering query

@parkiino parkiino requested a review from academo November 29, 2021 17:30
body: listResponse,
});
} catch (error) {
return response.notFound({
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not yet familiar with the way we do server API so let me know is this is the way we do things:

You are assuming here the reason why the request failed is because it was not found (404) but the error can be that and anything else. I'd argue that a 500 error makes more sense here since the error was unexpected and the API itself does exist.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed - this should be a 500 here.

Copy link
Copy Markdown
Contributor

@academo academo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. Check if the 404 error really makes sense for the API otherwise good.

Copy link
Copy Markdown
Contributor

@paul-tavares paul-tavares left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add some tests?

body: listResponse,
});
} catch (error) {
return response.notFound({
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed - this should be a 500 here.

const soClient = context.core.savedObjects.client;
const packagePolicyService = endpointAppContext.service.getPackagePolicyService();
try {
const listResponse = await packagePolicyService.list(soClient, request.query);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my prior comment around ensuring we are not creating a path to retrieve non-endpoint packages with this API. All calls to this API need to ensure they are being wrapped with a filter that looks only at endpoint integrations. I'm ok if you want to do that here for now, but we may want to create a service module in our code base that can do that.


const ListWithKuerySchema = schema.object({
page: schema.maybe(schema.number({ defaultValue: 1 })),
perPage: schema.maybe(schema.number({ defaultValue: 20 })),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we rename this to pageSize for consistency

pageSize: schema.number({ defaultValue: ENDPOINT_DEFAULT_PAGE_SIZE, min: 1, max: 10000 }),

const ListWithKuerySchema = schema.object({
page: schema.maybe(schema.number({ defaultValue: 1 })),
perPage: schema.maybe(schema.number({ defaultValue: 20 })),
sortField: schema.maybe(schema.string()),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we agreed on sort for this https://github.com/elastic/security-team/issues/2149 though I don't mind this more explicit naming.

});
});

it('should add endpoint-specific kuery to the requests kuery', async () => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be good to also add tests for the other query params too.

}),
};

const ListWithKuerySchema = schema.object({
Copy link
Copy Markdown
Member

@joeypoon joeypoon Dec 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be good to add tests for these. I have a suspicion that these default values might not work since they're wrapped in a maybe.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

});
});
});
describe('test GET policy list handler', () => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

? [
{
_index: 'metrics-endpoint.policy-default-1',
_index: 'ingest-package-policies',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not familiar with this code but just checking this change is intended.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, this is probably not intended. This function seems to be mocking the ES response for when searching for the Endpoint's Policy Response document.

? [
{
_index: 'metrics-endpoint.policy-default-1',
_index: 'ingest-package-policies',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, this is probably not intended. This function seems to be mocking the ES response for when searching for the Endpoint's Policy Response document.

return async (context, request, response) => {
const soClient = context.core.savedObjects.client;
const fleetServices = endpointAppContext.service.getScopedFleetServices(request);
const endpointFilteredKuery = `${
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not look correct - you need to ensure that both side of the AND are wrapped in parentheses

Suggested change
const endpointFilteredKuery = `${
const endpointFilteredKuery = `${request?.query?.kuery ? `(${request.query.kuery}) AND ` : ''
}(${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: endpoint)`

Here is a test that you can do to see why this is important (note: i did not actually execute this, but you should be able to and get the results I'm thinking you should get):

  • call this API (prior to my suggestion above) with: query.kuery: '(ingest-package-policies.package.name: nginx) OR '

This will likely match and return package policies for nginx (if you have any) instead of only looking for endpoint. Thats because the parentheses are evaluated first and if true, then the other side of the OR will never be eval'd and thus the API can be abused to actually query for non-endpoint stuff.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I worked with @parkiino yesterday on this and we validated that what i thought was going to happen (retrieval of non-endpoint package policies) did not actually happen. that's because even if a user of the API ends their kql with 0R, when we append the AND package.name: endpoint to it, you get a KQL parsing error (...OR AND...). So I think we're good, especially since parentheses were added around the request's kuery value (if any).

mockResponse
);
expect(mockPackagePolicyService.list.mock.calls[0][1]).toEqual({
kuery: `some query and ${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: endpoint`,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test assertion is not correct. the kuery should have both sides of the AND wrapped in parentheses - like: (some query) AND (${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: endpoint)

@parkiino parkiino removed the v8.0.0 label Dec 13, 2021
Copy link
Copy Markdown
Contributor

@paul-tavares paul-tavares left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥
Thanks @parkiino for working on this and following through on all the feedback. Like @joeypoon mentioned, please create an issue to add more tests - including integration tests and follow through on that prior to 8.1.

Also - reminder: this probably does not need to go into 8.0. only 8.1

Copy link
Copy Markdown
Member

@pzl pzl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚋

@kibana-ci
Copy link
Copy Markdown

💚 Build Succeeded

Metrics [docs]

✅ unchanged

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

@parkiino parkiino merged commit 9b15697 into elastic:main Dec 14, 2021
@kibanamachine kibanamachine added the backport:skip This PR does not require backporting label Dec 14, 2021
@parkiino parkiino deleted the task/get-endpoint-packagepolicy-api branch December 14, 2021 21:51
@kibanamachine
Copy link
Copy Markdown
Contributor

💔 Backport failed

The backport operation could not be completed due to the following error:
There are no branches to backport to. Aborting.

The backport PRs will be merged automatically after passing CI.

To backport manually run:
node scripts/backport --pr 119545

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto-backport Deprecated - use backport:version if exact versions are needed backport:skip This PR does not require backporting Feature:Endpoint Elastic Endpoint feature release_note:skip Skip the PR/issue when compiling release notes Team:Defend Workflows “EDR Workflows” sub-team of Security Solution v8.1.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants