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

[Serverless] Add schema validation to Search Strategies in security solution & timelines #162539

Merged
merged 29 commits into from
Sep 21, 2023

Conversation

lgestc
Copy link
Contributor

@lgestc lgestc commented Jul 26, 2023

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.

  • Unit or functional tests were updated or added to match the most common scenarios
  • Add schemas
  • filterQuery schema is too liberal (?)
  • runtime fields type in model allows everything really

Fixing tests on CI

Adding this to kibana.dev.yml helps a lot in debugging.

uiSettings:
  overrides:
    'bfetch:disable': true

For maintainers

@lgestc lgestc force-pushed the hosts_api_schema branch 4 times, most recently from e77950d to 1f6a83b Compare July 26, 2023 08:03
@lgestc lgestc changed the title [Serverless] Hosts search strategy schema [Serverless] Remaining strategy schemas Jul 26, 2023
@lgestc lgestc force-pushed the hosts_api_schema branch 21 times, most recently from 14209d9 to 56ee1b5 Compare July 31, 2023 12:44
@lgestc lgestc force-pushed the hosts_api_schema branch from 56ee1b5 to c9dbf09 Compare August 1, 2023 14:09
@@ -0,0 +1,83 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
Copy link
Contributor

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')]);
Copy link
Contributor

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

const { activePage, querySize } = options.pagination;
const {
pagination: { activePage, querySize } = {
activePage: undefined,
Copy link
Contributor

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?

Copy link
Contributor

@michaelolo24 michaelolo24 left a 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.

Copy link
Contributor

@marshallmain marshallmain left a 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

Comment on lines +30 to +34
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);
Copy link
Contributor

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.

Copy link
Contributor Author

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(),
Copy link
Contributor

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?

Copy link
Contributor Author

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

Copy link
Contributor

@marshallmain marshallmain left a 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 {
Copy link
Contributor

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
Copy link
Contributor

@marshallmain marshallmain Sep 15, 2023

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.

@kibana-ci
Copy link
Collaborator

💛 Build succeeded, but was flaky

Failed CI Steps

Metrics [docs]

Module Count

Fewer modules leads to a faster build time

id before after diff
securitySolution 4487 4541 +54
timelines 93 121 +28
total +82

Public APIs missing comments

Total count of every public API that lacks a comment. Target amount is 0. Run node scripts/build_api_docs --plugin [yourplugin] --stats comments for more detailed information.

id before after diff
timelines 213 196 -17

Async chunks

Total size of all lazy-loaded chunks that will be downloaded as the user navigates the app

id before after diff
securitySolution 12.8MB 12.8MB +34.5KB
timelines 30.0KB 30.0KB +2.0B
total +34.5KB

Public APIs missing exports

Total count of every type that is part of your API that should be exported but is not. This will cause broken links in the API documentation system. Target amount is 0. Run node scripts/build_api_docs --plugin [yourplugin] --stats exports for more detailed information.

id before after diff
timelines 22 17 -5

Page load bundle

Size of the bundles that are downloaded on every page load. Target size is below 100kb

id before after diff
timelines 42.6KB 109.5KB +66.8KB
Unknown metric groups

API count

id before after diff
timelines 257 240 -17

ESLint disabled line counts

id before after diff
securitySolution 453 455 +2
timelines 22 23 +1
total +3

Total ESLint disabled count

id before after diff
securitySolution 519 521 +2
timelines 22 23 +1
total +3

History

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

Copy link
Contributor

@angorayc angorayc left a 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.

@lgestc lgestc merged commit 3a017de into elastic:main Sep 21, 2023
@lgestc lgestc deleted the hosts_api_schema branch September 21, 2023 05:57
gergoabraham pushed a commit to gergoabraham/kibana that referenced this pull request Sep 21, 2023
…egies in security solution & timelines (elastic#162539)

## Summary

This PR specifies validation schemas for enpoints listed here:
elastic/security-team#6486
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
8.11 candidate backport:skip This commit does not require backporting release_note:skip Skip the PR/issue when compiling release notes Team:Threat Hunting:Investigations Security Solution Investigations Team v8.11.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants