-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
[Serverless] Add schema validation to Search Strategies in security solution & timelines #162539
Conversation
e77950d
to
1f6a83b
Compare
14209d9
to
56ee1b5
Compare
56ee1b5
to
c9dbf09
Compare
x-pack/plugins/security_solution/common/api/search_strategy/model/request_basic_options.ts
Outdated
Show resolved
Hide resolved
@@ -0,0 +1,83 @@ | |||
/* | |||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Going through some of these schema's, I feel like these should be available at the Kibana level, because I can see it being replicated across plugins. Not something for you to do, but worth adding a ticket to circle back and see if Kibana platform can package these
|
||
import { z } from 'zod'; | ||
|
||
export const language = z.union([z.literal('eql'), z.literal('kuery'), z.literal('lucene')]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome! Going to have to add esql
to this soon as well. Same comment about kibana platform here
x-pack/plugins/timelines/common/api/search_strategy/model/runtime_mappings.ts
Show resolved
Hide resolved
const { activePage, querySize } = options.pagination; | ||
const { | ||
pagination: { activePage, querySize } = { | ||
activePage: undefined, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why default this to undefined
rather than 0
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work with this PR! Just had some questions around certain typing decisions, but it looks great and works great. Still a couple todo's but giving the approve and would love to chat about some of the type decisions I commented on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did a first pass, will review more later today
const parsedRequest = searchStrategyRequestSchema.parse(request); | ||
|
||
const queryFactory = securitySolutionFactory[parsedRequest.factoryQueryType]; | ||
|
||
const queryFactory: SecuritySolutionFactory<T> = | ||
securitySolutionFactory[request.factoryQueryType]; | ||
const dsl = queryFactory.buildDsl(request); | ||
const dsl = queryFactory.buildDsl(parsedRequest); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was digging in here to figure out how the compiler ensures that parsedRequest
is passed into the correct implementation of buildDsl
for a particular factoryQueryType
. The short answer is it doesn't: in x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts
, for example, we have
export const hostsFactory: Record<
HostsQueries | HostsKpiQueries,
SecuritySolutionFactory<FactoryQueryTypes>
> = {
[HostsQueries.details]: hostDetails,
[HostsQueries.hosts]: allHosts,
[HostsQueries.overview]: hostOverview,
[HostsQueries.uncommonProcesses]: uncommonProcesses,
[HostsKpiQueries.kpiHosts]: hostsKpiHosts,
[HostsKpiQueries.kpiUniqueIps]: hostsKpiUniqueIps,
};
but the compiler does not throw any error if we assign hostDetails
or any other value from this record to the wrong key. The problem here is that hostDetails
should not actually be assignable to a key of type SecuritySolutionFactory<FactoryQueryTypes>
because the types are incompatible. The type of buildDsl
in SecuritySolutionFactory<FactoryQueryTypes>
says it must accept any factoryQueryType
, but the type SecuritySolutionFactory<HostsQueries.details>
specifies a type for buildDsl
that accepts only hostDetails
for factoryQueryType
. This should throw a type error but does not due to a bug in the compiler. The linked bug report also shows how this sort of error can lead to runtime exceptions.
In this file, the result of the bug is that the type system says we have always get an implementation of buildDsl
that can accept any factoryQueryType
from securitySolutionFactory
, so it's no problem to pass in any parsedRequest
to any queryFactory
that we retrieve.
The problem is not introduced by this PR but we should definitely fix it either here or soon after in a follow up PR. A good start to fixing the issue would be replacing all the factory record types like
export const hostsFactory: Record<
HostsQueries | HostsKpiQueries,
SecuritySolutionFactory<FactoryQueryTypes>
>
with something more like
export type FactoryRecordType = {
[T in FactoryQueryTypes]: SecuritySolutionFactory<T>;
};
export const hostsFactory: FactoryRecordType ....
This uses the compiler to enforce that the factory implementations are associated with the correct keys in the record. A number of additional changes will need to be made as well to fix corresponding types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thank you for the heads up, I will try to fix it in scope of this PR even
filterQuery, | ||
defaultIndex: z.array(z.string()).optional(), | ||
id: z.string().optional(), | ||
params: z.any().optional(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
z.any()
here seems problematic - are we using this param in requests right now somewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will take a look, though something should be here for basic search request compat
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall this looks great! This was a huge number of new schemas to add so thank you for the hard work on hardening these APIs. I left a couple more comments about improvements we can make, but I don't think they should block merging the huge step forward we have here already.
import type { HostsKpiHistogramData } from '../common'; | ||
|
||
export type HostsKpiUniqueIpsRequestOptions = RequestBasicOptions; | ||
|
||
export interface HostsKpiUniqueIpsStrategyResponse extends IEsSearchResponse { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should move these Response
types to the common/api
folder next to the associated Request
type - doesn't have to be now, since this PR is large and has major improvements already, but soon. It's convenient to have both the request and response types together so the full contract for each API is contained in a single place. We don't have to implement runtime schemas for the response types though because the compiler can ensure that our response conforms to the response type.
@@ -218,6 +219,76 @@ export type StrategyResponseType<T extends FactoryQueryTypes> = T extends HostsQ | |||
? UsersRelatedHostsStrategyResponse | |||
: never; | |||
|
|||
export type StrategyRequestInputType<T extends FactoryQueryTypes> = T extends HostsQueries.hosts |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A neat trick we can use here to avoid maintaining this large conditional is the TypeScript Extract
feature (https://stackoverflow.com/questions/64527150/in-typescript-how-to-select-a-type-from-a-union-using-a-literal-type-property). Since we have all our request schemas in one discriminated union searchStrategyRequestSchema
, we can create this generic type based on that union:
type SearchStrategyRequestInputSchema = z.input<typeof searchStrategyRequestSchema>;
export type RequestSchemaType<T extends FactoryQueryTypes> = Extract<SearchStrategyRequestInputSchema, {factoryQueryType: T}>;
export type Test = RequestSchemaType<HostsQueries.hosts>;
This code makes the Test
type equivalent to StrategyRequestInputType<HostsQueries.hosts>
. We can do the same thing for the existing code (StrategyRequestType
and StrategyResponseType
) that established this pattern and reduce some of the tedious code maintenance here.
💛 Build succeeded, but was flaky
Failed CI StepsMetrics [docs]Module Count
Public APIs missing comments
Async chunks
Public APIs missing exports
Page load bundle
Unknown metric groupsAPI count
ESLint disabled line counts
Total ESLint disabled count
History
To update your PR or re-run it, just comment with: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tested locally and LGTM, thanks so much for adding the schemas.
…egies in security solution & timelines (elastic#162539) ## Summary This PR specifies validation schemas for enpoints listed here: elastic/security-team#6486
Summary
Specifies validation schemas for enpoints listed here:
https://github.com/elastic/security-team/issues/6486
Checklist
Delete any items that are not applicable to this PR.
Fixing tests on CI
Adding this to
kibana.dev.yml
helps a lot in debugging.For maintainers