-
Notifications
You must be signed in to change notification settings - Fork 8.6k
[Elastic Assistant] React to FF updates #223429
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
Changes from all commits
c2afdfd
6777461
b9ed174
f67421b
91f6c9d
1afbb95
4df3877
8920d2b
fe706b2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,5 +21,6 @@ | |
| "@kbn/config-schema", | ||
| "@kbn/config-mocks", | ||
| "@kbn/logging-mocks", | ||
| "@kbn/std", | ||
| ] | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,15 +5,21 @@ | |
| * 2.0. | ||
| */ | ||
|
|
||
| import { PluginInitializerContext, CoreStart, Plugin, Logger } from '@kbn/core/server'; | ||
| import type { | ||
| PluginInitializerContext, | ||
| CoreStart, | ||
| Plugin, | ||
| Logger, | ||
| FeatureFlagsStart, | ||
| } from '@kbn/core/server'; | ||
|
|
||
| import { | ||
| ATTACK_DISCOVERY_ALERTS_ENABLED_FEATURE_FLAG, | ||
| ATTACK_DISCOVERY_SCHEDULES_CONSUMER_ID, | ||
| ATTACK_DISCOVERY_SCHEDULES_ENABLED_FEATURE_FLAG, | ||
| AssistantFeatures, | ||
| } from '@kbn/elastic-assistant-common'; | ||
| import { ReplaySubject, type Subject } from 'rxjs'; | ||
| import { ReplaySubject, type Subject, exhaustMap, takeWhile, takeUntil } from 'rxjs'; | ||
| import { ECS_COMPONENT_TEMPLATE_NAME } from '@kbn/alerting-plugin/server'; | ||
| import { Dataset, IRuleDataClient, IndexOptions } from '@kbn/rule-registry-plugin/server'; | ||
| import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; | ||
|
|
@@ -40,6 +46,17 @@ import type { ConfigSchema } from './config_schema'; | |
| import { attackDiscoveryAlertFieldMap } from './lib/attack_discovery/schedules/fields'; | ||
| import { ATTACK_DISCOVERY_ALERTS_CONTEXT } from './lib/attack_discovery/schedules/constants'; | ||
|
|
||
| interface FeatureFlagDefinition { | ||
| featureFlagName: string; | ||
| fallbackValue: boolean; | ||
| /** | ||
| * Function to execute when the feature flag is evaluated. | ||
| * @param enabled If the feature flag is enabled or not. | ||
| * @return `true` if susbscription needs to stay active, `false` if it can be unsubscribed. | ||
| */ | ||
| fn: (enabled: boolean) => boolean | Promise<boolean>; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The subscription/unsubscribe feels a bit complex and easy to get wrong, is it necessary or could we just keep the subscription always?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We covered it on Slack. But this doesn't mean that future feature flags will follow this approach. The security team is free to simplify it, though, if they want to. |
||
| } | ||
|
|
||
| export class ElasticAssistantPlugin | ||
| implements | ||
| Plugin< | ||
|
|
@@ -116,15 +133,11 @@ export class ElasticAssistantPlugin | |
| // to wait for the start services to be available to read the feature flags. | ||
| // This can take a while, but the plugin setup phase cannot run for a long time. | ||
| // As a workaround, this promise does not block the setup phase. | ||
| core | ||
| .getStartServices() | ||
| .then(([{ featureFlags }]) => { | ||
| // read all feature flags: | ||
| void Promise.all([ | ||
| featureFlags.getBooleanValue(ATTACK_DISCOVERY_SCHEDULES_ENABLED_FEATURE_FLAG, false), | ||
| featureFlags.getBooleanValue(ATTACK_DISCOVERY_ALERTS_ENABLED_FEATURE_FLAG, false), | ||
| // add more feature flags here | ||
| ]).then(([assistantAttackDiscoverySchedulingEnabled, attackDiscoveryAlertsEnabled]) => { | ||
| const featureFlagDefinitions: FeatureFlagDefinition[] = [ | ||
| { | ||
| featureFlagName: ATTACK_DISCOVERY_SCHEDULES_ENABLED_FEATURE_FLAG, | ||
| fallbackValue: false, | ||
| fn: (assistantAttackDiscoverySchedulingEnabled) => { | ||
| if (assistantAttackDiscoverySchedulingEnabled) { | ||
| // Register Attack Discovery Schedule type | ||
| plugins.alerting.registerType( | ||
|
|
@@ -135,6 +148,13 @@ export class ElasticAssistantPlugin | |
| }) | ||
| ); | ||
| } | ||
| return !assistantAttackDiscoverySchedulingEnabled; // keep subscription active while the feature flag is disabled | ||
| }, | ||
| }, | ||
| { | ||
| featureFlagName: ATTACK_DISCOVERY_ALERTS_ENABLED_FEATURE_FLAG, | ||
| fallbackValue: false, | ||
| fn: (attackDiscoveryAlertsEnabled) => { | ||
| let adhocAttackDiscoveryDataClient: IRuleDataClient | undefined; | ||
| if (attackDiscoveryAlertsEnabled) { | ||
| // Initialize index for ad-hoc generated attack discoveries | ||
|
|
@@ -157,8 +177,14 @@ export class ElasticAssistantPlugin | |
| ruleDataService.initializeIndex(ruleDataServiceOptions); | ||
| } | ||
| requestContextFactory.setup(adhocAttackDiscoveryDataClient); | ||
| }); | ||
| }) | ||
| return !attackDiscoveryAlertsEnabled; // keep subscription active while the feature flag is disabled. | ||
| }, | ||
| }, | ||
| ]; | ||
|
|
||
| core | ||
| .getStartServices() | ||
| .then(([{ featureFlags }]) => this.evaluateFeatureFlags(featureFlagDefinitions, featureFlags)) | ||
| .catch((error) => { | ||
| this.logger.error(`error in security assistant plugin setup: ${error}`); | ||
| }); | ||
|
|
@@ -214,4 +240,30 @@ export class ElasticAssistantPlugin | |
| this.pluginStop$.next(); | ||
| this.pluginStop$.complete(); | ||
| } | ||
|
|
||
| private evaluateFeatureFlags( | ||
| featureFlagDefinitions: FeatureFlagDefinition[], | ||
| featureFlags: FeatureFlagsStart | ||
| ) { | ||
| featureFlagDefinitions.forEach(({ featureFlagName, fallbackValue, fn }) => { | ||
| featureFlags | ||
| .getBooleanValue$(featureFlagName, fallbackValue) | ||
| .pipe( | ||
| takeUntil(this.pluginStop$), | ||
| exhaustMap(async (enabled) => { | ||
| let continueSubscription = true; | ||
| try { | ||
| continueSubscription = await fn(enabled); | ||
| } catch (error) { | ||
| this.logger.error( | ||
| `Error during setup based on feature flag ${featureFlagName}: ${error}` | ||
| ); | ||
| } | ||
| return continueSubscription; | ||
| }), | ||
| takeWhile((continueSubscription) => continueSubscription) | ||
| ) | ||
| .subscribe(); | ||
| }); | ||
| } | ||
| } | ||
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.
we're now flattening the object as we receive the overrides to make sure that we capture all flag names