-
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
[Alerting] Exempt Alerts pre 7.10 from RBAC on their Action execution until updated #75563
Conversation
This reverts commit 04054fb.
* master: (340 commits) [data.search.SearchSource] Remove legacy ES client APIs. (elastic#75943) [release notes] automatically retry on Github API 5xx errors (elastic#76447) [es_ui_shared] Fix eslint exhaustive deps rule (elastic#76392) [i18n] Integrate 7.9.1 Translations (elastic#76391) [APM] Update aggregations to support script sources (elastic#76429) [Security Solution] Refactor Network Top Countries to use Search Strategy (elastic#76244) Document security settings available on ESS (elastic#76513) [Ingest Manager] Add input revision to the config send to the agent (elastic#76327) [DOCS] Identifies cloud settings for Monitoring (elastic#76579) [DOCS] Identifies Cloud settings in Dev Tools (elastic#76583) [Ingest Manager] Better default value for fleet long polling timeout (elastic#76393) [data.indexPatterns] Fix broken rollup index pattern creation (elastic#76593) [Ingest Manager] Split Registry errors into Connection & Response (elastic#76558) [Security Solution] add an excess validation instead of the exact match (elastic#76472) Introduce TS incremental builds & move src/test_utils to TS project (elastic#76082) fix bad merge (elastic#76629) [Newsfeed] Ensure the version format when calling the API (elastic#76381) remove server_extensions mixin (elastic#76606) Remove legacy applications and legacy mode (elastic#75987) [Discover] Fix sidebar element focus behavior when adding / removing columns (elastic#75749) ...
* master: (47 commits) Do not require id & description when creating a logstash pipeline (elastic#76616) Remove commented src/core/tsconfig file (elastic#76792) Replaced whitelistedHosts with allowedHosts in actions ascii docs (elastic#76731) [Dashboard First] Genericize Attribute Service (elastic#76057) [ci-metrics] unify distributable file count metrics (elastic#76448) [Security Solution][Detections] Handle conflicts on alert status update (elastic#75492) [eslint] convert to @typescript-eslint/no-unused-expressions (elastic#76471) [DOCS] Add default time range filter to advanced settings (elastic#76414) [Security Solution] Refactor NetworkTopNFlow to use Search Strategy (elastic#76249) [Dashboard] Update Index Patterns when Child Index Patterns Change (elastic#76356) [ML] Add option to Advanced Settings to set default time range filter for AD jobs (elastic#76347) Add CSM app to CODEOWNERS (elastic#76793) [Security Solution][Exceptions] - Updates exception item find sort field (elastic#76685) [Security Solution][Detections][Tech Debt] - Move to using common io-ts types (elastic#75009) [Lens] Drag dimension to replace (elastic#75895) URI encode the index names we fetch in the fetchIndices lib function. (elastic#76584) [Security Solution] Resolver retrieve entity id of documents without field mapped (elastic#76562) [Ingest Manager] validate agent route using AJV instead kbn-config-schema (elastic#76546) Updated non-dev usages of node-forge (elastic#76699) [Ingest Pipelines] Processor forms for processors K-S (elastic#75638) ...
* master: [APM] Use observer.hostname instead of observer.name (elastic#76074) Legacy logging: fix remoteAddress being duplicated in userAgent field (elastic#76751) Remove legacy deprecation adapter (elastic#76753) [Security Solution][Detections] Rule forms cleanup (elastic#76138) Adds back in custom images for reporting + tests (elastic#76810) [APM] Fix overlapping transaction names (elastic#76083) [Ingest Pipelines] Add descriptions for ingest processors A-D (elastic#75975)
Pinging @elastic/kibana-alerting-services (Team:Alerting Services) |
if (!(await shouldLegacyRbacApplyBySource(this.unsecuredSavedObjectsClient, source))) { | ||
await this.authorization.ensureAuthorized('execute'); | ||
} |
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 hoping to keep this kind of check limited to the Authorization class itself, but as this authorization will have been initialized prior to us knowing what the source is, that isn't really possible without adding the source
as an argument to the ensureAuthorized
method, which actually felt like a worse solution as most operations won't have that.
Considering we'll be able to remove this all together in 8.0.0 I think it's best to keep this check here as is.
this.auditLogger.actionsAuthorizationSuccess(username, operation, actionTypeId); | ||
if (this.isOperationExemptDueToLegacyRbac(operation)) { | ||
this.auditLogger.actionsAuthorizationSuccess( | ||
this.authentication?.getCurrentUser(this.request)?.username ?? '', | ||
operation, | ||
actionTypeId | ||
); |
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.
Even if we skip the auth check - we should log this to the audit log!
spaceId: string; | ||
apiKey: string | null; | ||
} | ||
|
||
export type ExecutionEnqueuer = ( | ||
savedObjectsClient: SavedObjectsClientContract, | ||
unsecuredSavedObjectsClient: SavedObjectsClientContract, |
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.
This was unsecured before, but it wasn't named as such. As we've marked the other ones in this way, it felt like we should do that here too.
'7.10.0': (doc: SavedObjectUnsanitizedDoc<RawAlert>, context: SavedObjectMigrationContext) => { | ||
if (doc.attributes.consumer === 'alerting') { | ||
return executeMigration(doc, context, alertsMigration); | ||
} else if (doc.attributes.consumer === 'metrics') { | ||
return executeMigration(doc, context, infrastructureMigration); | ||
} | ||
return doc; | ||
}, |
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.
As we now migrate every single Saved Object as part of this upgrade, it felt cleaner to flatten this out.
@@ -122,7 +126,7 @@ export interface RawAlert extends SavedObjectAttributes { | |||
schedule: SavedObjectAttributes; | |||
actions: RawAlertAction[]; | |||
params: SavedObjectAttributes; | |||
scheduledTaskId?: string; | |||
scheduledTaskId?: string | null; |
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.
Seems we were setting this as Null
in some places, but the type system got confused and didn't notice it.
Adding the updateMeta
call revealed the mistake and forced this addition.
I did a quick view at the core changes and it seems to be heading in the right direction 👍 |
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.
Changes LGTM once the alerts client changes are applied.
I am also curious on the performance impact this will have when migrating > 2000 alerts (ex: SIEM rules).
* master: (68 commits) a11y tests on spaces home page including feature control (elastic#76515) [ML] Transforms list: persist pagination through refresh interval (elastic#76786) [ML] Replace all use of date_histogram interval with fixed_interval (elastic#76876) [Timelion] Update timelion deprecation links (elastic#77008) [Security Solution] Refactor Network Details to use Search Strategy (elastic#76928) Upgrade elastic charts to 21.1.2 (elastic#76939) [Alerting][Connectors] Refactor Jira: Generic Implementation (phase one) (elastic#73778) [Snapshot & Restore] fix pre existing policy with no existing repository (elastic#76861) Update saved object management UI text (elastic#76826) [Form lib] Add validations prop to UseArray and expose "moveItem" handler (elastic#76949) [Logs UI] Use fields api in log stream (elastic#76919) [UI Metrics] Support multi-colon keys (elastic#76913) [APM] Script for creating functional test archive (elastic#76926) [ENDPOINT] First version of the trusted apps list. (elastic#76304) Correct field for rum page url (elastic#76916) [Security Solution] Fix redirect properly old SIEM App routes (elastic#76868) Bump http-proxy from 1.17.0 to 1.18.1 (elastic#76924) [RUM Dashboard] Visitor breakdown usability (elastic#76834) [Search] Add a new advanced setting searchTimeout (elastic#75728) [DOCS] Adds timelion deprecation to new visualize docs structure (elastic#76959) ...
@elasticmachine merge upstream |
* master: (25 commits) [bugfix] Replace panel flyout opens 2 flyouts (elastic#76931) clean up test (elastic#76887) [Enterprise Search] Update shared API request handler (elastic#77112) [Maps] convert ESAggSource to TS (elastic#76999) Add plugin status API - take 2 (elastic#76732) Adds lens as a readable saved object for read-only dashboard users (elastic#77067) Skip checking for the reserved realm (elastic#76687) Reporting/diagnostics (elastic#74314) Reporting/Test: unskip non-screenshot tests (elastic#77088) Move metrics to setup and add cgroup metrics (elastic#76730) [Enterprise Search] Add Overview landing page/plugin (elastic#76734) First pass. Change TS type. Update OpenAPI (elastic#76434) [CI] Balance xpack ci groups a bit (elastic#77068) [Security_solution][Detections] Refactor signal ancestry to allow multiple parents (elastic#76531) [Maps] convert MetricsEditor to TS (elastic#76727) IndexMigrator: fix non blocking migration wrapper promise rejection (elastic#77018) [Enterprise Search] Update config data endpoint to v2 (elastic#76970) [ML] Add decision path charts to exploration results table (elastic#73561) Bump eventemitter3 from 4.0.0 to 4.0.7 (elastic#77016) [Ingest Pipelines] Add descriptions for ingest processors K-S (elastic#76981) ...
@elasticmachine merge upstream |
* master: (26 commits) updating datatable type (elastic#77320) [ML] Fix custom URLs processing for security app (elastic#76957) [telemetry] add schema guideline + schema_check new check for --path config (elastic#75747) [ML] Transforms: API schemas and integration tests (elastic#75164) [Mappings editor] Add support for wildcard field type (elastic#76574) [Ingest Manager] Fix flyout instruction selection (elastic#77071) [Telemetry Tools] update lodash to 4.17 (elastic#77317) [APM] Service inventory redesign (elastic#76744) Hide management sections based on cluster/index privileges (elastic#67791) [Snapshot Restore] Disable steps when form is invalid (elastic#76540) [Mappings editor] Add support for positive_score_impact to rank_feature (elastic#76824) Update apm.ts (elastic#77310) [OBS] Remove beta badge, change news feed size and add external icon to news feed link (elastic#77164) [Discover] Convert legacy sort to be compatible with multi sort (elastic#76986) [APM] API Snapshot Testing (elastic#77229) [ML] Functional tests - increase wait time for DFA start (elastic#77307) [UiActions][Drilldowns] Fix actions sorting in context menu (elastic#77162) [Drilldowns] Wire up new links to new docs (elastic#77154) Fix APM issue template [Ingest Pipelines] Drop into an empty tree (elastic#76885) ...
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
* master: (25 commits) [Security Solution] Add unit tests for Network search strategy (elastic#77416) [Alerting] Improves performance of the authorization filter in AlertsClient.find by skipping KQL parsing (elastic#77040) [Ingest Manager] Add route for package installation by upload (elastic#77044) [APM-UI][E2E] filter PRs from the uptime GH team (elastic#77359) [APM] Remove useLocation and some minor route improvements (elastic#76343) [Enterprise Search] Update enterpriseSearchRequestHandler to manage range of errors + add handleAPIErrors helper (elastic#77258) [SECURITY_SOLUTION] Task/hostname policy response ux updates (elastic#76444) Move remaining uses of serviceName away from urlParams (elastic#77248) [Lens] Move configuration popover to flyout (elastic#76046) [Ingest Manager] Manually build Fleet kuery with Node arguments (elastic#76589) skip flaky suite (elastic#59975) Neutral-naming in reporting plugin (elastic#77371) [Enterprise Search] Add UserIcon styles (elastic#77385) [RUM Dashboard] Added loading state to visitor breakdown pie charts (elastic#77201) [Ingest Manager] Fix polling for new agent action (elastic#77339) Remote cluster - Functional UI test to change the superuser to a test_user with limited role (elastic#77212) Stacked headers and navigational search (elastic#72331) [ML] DF Analytics creation wizard: Fixing field loading race condition (elastic#77326) [Monitoring] Handle no mappings found for sort and collapse fields (elastic#77099) Add Lens to Recently Accessed (elastic#77249) ...
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, made some comments, none seem critical
@@ -160,7 +162,8 @@ export class AlertsClient { | |||
private readonly getActionsClient: () => Promise<ActionsClient>; | |||
private readonly actionsAuthorization: ActionsAuthorization; | |||
private readonly getEventLogClient: () => Promise<IEventLogClient>; | |||
encryptedSavedObjectsClient: EncryptedSavedObjectsClient; | |||
private readonly encryptedSavedObjectsClient: EncryptedSavedObjectsClient; | |||
private readonly kibanaVersion!: PluginInitializerContext['env']['packageInfo']['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.
I'm curious if this returns, in a dev launch, 8.0.0
or the relevant 7.x version. And if that matters. Presumably it's correct for the official packaged versions of Kibana, wondering if this could affect devs somehow.
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.
Yeah, on trunk it's 8.0.0 which threw me the first time I saw it.
For now it shouldn't cause any issues but it might come up in the future 🤷
@@ -298,13 +299,19 @@ export class ActionsClient { | |||
public async execute({ | |||
actionId, | |||
params, | |||
source, |
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! Looking forward to plumbing this through to the event log, we'll finally be able to track and action execution to an alert, which we can't do today.
@@ -324,6 +328,10 @@ const result = await actionsClient.execute({ | |||
subject: 'My email subject', | |||
body: 'My email body', | |||
}, | |||
source: asSavedObjectExecutionSource({ | |||
id: '573891ae-8c48-49cb-a197-0cd5ec34a88b', |
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.
The spaceId is not in here - presumably not needed today since alerts are space-specific and we will have the space via some other context. I don't think we need to pass it along here, but not completely sure. I think once we start using this value in the event log, we'll definitely need the spaceId, but again I think we can get that from some other context.
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.
Yeah, that's a fair point... the reference
object doesn't support spaceId at the moment so we wouldn't be able to use it even if we add it in.
Once SOs can reference SOs in other spaces, that'll make sense here too.
x-pack/plugins/actions/server/authorization/actions_authorization.ts
Outdated
Show resolved
Hide resolved
encryptedSavedObjectsClient: EncryptedSavedObjectsClient; | ||
actionTypeRegistry: ActionTypeRegistryContract; | ||
eventLogger: IEventLogger; | ||
preconfiguredActions: PreConfiguredAction[]; | ||
proxySettings?: ProxySettings; | ||
} | ||
|
||
export interface ExecuteOptions { | ||
export interface ExecuteOptions<Source = unknown> { |
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.
slightly curious if we have cases where the source isn't provided - seems like we shouldn't - execution via an alert (saved object reference) or http request - but that's just today anyway. Maybe no source make sense in other future action usages ...
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 shouldn't but as we've added it exiting executions (that might exist at upgrade time) won't have it and I wanted to make sure we don't cause breakages on upgrade by assuming it must exist.
@@ -298,8 +315,13 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi | |||
}); | |||
}; | |||
|
|||
// Ensure the public API cannot be used to circumvent authorization | |||
// using our legacy exemption mechanism | |||
const secureGetActionsClientWithRequest = (request: KibanaRequest) => |
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.
Not clear what value this function provides, other than the more explicit name. There are only two usages of it, both look like they could be replaced with getActionsClientWithRequest
. Maybe it's a left-over, previously it did a few more things?
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's preventing external executions from associating their ActionsClient with a legacy source to circumvent RBAC... but that might not be clear enough 🤔
One of the drawbacks of the current design of the ActionsClient is that when you perform an operation like get
you don't really have a way of asking what the context of the operation is (as in, you're getting an Action from the context of a Legacy Alert), so we instantiate the whole client in this dialed down RBAC mode.
We don't want users to be able to do this, only the plugin itself... so we don't expose the a version of getActionsClientWithRequest
which can also set the context......
I'll try to rename things to make this clearer, as it clearly isn't clear enough 😬
"meta": { | ||
"properties": { | ||
"versionApiKeyLastmodified": { | ||
"type": "keyword" |
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.
do we want enabled: false
on here? IOW, does this field need to be searchable? Might be useful, but also contributing to the saved object indexed field bloat
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.
That's a good question.. 🤔
I feel this will be useful in the future, the question is - is there a reason we can't enable it later?
x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts
Show resolved
Hide resolved
x-pack/plugins/actions/server/authorization/should_legacy_rbac_apply_by_source.test.ts
Show resolved
Hide resolved
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 out with two scenarios. One where a user with the superuser
role created a rule (alert) + action in 7.9. Checked out this PR and kibana migrated that rule and the action kicked off 👍.
The other scenario was with a user who had the minimum Security Solution Detections permissions. Created a rule (alert) + action in 7.9 branch. Checked out this PR and kibana successfully migrated that as well. Only caveat was it took around 10 minutes of waiting until task manager picked up the rule (which was set to run at an interval of every 30 seconds) upon which the action began firing.
These changes are definitely getting the job done as far as my testing is concerned. Thank you for these changes!!! LGTM!
That means the rule tried to run a failed, going into retry mode, which currently ignores the scheduling (this should be addressed by this issue: #46001). I'm not sure why it failed, but this does sometimes happen as part of an upgrade. Task manager recovers after the retry interval (5 minutes for every attempt) passes. |
💚 Build SucceededBuild metricsdistributable file count
Saved Objects .kibana field count
History
To update your PR or re-run it, just comment with: |
… until updated (elastic#75563) Marks all Alerts with a `versionApiKeyLastmodified ` field that tracks what version the alert's Api Key was last updated in. We then use this field to exempt legacy alerts (created pre `7.10.0`) in order to use a _dialed down_ version of RBAC which should allow old alerts to continue to function after the upgrade, until they are updates (at which point they will no longer be **Legacy**). More details here: elastic#74858 (comment)
* master: (55 commits) [Grok] Fix missing error message in error toasts (elastic#77499) [Alerting] Exempt Alerts pre 7.10 from RBAC on their Action execution until updated (elastic#75563) [ML] fix type in apply_influencer_filters_action (elastic#77495) [Lens] create reusable component for filters and range aggregation (elastic#77453) fix eslint violations Collapse alert chart previews by default (elastic#77479) [ML] Fixing field caps wrapper endpoint (elastic#77546) showing service maps when filte by environment not defined (elastic#77483) [Lens] Settings panel redesign and separate settings per y axis (elastic#76373) log request body in new ES client (elastic#77150) use `navigateToUrl` to navigate to recent nav links (elastic#77446) Move core config service to `kbn/config` package (elastic#76874) [UBI] Copy license to /licenses folder (elastic#77563) Skip flaky Events Viewer Cypress test [Lens] Remove dynamic names in telemetry fields (elastic#76988) [Maps] Add DynamicStyleProperty#getMbPropertyName and DynamicStyleProperty#getMbPropertyValue (elastic#77366) [Enterprise Search] Add flag to restrict width of layout (elastic#77539) [Security Solutions][Cases - Timeline] Fix bug when adding a timeline to a case (elastic#76967) [Security Solution][Detections] Integration test for Editing a Rule (elastic#77090) [Ingest pipelines] Polish pipeline debugging workflow (elastic#76058) ...
* master: (54 commits) [Grok] Fix missing error message in error toasts (elastic#77499) [Alerting] Exempt Alerts pre 7.10 from RBAC on their Action execution until updated (elastic#75563) [ML] fix type in apply_influencer_filters_action (elastic#77495) [Lens] create reusable component for filters and range aggregation (elastic#77453) fix eslint violations Collapse alert chart previews by default (elastic#77479) [ML] Fixing field caps wrapper endpoint (elastic#77546) showing service maps when filte by environment not defined (elastic#77483) [Lens] Settings panel redesign and separate settings per y axis (elastic#76373) log request body in new ES client (elastic#77150) use `navigateToUrl` to navigate to recent nav links (elastic#77446) Move core config service to `kbn/config` package (elastic#76874) [UBI] Copy license to /licenses folder (elastic#77563) Skip flaky Events Viewer Cypress test [Lens] Remove dynamic names in telemetry fields (elastic#76988) [Maps] Add DynamicStyleProperty#getMbPropertyName and DynamicStyleProperty#getMbPropertyValue (elastic#77366) [Enterprise Search] Add flag to restrict width of layout (elastic#77539) [Security Solutions][Cases - Timeline] Fix bug when adding a timeline to a case (elastic#76967) [Security Solution][Detections] Integration test for Editing a Rule (elastic#77090) [Ingest pipelines] Polish pipeline debugging workflow (elastic#76058) ...
… until updated (#75563) (#77595) Marks all Alerts with a `versionApiKeyLastmodified ` field that tracks what version the alert's Api Key was last updated in. We then use this field to exempt legacy alerts (created pre `7.10.0`) in order to use a _dialed down_ version of RBAC which should allow old alerts to continue to function after the upgrade, until they are updates (at which point they will no longer be **Legacy**). More details here: #74858 (comment)
* master: (76 commits) Fixing service maps API test (elastic#77586) [Grok] Fix missing error message in error toasts (elastic#77499) [Alerting] Exempt Alerts pre 7.10 from RBAC on their Action execution until updated (elastic#75563) [ML] fix type in apply_influencer_filters_action (elastic#77495) [Lens] create reusable component for filters and range aggregation (elastic#77453) fix eslint violations Collapse alert chart previews by default (elastic#77479) [ML] Fixing field caps wrapper endpoint (elastic#77546) showing service maps when filte by environment not defined (elastic#77483) [Lens] Settings panel redesign and separate settings per y axis (elastic#76373) log request body in new ES client (elastic#77150) use `navigateToUrl` to navigate to recent nav links (elastic#77446) Move core config service to `kbn/config` package (elastic#76874) [UBI] Copy license to /licenses folder (elastic#77563) Skip flaky Events Viewer Cypress test [Lens] Remove dynamic names in telemetry fields (elastic#76988) [Maps] Add DynamicStyleProperty#getMbPropertyName and DynamicStyleProperty#getMbPropertyValue (elastic#77366) [Enterprise Search] Add flag to restrict width of layout (elastic#77539) [Security Solutions][Cases - Timeline] Fix bug when adding a timeline to a case (elastic#76967) [Security Solution][Detections] Integration test for Editing a Rule (elastic#77090) ...
Summary
closes #74858
Mark all Alerts with a
versionApiKeyLastmodified
field that tracks what version the alert's Api Key was last updated in. We then use this field to exempt legacy alerts (created pre7.10.0
) in order to use a dialed down version of RBAC which should allow old alerts to continue to function after the upgrade, until they are updates (at which point they will no longer be Legacy).More details here: #74858 (comment)
Checklist
Delete any items that are not applicable to this PR.
For maintainers