-
Notifications
You must be signed in to change notification settings - Fork 8.2k
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
[Security Solution] Extend the /upgrade/_perform
API endpoint's contract
#189187
Conversation
/upgrade/_perform
API endpoint's contract and functionality/upgrade/_perform
API endpoint's contract
Pinging @elastic/security-detections-response (Team:Detections and Resp) |
Pinging @elastic/security-solution (Team: SecuritySolution) |
Pinging @elastic/security-detection-rule-management (Team:Detection Rule Management) |
- $ref: '../../model/rule_schema/common_attributes.schema.yaml#/components/schemas/RuleName' | ||
- $ref: '../../model/rule_schema/common_attributes.schema.yaml#/components/schemas/RuleTagArray' | ||
- $ref: '../../model/rule_schema/common_attributes.schema.yaml#/components/schemas/RuleDescription' |
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.
What is the reason for making this type a union? The rule name
prop should have the resolved_value
matching the ruleName
type, not any of these. Can it be implemented that way?
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'm trying to reverse engineer these types. Notice the generic FieldUpgradeRequest
type, which is what I tried to replicate with a union:
export interface PerformRuleUpgradeRequestBody {
mode: 'ALL_RULES' | 'SPECIFIC_RULES';
pick_version?: 'BASE' | 'CURRENT' | 'TARGET' | 'MERGED';
rules: SingleRuleUpgradeRequest[]; // required if mode is SPECIFIC_RULES
}
export interface SingleRuleUpgradeRequest {
id: RuleObjectId;
pick_version?: 'BASE' | 'CURRENT' | 'TARGET' | 'MERGED';
fields?: {
name?: FieldUpgradeRequest<RuleName>;
description?: FieldUpgradeRequest<RuleDescription>;
// etc
// Every non-specified field will default to pick_version: 'MERGED'.
// If pick_version is MERGED and there's a merge conflict the endpoint will throw.
};
rule_revision: number;
}
export interface FieldUpgradeRequest<T> {
pick_version: 'BASE' | 'CURRENT' | 'TARGET' | 'MERGED' | 'RESOLVED';
resolved_value: T; // required if pick_version is RESOLVED; type depends on the rule field type
}
So each of the properties within fields
in SingleRuleUpgradeRequest
should be of type FieldUpgradeRequest
because each property should be able to be updated to any of the pick_version
s listed in the full enum (which includes RESOLVED
).
Only if the pick_version
value for that field is RESOLVED
, then the resolved_value
can take the value of the prop (RuleName
for the case of the name
property).
I'm not sure how to write the OAS in some other way without that union. Do you have in mind something more simple? Ideas welcome 😄
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.
Maybe instead of having a "generic" FieldUpgradeRequest
we could have something like RuleNameFieldUpgradeRequest
, which directly ref
s to RuleName
. And create a similar schema for each of the updatable fields.
With such an approach we wouldn't need a union, but it would mean a lot more schemas manually created.
What do you think?
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 think we need to list every upgradeable field, something like this:
RuleUpgradeSpecifier:
type: object
properties:
fields:
type: object
properties:
name:
type: object
required:
- pick_version
properties:
pick_version:
$ref: '#/components/schemas/FieldPickVersionValues'
resolved_value:
$ref: '../../model/rule_schema/common_attributes.schema.yaml#/components/schemas/RuleName'
tags:
type: object
required:
- pick_version
properties:
pick_version:
$ref: '#/components/schemas/FieldPickVersionValues'
resolved_value:
$ref: '../../model/rule_schema/common_attributes.schema.yaml#/components/schemas/RuleTagArray'
This approach is a bit more verbose than using a union with all possible values, but it will produce the most accurate type. I believe the effort is worth it.
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.
LGTM, cr only.
af446ba
to
c30b261
Compare
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.
Thanks @jpdjere! I've taken a look, left a few questions.
...ty_solution/docs/openapi/ess/security_solution_detections_api_2023_10_31.bundled.schema.yaml
Outdated
Show resolved
Hide resolved
.../detection_engine/prebuilt_rules/perform_rule_upgrade/perform_rule_upgrade_route.schema.yaml
Show resolved
Hide resolved
required: | ||
- pick_version | ||
properties: | ||
pick_version: |
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.
Should it be oneOf
? Either pick_version
or resolved_value
need to be specified?
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.
No, resolved_value
should only be defined if pick_version
is RESOLVED
. And it should contain the "resolved" value -as passed by the user in the request- that the field should be updated to.
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.
Thanks, makes sense now! Do you think we could make the type more precise, something like:
{
pick_version: 'BASE' | 'CURRENT' | 'TARGET' | 'MERGED'
} |
{
pick_version: 'RESOLVED',
resolved_value: <value>
}
.../detection_engine/prebuilt_rules/perform_rule_upgrade/perform_rule_upgrade_route.schema.yaml
Show resolved
Hide resolved
title: Perform Rule Upgrade API endpoint | ||
version: '2023-10-31' | ||
paths: | ||
/api/detection_engine/rules/prebuilt/_perform_upgrade: |
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.
/api/detection_engine/rules/prebuilt/_perform_upgrade: | |
/internal/detection_engine/prebuilt_rules/upgrade/_perform: |
openapi: 3.0.0 | ||
info: | ||
title: Perform Rule Upgrade API endpoint | ||
version: '2023-10-31' |
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.
version: '2023-10-31' | |
version: '1' |
paths: | ||
/api/detection_engine/rules/prebuilt/_perform_upgrade: | ||
post: | ||
x-labels: [ess] |
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.
x-labels: [ess] | |
x-labels: [ess, serverless] |
errors: | ||
type: array | ||
items: | ||
$ref: '../../model/error_schema.schema.yaml#/components/schemas/ErrorSchema' |
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.
Shouldn't it be aggregated error?
Lines 8 to 15 in 329e696
export interface AggregatedPrebuiltRuleError { | |
message: string; | |
status_code?: number; | |
rules: Array<{ | |
rule_id: string; | |
name?: string; | |
}>; | |
} |
ErrorSchema is pretty different:
kibana/x-pack/plugins/security_solution/common/api/detection_engine/model/error_schema.gen.ts
Lines 21 to 33 in 329e696
export type ErrorSchema = z.infer<typeof ErrorSchema>; | |
export const ErrorSchema = z | |
.object({ | |
id: z.string().optional(), | |
rule_id: RuleSignatureId.optional(), | |
list_id: z.string().min(1).optional(), | |
item_id: z.string().min(1).optional(), | |
error: z.object({ | |
status_code: z.number().int().min(400), | |
message: z.string(), | |
}), | |
}) | |
.strict(); |
description: | | ||
Fields that can be customized during the upgrade workflow | ||
as decided in: https://github.com/elastic/kibana/issues/186544 | ||
Fields listed here, which are not specified in the request body, | ||
will default to a `pick_version` of `MERGED`. |
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.
Descriptions become a part of our public documentation. We should avoid putting technical details or links to GitHub issues in the description.
Also, is this part still accurate?
Fields listed here, which are not specified in the request body,
will default to a `pick_version` of `MERGED`.
Shouldn't omitted fields default to the high-level pick_version
?
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.
Shouldn't omitted fields default to the high-level pick_version?
Yes and no. Omitted fields will default to the higher-level pick_version
, but only if one is defined (notice that pick_version
is not required
at any higher level, only on each field within fields
). So I think the behaviour should be:
- Use the
pick_version
defined within each rule field. - If the field is not defined, update that field to:
2.1.MERGED
if no higher-levelpick_version
is defined.
2.2. Otherwise: thepick_version
passed on theRuleUpgradeSpecifier
, if defined.
2.3. Otherwise: thepick_version
passed on theUpgradeSpecificRulesRequest
, if defined.
2.4. Otherwise (nopick_version
passed on individual fields or at any level): useMERGED
as default.
Same for UpgradeAllRulesRequest
: here use MERGED
as default, unless pick_version
is explicitly passed.
What do you think? The alternative to this would be to make the top-level pick_version
required so that we can default to that instead of MERGED
.
rule_id: | ||
$ref: '../../model/rule_schema/common_attributes.schema.yaml#/components/schemas/RuleSignatureId' | ||
revision: | ||
type: number | ||
version: | ||
$ref: '../../model/rule_schema/common_attributes.schema.yaml#/components/schemas/RuleVersion' | ||
pick_version: | ||
$ref: '#/components/schemas/PickVersionValues' |
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.
It would be super helpful to provide some description for other fields as well
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.
Yes, added to the fields here. What do you think about the description for revision?
Rule's current revision number. Should match the rule's revision number returned from the Review Rule Upgrade API endpoint.
I think that is understandable by the user (mentioning the upgrade endpoint), but is not technically correct. If a request slips in between calling /upgrade and /perform, the second request will fail because the revision number won't match. But that's hard to explain here.
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.
@jpdjere I would like to avoid migrating prebuilt rules API endpoints to OpenAPI at this stage. This can require significant effort, since a lot of types like ThreeWayDiff, RuleFieldsDiff, DiffableRule, diff algorithms are all generics. Some of them are a part of the API contract.
My proposal is to extend the perform's API request schema using the current tech used for Prebuilt Rules API. This will allow us to iterate on the perform endpoint implementation much sooner.
When we have implemented the API behavior required for Milestone 3 + a comprehensive integration test coverage for it, we will plan a migration to OpenAPI. Tests would be a safety net for this significant refactoring.
💔 Build FailedFailed CI Steps
Test Failures
Metrics [docs]
History
To update your PR or re-run it, just comment with: cc @jpdjere |
Closing in favour of: #189790 |
…tract migrating to Zod (#189790) Partially addresses (contract change only): #166376 Created in favour of: #189187 (closed) ## Summary - Extends contract as described in the [POC](#144060), migrating from `io-ts` to Zod (search for `Perform rule upgrade`) - Uses new types in endpoint, but functionality remains unchaged. ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
Partially addresses (contract change only): #166376
Summary
/upgrade/_perform
endpoint.Perform rule upgrade
)/upgrade/_perform
endpoint upgrade workflow #186544For maintainers