[Security Solution][Endpoint][Admin][Policy List] GET endpoint package policy api#119545
Conversation
|
Pinging @elastic/security-onboarding-and-lifecycle-mgt (Team:Onboarding and Lifecycle Mgt) |
|
Pinging @elastic/esecurity-onboarding-and-lifecycle-mgt (Feature:Endpoint) |
| router.get( | ||
| { | ||
| path: BASE_POLICY_ROUTE, | ||
| validate: false, |
There was a problem hiding this comment.
need to add the schema from fleet
There was a problem hiding this comment.
Can you include the schema validation prior to committing this?
The schema you want is GetPackagePoliciesSchema located here:
kibana/x-pack/plugins/fleet/server/types/rest_spec/package_policy.ts
Lines 14 to 16 in b1253db
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
paul-tavares
left a comment
There was a problem hiding this comment.
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' }); |
There was a problem hiding this comment.
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.
| 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, | ||
| }); | ||
| } |
There was a problem hiding this comment.
this should be wrapped in a try {} catch (error) {} and the catch of the error should then do this:
| 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:
- it returns back to the consumer of this API a
bodythat is consistent with other errors (normally it reaches the UI code as an object containing themessageproperty) - 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, |
There was a problem hiding this comment.
Can you include the schema validation prior to committing this?
The schema you want is GetPackagePoliciesSchema located here:
kibana/x-pack/plugins/fleet/server/types/rest_spec/package_policy.ts
Lines 14 to 16 in b1253db
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
| body: listResponse, | ||
| }); | ||
| } catch (error) { | ||
| return response.notFound({ |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
agreed - this should be a 500 here.
academo
left a comment
There was a problem hiding this comment.
Looks good to me. Check if the 404 error really makes sense for the API otherwise good.
paul-tavares
left a comment
There was a problem hiding this comment.
can you add some tests?
| body: listResponse, | ||
| }); | ||
| } catch (error) { | ||
| return response.notFound({ |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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 })), |
There was a problem hiding this comment.
can we rename this to pageSize for consistency
| const ListWithKuerySchema = schema.object({ | ||
| page: schema.maybe(schema.number({ defaultValue: 1 })), | ||
| perPage: schema.maybe(schema.number({ defaultValue: 20 })), | ||
| sortField: schema.maybe(schema.string()), |
There was a problem hiding this comment.
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 () => { |
There was a problem hiding this comment.
would be good to also add tests for the other query params too.
| }), | ||
| }; | ||
|
|
||
| const ListWithKuerySchema = schema.object({ |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
| }); | ||
| }); | ||
| }); | ||
| describe('test GET policy list handler', () => { |
There was a problem hiding this comment.
we should add some integration tests as well. I'm guessing in here: https://github.com/elastic/kibana/blob/main/x-pack/test/security_solution_endpoint_api_int/apis/policy.ts.
| ? [ | ||
| { | ||
| _index: 'metrics-endpoint.policy-default-1', | ||
| _index: 'ingest-package-policies', |
There was a problem hiding this comment.
I am not familiar with this code but just checking this change is intended.
There was a problem hiding this comment.
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', |
There was a problem hiding this comment.
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 = `${ |
There was a problem hiding this comment.
This does not look correct - you need to ensure that both side of the AND are wrapped in parentheses
| 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.
There was a problem hiding this comment.
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`, |
There was a problem hiding this comment.
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)
💚 Build Succeeded
Metrics [docs]
History
To update your PR or re-run it, just comment with: |
💔 Backport failedThe backport operation could not be completed due to the following error: The backport PRs will be merged automatically after passing CI. To backport manually run: |
Summary
GET /api/endpoint/policy: which returns a list of package policiessendGetEndpointSpecificPackagePolicies