Skip to content

Commit 9556de5

Browse files
kibanamachinesemd
andauthored
[8.18] [Security Solution] Allow disabling experimental features via config (elastic#217363) (elastic#218427)
# Backport This will backport the following commits from `main` to `8.18`: - [[Security Solution] Allow disabling experimental features via config (elastic#217363)](elastic#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":"[email protected]"},"sourceCommit":{"committedDate":"2025-04-16T12:09:28Z","message":"[Security Solution] Allow disabling experimental features via config (elastic#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 <[email protected]>","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 (elastic#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 <[email protected]>","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 (elastic#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 <[email protected]>","sha":"937dbba41ef6d52b1d060f03f0e2d9a99247016e"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Sergi Massaneda <[email protected]>
1 parent cea79ff commit 9556de5

File tree

1 file changed

+10
-3
lines changed

1 file changed

+10
-3
lines changed

x-pack/solutions/security/plugins/security_solution/common/experimental_features.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -263,9 +263,12 @@ type Mutable<T> = { -readonly [P in keyof T]: T[P] };
263263

264264
const allowedKeys = Object.keys(allowedExperimentalValues) as Readonly<ExperimentalConfigKeys>;
265265

266+
const disableExperimentalPrefix = 'disable:' as const;
267+
266268
/**
267269
* Parses the string value used in `xpack.securitySolution.enableExperimental` kibana configuration,
268-
* which should be a string of values delimited by a comma (`,`)
270+
* which should be an array of strings corresponding to allowedExperimentalValues keys.
271+
* Use the `disable:` prefix to disable a feature.
269272
*
270273
* @param configValue
271274
* @throws SecuritySolutionInvalidExperimentalValue
@@ -276,11 +279,15 @@ export const parseExperimentalConfigValue = (
276279
const enabledFeatures: Mutable<Partial<ExperimentalFeatures>> = {};
277280
const invalidKeys: string[] = [];
278281

279-
for (const value of configValue) {
282+
for (let value of configValue) {
283+
const isDisabled = value.startsWith(disableExperimentalPrefix);
284+
if (isDisabled) {
285+
value = value.replace(disableExperimentalPrefix, '');
286+
}
280287
if (!allowedKeys.includes(value as keyof ExperimentalFeatures)) {
281288
invalidKeys.push(value);
282289
} else {
283-
enabledFeatures[value as keyof ExperimentalFeatures] = true;
290+
enabledFeatures[value as keyof ExperimentalFeatures] = !isDisabled;
284291
}
285292
}
286293

0 commit comments

Comments
 (0)