[9.0] [Security Solution] Allow disabling experimental features via config (#217363) (#218429)

# Backport

This will backport the following commits from `main` to `9.0`:
- [[Security Solution] Allow disabling experimental features via config
(#217363)](https://github.com/elastic/kibana/pull/217363)

<!--- Backport version: 9.6.6 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)

<!--BACKPORT [{"author":{"name":"Sergi
Massaneda","email":"sergi.massaneda@elastic.co"},"sourceCommit":{"committedDate":"2025-04-16T12:09:28Z","message":"[Security
Solution] Allow disabling experimental features via config
(#217363)\n\n## Summary\n\nThis PR adds support for disabling
experimental features using the\nexisting
`xpack.securitySolution.enableExperimental` configuration.\n\nThis
solves the problem of not being able to disable a feature by
config\nonce the feature has been enabled by default.\n\n### The
Challenge \n\nWhen we start developing a feature under an experimental
flag we always\nfollow the same steps:\n\n1 - Create the experimental
flag disabled by default + enable it via\nconfig for testing\n2 -
Implement the feature\n3 - Enable the experimental flag by default when
we want to release the\nfeature.\n4 - Deployments can disable the
feature via config (as a safety\nmeasure).\n5 - Remove the experimental
flag after some time.\n\nWe start by creating the flag disabled by
default while we implement it.\nIn
`experimental_features.ts`:\n```ts\nexport const
allowedExperimentalValues = Object.freeze({\n myFeatureEnabled: false,\n
[...]\n```\nAnd enable it via config
with:\n```yml\nxpack.securitySolution.enableExperimental:\n -
myFeatureEnabled\n```\n\nOnce the implementation is done and the
experimental flag can be enabled\nby default, we have to do a
trick:\nSince the `xpack.securitySolution.enableExperimental` config can
only\nturn flags to _true_, instead of setting `myFeatureEnabled: true`,
what\nwe have to do is rename the flag to `myFeatureDisabled` and keep
the\nvalue as _false_:\n\n```ts\nexport const allowedExperimentalValues
= Object.freeze({\n myFeatureDisabled: false,\n [...]\n```\nThen we also
need to do a code refactor to update all the places in the\ncode where
the flag was checked: `if (myFeatureEnabled)` ->
`if\n(!myFeatureDisabled)`\n\nThis way, we have the option of disabling
the feature via config (in\ncase something goes
wrong):\n```yml\nxpack.securitySolution.enableExperimental:\n -
myFeatureDisabled\n```\n\n### A solution\n\nThis PR introduces the
possibility to turn a flag to _false_ using the\nsame
`xpack.securitySolution.enableExperimental` config. This was\npreferable
to introducing a new config since this one is already\nwhitelisted in
Cloud UI, can be easily overritten in deployments, and\nalso because
people are used to it.\n\nWith these changes, the first two steps would
be the same, with the\ndifference that we won't need to have the
_Enabled_ or _Disabled_ word\nat the end of the flag name. It could be
just the feature name, in\n`experimental_features.ts`:\n```ts\nexport
const allowedExperimentalValues = Object.freeze({\n myFeature: false,\n
[...]\n```\n\nAnd when we need to enable the feature by default, we can
just turn it\nto `true`:\n```ts\nexport const allowedExperimentalValues
= Object.freeze({\n myFeature: true,\n [...]\n```\nNo tedious refactor
or confusing naming would be required. \n\nThen, in case we need to
disable the feature in a production deployment\nfor some reason, we
could just do this via config
:\n```yml\nxpack.securitySolution.enableExperimental:\n -
disable:myFeature\n```\n\n---------\n\nCo-authored-by: Elastic Machine
<elasticmachine@users.noreply.github.com>","sha":"937dbba41ef6d52b1d060f03f0e2d9a99247016e","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:
SecuritySolution","backport:version","v8.18.0","v9.1.0","v8.19.0","v9.0.1"],"title":"[Security
Solution] Allow disabling experimental features via
config","number":217363,"url":"https://github.com/elastic/kibana/pull/217363","mergeCommit":{"message":"[Security
Solution] Allow disabling experimental features via config
(#217363)\n\n## Summary\n\nThis PR adds support for disabling
experimental features using the\nexisting
`xpack.securitySolution.enableExperimental` configuration.\n\nThis
solves the problem of not being able to disable a feature by
config\nonce the feature has been enabled by default.\n\n### The
Challenge \n\nWhen we start developing a feature under an experimental
flag we always\nfollow the same steps:\n\n1 - Create the experimental
flag disabled by default + enable it via\nconfig for testing\n2 -
Implement the feature\n3 - Enable the experimental flag by default when
we want to release the\nfeature.\n4 - Deployments can disable the
feature via config (as a safety\nmeasure).\n5 - Remove the experimental
flag after some time.\n\nWe start by creating the flag disabled by
default while we implement it.\nIn
`experimental_features.ts`:\n```ts\nexport const
allowedExperimentalValues = Object.freeze({\n myFeatureEnabled: false,\n
[...]\n```\nAnd enable it via config
with:\n```yml\nxpack.securitySolution.enableExperimental:\n -
myFeatureEnabled\n```\n\nOnce the implementation is done and the
experimental flag can be enabled\nby default, we have to do a
trick:\nSince the `xpack.securitySolution.enableExperimental` config can
only\nturn flags to _true_, instead of setting `myFeatureEnabled: true`,
what\nwe have to do is rename the flag to `myFeatureDisabled` and keep
the\nvalue as _false_:\n\n```ts\nexport const allowedExperimentalValues
= Object.freeze({\n myFeatureDisabled: false,\n [...]\n```\nThen we also
need to do a code refactor to update all the places in the\ncode where
the flag was checked: `if (myFeatureEnabled)` ->
`if\n(!myFeatureDisabled)`\n\nThis way, we have the option of disabling
the feature via config (in\ncase something goes
wrong):\n```yml\nxpack.securitySolution.enableExperimental:\n -
myFeatureDisabled\n```\n\n### A solution\n\nThis PR introduces the
possibility to turn a flag to _false_ using the\nsame
`xpack.securitySolution.enableExperimental` config. This was\npreferable
to introducing a new config since this one is already\nwhitelisted in
Cloud UI, can be easily overritten in deployments, and\nalso because
people are used to it.\n\nWith these changes, the first two steps would
be the same, with the\ndifference that we won't need to have the
_Enabled_ or _Disabled_ word\nat the end of the flag name. It could be
just the feature name, in\n`experimental_features.ts`:\n```ts\nexport
const allowedExperimentalValues = Object.freeze({\n myFeature: false,\n
[...]\n```\n\nAnd when we need to enable the feature by default, we can
just turn it\nto `true`:\n```ts\nexport const allowedExperimentalValues
= Object.freeze({\n myFeature: true,\n [...]\n```\nNo tedious refactor
or confusing naming would be required. \n\nThen, in case we need to
disable the feature in a production deployment\nfor some reason, we
could just do this via config
:\n```yml\nxpack.securitySolution.enableExperimental:\n -
disable:myFeature\n```\n\n---------\n\nCo-authored-by: Elastic Machine
<elasticmachine@users.noreply.github.com>","sha":"937dbba41ef6d52b1d060f03f0e2d9a99247016e"}},"sourceBranch":"main","suggestedTargetBranches":["9.0","8.18","8.x"],"targetPullRequestStates":[{"branch":"9.0","label":"v9.0.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.18","label":"v8.18.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/217363","number":217363,"mergeCommit":{"message":"[Security
Solution] Allow disabling experimental features via config
(#217363)\n\n## Summary\n\nThis PR adds support for disabling
experimental features using the\nexisting
`xpack.securitySolution.enableExperimental` configuration.\n\nThis
solves the problem of not being able to disable a feature by
config\nonce the feature has been enabled by default.\n\n### The
Challenge \n\nWhen we start developing a feature under an experimental
flag we always\nfollow the same steps:\n\n1 - Create the experimental
flag disabled by default + enable it via\nconfig for testing\n2 -
Implement the feature\n3 - Enable the experimental flag by default when
we want to release the\nfeature.\n4 - Deployments can disable the
feature via config (as a safety\nmeasure).\n5 - Remove the experimental
flag after some time.\n\nWe start by creating the flag disabled by
default while we implement it.\nIn
`experimental_features.ts`:\n```ts\nexport const
allowedExperimentalValues = Object.freeze({\n myFeatureEnabled: false,\n
[...]\n```\nAnd enable it via config
with:\n```yml\nxpack.securitySolution.enableExperimental:\n -
myFeatureEnabled\n```\n\nOnce the implementation is done and the
experimental flag can be enabled\nby default, we have to do a
trick:\nSince the `xpack.securitySolution.enableExperimental` config can
only\nturn flags to _true_, instead of setting `myFeatureEnabled: true`,
what\nwe have to do is rename the flag to `myFeatureDisabled` and keep
the\nvalue as _false_:\n\n```ts\nexport const allowedExperimentalValues
= Object.freeze({\n myFeatureDisabled: false,\n [...]\n```\nThen we also
need to do a code refactor to update all the places in the\ncode where
the flag was checked: `if (myFeatureEnabled)` ->
`if\n(!myFeatureDisabled)`\n\nThis way, we have the option of disabling
the feature via config (in\ncase something goes
wrong):\n```yml\nxpack.securitySolution.enableExperimental:\n -
myFeatureDisabled\n```\n\n### A solution\n\nThis PR introduces the
possibility to turn a flag to _false_ using the\nsame
`xpack.securitySolution.enableExperimental` config. This was\npreferable
to introducing a new config since this one is already\nwhitelisted in
Cloud UI, can be easily overritten in deployments, and\nalso because
people are used to it.\n\nWith these changes, the first two steps would
be the same, with the\ndifference that we won't need to have the
_Enabled_ or _Disabled_ word\nat the end of the flag name. It could be
just the feature name, in\n`experimental_features.ts`:\n```ts\nexport
const allowedExperimentalValues = Object.freeze({\n myFeature: false,\n
[...]\n```\n\nAnd when we need to enable the feature by default, we can
just turn it\nto `true`:\n```ts\nexport const allowedExperimentalValues
= Object.freeze({\n myFeature: true,\n [...]\n```\nNo tedious refactor
or confusing naming would be required. \n\nThen, in case we need to
disable the feature in a production deployment\nfor some reason, we
could just do this via config
:\n```yml\nxpack.securitySolution.enableExperimental:\n -
disable:myFeature\n```\n\n---------\n\nCo-authored-by: Elastic Machine
<elasticmachine@users.noreply.github.com>","sha":"937dbba41ef6d52b1d060f03f0e2d9a99247016e"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Sergi Massaneda <sergi.massaneda@elastic.co>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2025-04-17 11:19:10 +02:00 committed by GitHub
parent 3fc06a8092
commit 25cef6d35b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -263,9 +263,12 @@ type Mutable<T> = { -readonly [P in keyof T]: T[P] };
const allowedKeys = Object.keys(allowedExperimentalValues) as Readonly<ExperimentalConfigKeys>;
const disableExperimentalPrefix = 'disable:' as const;
/**
* Parses the string value used in `xpack.securitySolution.enableExperimental` kibana configuration,
* which should be a string of values delimited by a comma (`,`)
* which should be an array of strings corresponding to allowedExperimentalValues keys.
* Use the `disable:` prefix to disable a feature.
*
* @param configValue
* @throws SecuritySolutionInvalidExperimentalValue
@ -276,11 +279,15 @@ export const parseExperimentalConfigValue = (
const enabledFeatures: Mutable<Partial<ExperimentalFeatures>> = {};
const invalidKeys: string[] = [];
for (const value of configValue) {
for (let value of configValue) {
const isDisabled = value.startsWith(disableExperimentalPrefix);
if (isDisabled) {
value = value.replace(disableExperimentalPrefix, '');
}
if (!allowedKeys.includes(value as keyof ExperimentalFeatures)) {
invalidKeys.push(value);
} else {
enabledFeatures[value as keyof ExperimentalFeatures] = true;
enabledFeatures[value as keyof ExperimentalFeatures] = !isDisabled;
}
}