[RAC] Fix index names used by RBAC, delete hardcoded map of Kibana features to index names (#109567)

**Ticket:** https://github.com/elastic/kibana/issues/102089

🚨 **This PR is critical for Observability 7.15** 🚨

## Summary

This PR introduces changes that fix the usage of alerts-as-data index naming in RBAC. It builds on top of https://github.com/elastic/kibana/pull/109346 and replaces https://github.com/elastic/kibana/pull/108872.

TODO:

- [x] Address https://github.com/elastic/kibana/pull/109346#pullrequestreview-735158370
- [x] Make changes to `AlertsClient.getAuthorizedAlertsIndices()` so it starts using `RuleDataService` to get index names by feature ids.
- [x] Delete the hardcoded `mapConsumerToIndexName` where we had incorrect index names.
- [x] Close https://github.com/elastic/kibana/pull/108872

### Checklist

Delete any items that are not applicable to this PR.

- [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
This commit is contained in:
Georgii Gorbachev 2021-08-25 16:29:16 +02:00 committed by GitHub
parent 811d3d779f
commit 8ce1d10791
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 162 additions and 126 deletions

View file

@ -26,17 +26,9 @@ export const AlertConsumers = {
export type AlertConsumers = typeof AlertConsumers[keyof typeof AlertConsumers]; export type AlertConsumers = typeof AlertConsumers[keyof typeof AlertConsumers];
export type STATUS_VALUES = 'open' | 'acknowledged' | 'closed' | 'in-progress'; // TODO: remove 'in-progress' after migration to 'acknowledged' export type STATUS_VALUES = 'open' | 'acknowledged' | 'closed' | 'in-progress'; // TODO: remove 'in-progress' after migration to 'acknowledged'
export const mapConsumerToIndexName: Record<AlertConsumers, string | string[]> = { export type ValidFeatureId = AlertConsumers;
apm: '.alerts-observability-apm',
logs: '.alerts-observability.logs',
infrastructure: '.alerts-observability.metrics',
observability: '.alerts-observability',
siem: '.alerts-security.alerts',
uptime: '.alerts-observability.uptime',
};
export type ValidFeatureId = keyof typeof mapConsumerToIndexName;
export const validFeatureIds = Object.keys(mapConsumerToIndexName); export const validFeatureIds = Object.values(AlertConsumers).map((v) => v as string);
export const isValidFeatureId = (a: unknown): a is ValidFeatureId => export const isValidFeatureId = (a: unknown): a is ValidFeatureId =>
typeof a === 'string' && validFeatureIds.includes(a); typeof a === 'string' && validFeatureIds.includes(a);

View file

@ -19,7 +19,7 @@ const testIndices = [
'.ds-metrics-system.process.summary-default-2021.05.25-00000', '.ds-metrics-system.process.summary-default-2021.05.25-00000',
'.kibana_shahzad_9', '.kibana_shahzad_9',
'.kibana-felix-log-stream_8.0.0_001', '.kibana-felix-log-stream_8.0.0_001',
'.kibana_smith_alerts-observability-apm-000001', '.kibana_smith_alerts-observability.apm.alerts-000001',
'.ds-logs-endpoint.events.process-default-2021.05.26-000001', '.ds-logs-endpoint.events.process-default-2021.05.26-000001',
'.kibana_dominiqueclarke54_8.0.0_001', '.kibana_dominiqueclarke54_8.0.0_001',
'.kibana-cmarcondes-19_8.0.0_001', '.kibana-cmarcondes-19_8.0.0_001',
@ -63,7 +63,7 @@ const onlySystemIndices = [
'.ds-metrics-system.process.summary-default-2021.05.25-00000', '.ds-metrics-system.process.summary-default-2021.05.25-00000',
'.kibana_shahzad_9', '.kibana_shahzad_9',
'.kibana-felix-log-stream_8.0.0_001', '.kibana-felix-log-stream_8.0.0_001',
'.kibana_smith_alerts-observability-apm-000001', '.kibana_smith_alerts-observability.apm.alerts-000001',
'.ds-logs-endpoint.events.process-default-2021.05.26-000001', '.ds-logs-endpoint.events.process-default-2021.05.26-000001',
'.kibana_dominiqueclarke54_8.0.0_001', '.kibana_dominiqueclarke54_8.0.0_001',
'.kibana-cmarcondes-19_8.0.0_001', '.kibana-cmarcondes-19_8.0.0_001',
@ -85,7 +85,7 @@ const kibanaNoTaskIndices = [
'.kibana_shahzad_1', '.kibana_shahzad_1',
'.kibana_shahzad_9', '.kibana_shahzad_9',
'.kibana-felix-log-stream_8.0.0_001', '.kibana-felix-log-stream_8.0.0_001',
'.kibana_smith_alerts-observability-apm-000001', '.kibana_smith_alerts-observability.apm.alerts-000001',
'.kibana_dominiqueclarke54_8.0.0_001', '.kibana_dominiqueclarke54_8.0.0_001',
'.kibana-cmarcondes-19_8.0.0_001', '.kibana-cmarcondes-19_8.0.0_001',
'.kibana_dominiqueclarke55-alerts-8.0.0-000001', '.kibana_dominiqueclarke55-alerts-8.0.0-000001',

View file

@ -72,7 +72,6 @@ export function AlertsPage({ routeParams }: AlertsPageProps) {
registrationContexts: [ registrationContexts: [
'observability.apm', 'observability.apm',
'observability.logs', 'observability.logs',
'observability.infrastructure',
'observability.metrics', 'observability.metrics',
'observability.uptime', 'observability.uptime',
], ],

View file

@ -6,6 +6,7 @@
*/ */
import * as t from 'io-ts'; import * as t from 'io-ts';
import { Dataset } from '../../../rule_registry/server';
import { createObservabilityServerRoute } from './create_observability_server_route'; import { createObservabilityServerRoute } from './create_observability_server_route';
import { createObservabilityServerRouteRepository } from './create_observability_server_route_repository'; import { createObservabilityServerRouteRepository } from './create_observability_server_route_repository';
@ -24,7 +25,7 @@ const alertsDynamicIndexPatternRoute = createObservabilityServerRoute({
const { namespace, registrationContexts } = params.query; const { namespace, registrationContexts } = params.query;
const indexNames = registrationContexts.flatMap((registrationContext) => { const indexNames = registrationContexts.flatMap((registrationContext) => {
const indexName = ruleDataService const indexName = ruleDataService
.getRegisteredIndexInfo(registrationContext) .findIndexByName(registrationContext, Dataset.alerts)
?.getPrimaryAlias(namespace); ?.getPrimaryAlias(namespace);
if (indexName != null) { if (indexName != null) {

View file

@ -73,7 +73,7 @@ await plugins.ruleRegistry.createOrUpdateComponentTemplate({
await plugins.ruleRegistry.createOrUpdateIndexTemplate({ await plugins.ruleRegistry.createOrUpdateIndexTemplate({
name: plugins.ruleRegistry.getFullAssetName('apm-index-template'), name: plugins.ruleRegistry.getFullAssetName('apm-index-template'),
body: { body: {
index_patterns: [plugins.ruleRegistry.getFullAssetName('observability-apm*')], index_patterns: [plugins.ruleRegistry.getFullAssetName('observability.apm*')],
composed_of: [ composed_of: [
// Technical component template, required // Technical component template, required
plugins.ruleRegistry.getFullAssetName(TECHNICAL_COMPONENT_TEMPLATE_NAME), plugins.ruleRegistry.getFullAssetName(TECHNICAL_COMPONENT_TEMPLATE_NAME),
@ -85,7 +85,7 @@ await plugins.ruleRegistry.createOrUpdateIndexTemplate({
// Finally, create the rule data client that can be injected into rule type // Finally, create the rule data client that can be injected into rule type
// executors and API endpoints // executors and API endpoints
const ruleDataClient = new RuleDataClient({ const ruleDataClient = new RuleDataClient({
alias: plugins.ruleRegistry.getFullAssetName('observability-apm'), alias: plugins.ruleRegistry.getFullAssetName('observability.apm'),
getClusterClient: async () => { getClusterClient: async () => {
const coreStart = await getCoreStart(); const coreStart = await getCoreStart();
return coreStart.elasticsearch.client.asInternalUser; return coreStart.elasticsearch.client.asInternalUser;

View file

@ -13,14 +13,13 @@ import type {
getEsQueryConfig as getEsQueryConfigTyped, getEsQueryConfig as getEsQueryConfigTyped,
getSafeSortIds as getSafeSortIdsTyped, getSafeSortIds as getSafeSortIdsTyped,
isValidFeatureId as isValidFeatureIdTyped, isValidFeatureId as isValidFeatureIdTyped,
mapConsumerToIndexName as mapConsumerToIndexNameTyped,
STATUS_VALUES, STATUS_VALUES,
ValidFeatureId,
} from '@kbn/rule-data-utils'; } from '@kbn/rule-data-utils';
import { import {
getEsQueryConfig as getEsQueryConfigNonTyped, getEsQueryConfig as getEsQueryConfigNonTyped,
getSafeSortIds as getSafeSortIdsNonTyped, getSafeSortIds as getSafeSortIdsNonTyped,
isValidFeatureId as isValidFeatureIdNonTyped, isValidFeatureId as isValidFeatureIdNonTyped,
mapConsumerToIndexName as mapConsumerToIndexNameNonTyped,
// @ts-expect-error // @ts-expect-error
} from '@kbn/rule-data-utils/target_node/alerts_as_data_rbac'; } from '@kbn/rule-data-utils/target_node/alerts_as_data_rbac';
@ -42,11 +41,11 @@ import {
SPACE_IDS, SPACE_IDS,
} from '../../common/technical_rule_data_field_names'; } from '../../common/technical_rule_data_field_names';
import { ParsedTechnicalFields } from '../../common/parse_technical_fields'; import { ParsedTechnicalFields } from '../../common/parse_technical_fields';
import { Dataset, RuleDataPluginService } from '../rule_data_plugin_service';
const getEsQueryConfig: typeof getEsQueryConfigTyped = getEsQueryConfigNonTyped; const getEsQueryConfig: typeof getEsQueryConfigTyped = getEsQueryConfigNonTyped;
const getSafeSortIds: typeof getSafeSortIdsTyped = getSafeSortIdsNonTyped; const getSafeSortIds: typeof getSafeSortIdsTyped = getSafeSortIdsNonTyped;
const isValidFeatureId: typeof isValidFeatureIdTyped = isValidFeatureIdNonTyped; const isValidFeatureId: typeof isValidFeatureIdTyped = isValidFeatureIdNonTyped;
const mapConsumerToIndexName: typeof mapConsumerToIndexNameTyped = mapConsumerToIndexNameNonTyped;
// TODO: Fix typings https://github.com/elastic/kibana/issues/101776 // TODO: Fix typings https://github.com/elastic/kibana/issues/101776
type NonNullableProps<Obj extends {}, Props extends keyof Obj> = Omit<Obj, Props> & type NonNullableProps<Obj extends {}, Props extends keyof Obj> = Omit<Obj, Props> &
@ -71,6 +70,7 @@ export interface ConstructorOptions {
authorization: PublicMethodsOf<AlertingAuthorization>; authorization: PublicMethodsOf<AlertingAuthorization>;
auditLogger?: AuditLogger; auditLogger?: AuditLogger;
esClient: ElasticsearchClient; esClient: ElasticsearchClient;
ruleDataService: RuleDataPluginService;
} }
export interface UpdateOptions<Params extends AlertTypeParams> { export interface UpdateOptions<Params extends AlertTypeParams> {
@ -115,15 +115,17 @@ export class AlertsClient {
private readonly authorization: PublicMethodsOf<AlertingAuthorization>; private readonly authorization: PublicMethodsOf<AlertingAuthorization>;
private readonly esClient: ElasticsearchClient; private readonly esClient: ElasticsearchClient;
private readonly spaceId: string | undefined; private readonly spaceId: string | undefined;
private readonly ruleDataService: RuleDataPluginService;
constructor({ auditLogger, authorization, logger, esClient }: ConstructorOptions) { constructor(options: ConstructorOptions) {
this.logger = logger; this.logger = options.logger;
this.authorization = authorization; this.authorization = options.authorization;
this.esClient = esClient; this.esClient = options.esClient;
this.auditLogger = auditLogger; this.auditLogger = options.auditLogger;
// If spaceId is undefined, it means that spaces is disabled // If spaceId is undefined, it means that spaces is disabled
// Otherwise, if space is enabled and not specified, it is "default" // Otherwise, if space is enabled and not specified, it is "default"
this.spaceId = this.authorization.getSpaceId(); this.spaceId = this.authorization.getSpaceId();
this.ruleDataService = options.ruleDataService;
} }
private getOutcome( private getOutcome(
@ -666,15 +668,18 @@ export class AlertsClient {
authorizedFeatures.add(ruleType.producer); authorizedFeatures.add(ruleType.producer);
} }
const toReturn = Array.from(authorizedFeatures).flatMap((feature) => { const validAuthorizedFeatures = Array.from(authorizedFeatures).filter(
if (featureIds.includes(feature) && isValidFeatureId(feature)) { (feature): feature is ValidFeatureId =>
if (feature === 'siem') { featureIds.includes(feature) && isValidFeatureId(feature)
return `${mapConsumerToIndexName[feature]}-${this.spaceId}`; );
} else {
return `${mapConsumerToIndexName[feature]}`; const toReturn = validAuthorizedFeatures.flatMap((feature) => {
} const indices = this.ruleDataService.findIndicesByFeature(feature, Dataset.alerts);
if (feature === 'siem') {
return indices.map((i) => `${i.baseName}-${this.spaceId}`);
} else {
return indices.map((i) => i.baseName);
} }
return [];
}); });
return toReturn; return toReturn;

View file

@ -13,6 +13,8 @@ import { loggingSystemMock } from 'src/core/server/mocks';
import { securityMock } from '../../../security/server/mocks'; import { securityMock } from '../../../security/server/mocks';
import { AuditLogger } from '../../../security/server'; import { AuditLogger } from '../../../security/server';
import { alertingAuthorizationMock } from '../../../alerting/server/authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../../alerting/server/authorization/alerting_authorization.mock';
import { ruleDataPluginServiceMock } from '../rule_data_plugin_service/rule_data_plugin_service.mock';
import { RuleDataPluginService } from '../rule_data_plugin_service';
jest.mock('./alerts_client'); jest.mock('./alerts_client');
@ -24,6 +26,7 @@ const alertsClientFactoryParams: AlertsClientFactoryProps = {
getAlertingAuthorization: (_: KibanaRequest) => alertingAuthMock, getAlertingAuthorization: (_: KibanaRequest) => alertingAuthMock,
securityPluginSetup, securityPluginSetup,
esClient: {} as ElasticsearchClient, esClient: {} as ElasticsearchClient,
ruleDataService: (ruleDataPluginServiceMock.create() as unknown) as RuleDataPluginService,
}; };
const fakeRequest = ({ const fakeRequest = ({
@ -64,6 +67,7 @@ describe('AlertsClientFactory', () => {
logger: alertsClientFactoryParams.logger, logger: alertsClientFactoryParams.logger,
auditLogger, auditLogger,
esClient: {}, esClient: {},
ruleDataService: alertsClientFactoryParams.ruleDataService,
}); });
}); });

View file

@ -5,10 +5,11 @@
* 2.0. * 2.0.
*/ */
import { ElasticsearchClient, KibanaRequest, Logger } from 'src/core/server';
import { PublicMethodsOf } from '@kbn/utility-types'; import { PublicMethodsOf } from '@kbn/utility-types';
import { SecurityPluginSetup } from '../../../security/server'; import { ElasticsearchClient, KibanaRequest, Logger } from 'src/core/server';
import { AlertingAuthorization } from '../../../alerting/server'; import { AlertingAuthorization } from '../../../alerting/server';
import { SecurityPluginSetup } from '../../../security/server';
import { RuleDataPluginService } from '../rule_data_plugin_service';
import { AlertsClient } from './alerts_client'; import { AlertsClient } from './alerts_client';
export interface AlertsClientFactoryProps { export interface AlertsClientFactoryProps {
@ -16,6 +17,7 @@ export interface AlertsClientFactoryProps {
esClient: ElasticsearchClient; esClient: ElasticsearchClient;
getAlertingAuthorization: (request: KibanaRequest) => PublicMethodsOf<AlertingAuthorization>; getAlertingAuthorization: (request: KibanaRequest) => PublicMethodsOf<AlertingAuthorization>;
securityPluginSetup: SecurityPluginSetup | undefined; securityPluginSetup: SecurityPluginSetup | undefined;
ruleDataService: RuleDataPluginService | null;
} }
export class AlertsClientFactory { export class AlertsClientFactory {
@ -26,6 +28,7 @@ export class AlertsClientFactory {
request: KibanaRequest request: KibanaRequest
) => PublicMethodsOf<AlertingAuthorization>; ) => PublicMethodsOf<AlertingAuthorization>;
private securityPluginSetup!: SecurityPluginSetup | undefined; private securityPluginSetup!: SecurityPluginSetup | undefined;
private ruleDataService!: RuleDataPluginService | null;
public initialize(options: AlertsClientFactoryProps) { public initialize(options: AlertsClientFactoryProps) {
/** /**
@ -40,6 +43,7 @@ export class AlertsClientFactory {
this.logger = options.logger; this.logger = options.logger;
this.esClient = options.esClient; this.esClient = options.esClient;
this.securityPluginSetup = options.securityPluginSetup; this.securityPluginSetup = options.securityPluginSetup;
this.ruleDataService = options.ruleDataService;
} }
public async create(request: KibanaRequest): Promise<AlertsClient> { public async create(request: KibanaRequest): Promise<AlertsClient> {
@ -50,6 +54,7 @@ export class AlertsClientFactory {
authorization: getAlertingAuthorization(request), authorization: getAlertingAuthorization(request),
auditLogger: securityPluginSetup?.audit.asScoped(request), auditLogger: securityPluginSetup?.audit.asScoped(request),
esClient: this.esClient, esClient: this.esClient,
ruleDataService: this.ruleDataService!,
}); });
} }
} }

View file

@ -18,6 +18,8 @@ import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mo
import { alertingAuthorizationMock } from '../../../../alerting/server/authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../../../alerting/server/authorization/alerting_authorization.mock';
import { AuditLogger } from '../../../../security/server'; import { AuditLogger } from '../../../../security/server';
import { AlertingAuthorizationEntity } from '../../../../alerting/server'; import { AlertingAuthorizationEntity } from '../../../../alerting/server';
import { ruleDataPluginServiceMock } from '../../rule_data_plugin_service/rule_data_plugin_service.mock';
import { RuleDataPluginService } from '../../rule_data_plugin_service';
const alertingAuthMock = alertingAuthorizationMock.create(); const alertingAuthMock = alertingAuthorizationMock.create();
const esClientMock = elasticsearchClientMock.createElasticsearchClient(); const esClientMock = elasticsearchClientMock.createElasticsearchClient();
@ -30,6 +32,7 @@ const alertsClientParams: jest.Mocked<ConstructorOptions> = {
authorization: alertingAuthMock, authorization: alertingAuthMock,
esClient: esClientMock, esClient: esClientMock,
auditLogger, auditLogger,
ruleDataService: (ruleDataPluginServiceMock.create() as unknown) as RuleDataPluginService,
}; };
const DEFAULT_SPACE = 'test_default_space_id'; const DEFAULT_SPACE = 'test_default_space_id';
@ -78,7 +81,7 @@ describe('bulkUpdate()', () => {
describe('ids', () => { describe('ids', () => {
describe('audit log', () => { describe('audit log', () => {
test('logs successful event in audit logger', async () => { test('logs successful event in audit logger', async () => {
const indexName = '.alerts-observability-apm.alerts'; const indexName = '.alerts-observability.apm.alerts';
const alertsClient = new AlertsClient(alertsClientParams); const alertsClient = new AlertsClient(alertsClientParams);
esClientMock.mget.mockResolvedValueOnce( esClientMock.mget.mockResolvedValueOnce(
elasticsearchClientMock.createApiResponse({ elasticsearchClientMock.createApiResponse({
@ -107,7 +110,7 @@ describe('bulkUpdate()', () => {
{ {
update: { update: {
_id: fakeAlertId, _id: fakeAlertId,
_index: '.alerts-observability-apm.alerts', _index: '.alerts-observability.apm.alerts',
result: 'updated', result: 'updated',
status: 200, status: 200,
}, },
@ -135,7 +138,7 @@ describe('bulkUpdate()', () => {
}); });
test('audit error access if user is unauthorized for given alert', async () => { test('audit error access if user is unauthorized for given alert', async () => {
const indexName = '.alerts-observability-apm.alerts'; const indexName = '.alerts-observability.apm.alerts';
const alertsClient = new AlertsClient(alertsClientParams); const alertsClient = new AlertsClient(alertsClientParams);
esClientMock.mget.mockResolvedValueOnce( esClientMock.mget.mockResolvedValueOnce(
elasticsearchClientMock.createApiResponse({ elasticsearchClientMock.createApiResponse({
@ -181,7 +184,7 @@ describe('bulkUpdate()', () => {
}); });
test('logs multiple error events in audit logger', async () => { test('logs multiple error events in audit logger', async () => {
const indexName = '.alerts-observability-apm.alerts'; const indexName = '.alerts-observability.apm.alerts';
const alertsClient = new AlertsClient(alertsClientParams); const alertsClient = new AlertsClient(alertsClientParams);
esClientMock.mget.mockResolvedValueOnce( esClientMock.mget.mockResolvedValueOnce(
elasticsearchClientMock.createApiResponse({ elasticsearchClientMock.createApiResponse({
@ -257,7 +260,7 @@ describe('bulkUpdate()', () => {
describe('query', () => { describe('query', () => {
describe('audit log', () => { describe('audit log', () => {
test('logs successful event in audit logger', async () => { test('logs successful event in audit logger', async () => {
const indexName = '.alerts-observability-apm.alerts'; const indexName = '.alerts-observability.apm.alerts';
const alertsClient = new AlertsClient(alertsClientParams); const alertsClient = new AlertsClient(alertsClientParams);
esClientMock.search.mockResolvedValueOnce( esClientMock.search.mockResolvedValueOnce(
elasticsearchClientMock.createApiResponse({ elasticsearchClientMock.createApiResponse({
@ -276,7 +279,7 @@ describe('bulkUpdate()', () => {
hits: [ hits: [
{ {
_id: fakeAlertId, _id: fakeAlertId,
_index: '.alerts-observability-apm.alerts', _index: '.alerts-observability.apm.alerts',
_source: { _source: {
[ALERT_RULE_TYPE_ID]: 'apm.error_rate', [ALERT_RULE_TYPE_ID]: 'apm.error_rate',
[ALERT_RULE_CONSUMER]: 'apm', [ALERT_RULE_CONSUMER]: 'apm',
@ -317,7 +320,7 @@ describe('bulkUpdate()', () => {
}); });
test('audit error access if user is unauthorized for given alert', async () => { test('audit error access if user is unauthorized for given alert', async () => {
const indexName = '.alerts-observability-apm.alerts'; const indexName = '.alerts-observability.apm.alerts';
const alertsClient = new AlertsClient(alertsClientParams); const alertsClient = new AlertsClient(alertsClientParams);
esClientMock.search.mockResolvedValueOnce( esClientMock.search.mockResolvedValueOnce(
elasticsearchClientMock.createApiResponse({ elasticsearchClientMock.createApiResponse({
@ -336,7 +339,7 @@ describe('bulkUpdate()', () => {
hits: [ hits: [
{ {
_id: fakeAlertId, _id: fakeAlertId,
_index: '.alerts-observability-apm.alerts', _index: '.alerts-observability.apm.alerts',
_source: { _source: {
[ALERT_RULE_TYPE_ID]: fakeRuleTypeId, [ALERT_RULE_TYPE_ID]: fakeRuleTypeId,
[ALERT_RULE_CONSUMER]: 'apm', [ALERT_RULE_CONSUMER]: 'apm',
@ -378,7 +381,7 @@ describe('bulkUpdate()', () => {
}); });
test('logs multiple error events in audit logger', async () => { test('logs multiple error events in audit logger', async () => {
const indexName = '.alerts-observability-apm.alerts'; const indexName = '.alerts-observability.apm.alerts';
const alertsClient = new AlertsClient(alertsClientParams); const alertsClient = new AlertsClient(alertsClientParams);
esClientMock.search.mockResolvedValueOnce( esClientMock.search.mockResolvedValueOnce(
elasticsearchClientMock.createApiResponse({ elasticsearchClientMock.createApiResponse({
@ -397,7 +400,7 @@ describe('bulkUpdate()', () => {
hits: [ hits: [
{ {
_id: successfulAuthzHit, _id: successfulAuthzHit,
_index: '.alerts-observability-apm.alerts', _index: '.alerts-observability.apm.alerts',
_source: { _source: {
[ALERT_RULE_TYPE_ID]: 'apm.error_rate', [ALERT_RULE_TYPE_ID]: 'apm.error_rate',
[ALERT_RULE_CONSUMER]: 'apm', [ALERT_RULE_CONSUMER]: 'apm',
@ -407,7 +410,7 @@ describe('bulkUpdate()', () => {
}, },
{ {
_id: unsuccessfulAuthzHit, _id: unsuccessfulAuthzHit,
_index: '.alerts-observability-apm.alerts', _index: '.alerts-observability.apm.alerts',
_source: { _source: {
[ALERT_RULE_TYPE_ID]: fakeRuleTypeId, [ALERT_RULE_TYPE_ID]: fakeRuleTypeId,
[ALERT_RULE_CONSUMER]: 'apm', [ALERT_RULE_CONSUMER]: 'apm',

View file

@ -18,6 +18,8 @@ import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mo
import { alertingAuthorizationMock } from '../../../../alerting/server/authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../../../alerting/server/authorization/alerting_authorization.mock';
import { AuditLogger } from '../../../../security/server'; import { AuditLogger } from '../../../../security/server';
import { AlertingAuthorizationEntity } from '../../../../alerting/server'; import { AlertingAuthorizationEntity } from '../../../../alerting/server';
import { ruleDataPluginServiceMock } from '../../rule_data_plugin_service/rule_data_plugin_service.mock';
import { RuleDataPluginService } from '../../rule_data_plugin_service';
const alertingAuthMock = alertingAuthorizationMock.create(); const alertingAuthMock = alertingAuthorizationMock.create();
const esClientMock = elasticsearchClientMock.createElasticsearchClient(); const esClientMock = elasticsearchClientMock.createElasticsearchClient();
@ -30,6 +32,7 @@ const alertsClientParams: jest.Mocked<ConstructorOptions> = {
authorization: alertingAuthMock, authorization: alertingAuthMock,
esClient: esClientMock, esClient: esClientMock,
auditLogger, auditLogger,
ruleDataService: (ruleDataPluginServiceMock.create() as unknown) as RuleDataPluginService,
}; };
const DEFAULT_SPACE = 'test_default_space_id'; const DEFAULT_SPACE = 'test_default_space_id';
@ -90,7 +93,7 @@ describe('find()', () => {
{ {
found: true, found: true,
_type: 'alert', _type: 'alert',
_index: '.alerts-observability-apm', _index: '.alerts-observability.apm.alerts',
_id: 'NoxgpHkBqbdrfX07MqXV', _id: 'NoxgpHkBqbdrfX07MqXV',
_version: 1, _version: 1,
_seq_no: 362, _seq_no: 362,
@ -110,7 +113,7 @@ describe('find()', () => {
); );
const result = await alertsClient.find({ const result = await alertsClient.find({
query: { match: { [ALERT_WORKFLOW_STATUS]: 'open' } }, query: { match: { [ALERT_WORKFLOW_STATUS]: 'open' } },
index: '.alerts-observability-apm', index: '.alerts-observability.apm.alerts',
}); });
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Object { Object {
@ -124,7 +127,7 @@ describe('find()', () => {
"hits": Array [ "hits": Array [
Object { Object {
"_id": "NoxgpHkBqbdrfX07MqXV", "_id": "NoxgpHkBqbdrfX07MqXV",
"_index": ".alerts-observability-apm", "_index": ".alerts-observability.apm.alerts",
"_primary_term": 2, "_primary_term": 2,
"_seq_no": 362, "_seq_no": 362,
"_source": Object { "_source": Object {
@ -194,7 +197,7 @@ describe('find()', () => {
"track_total_hits": undefined, "track_total_hits": undefined,
}, },
"ignore_unavailable": true, "ignore_unavailable": true,
"index": ".alerts-observability-apm", "index": ".alerts-observability.apm.alerts",
"seq_no_primary_term": true, "seq_no_primary_term": true,
}, },
] ]
@ -221,7 +224,7 @@ describe('find()', () => {
{ {
found: true, found: true,
_type: 'alert', _type: 'alert',
_index: '.alerts-observability-apm', _index: '.alerts-observability.apm.alerts',
_id: 'NoxgpHkBqbdrfX07MqXV', _id: 'NoxgpHkBqbdrfX07MqXV',
_version: 1, _version: 1,
_seq_no: 362, _seq_no: 362,
@ -241,7 +244,7 @@ describe('find()', () => {
); );
await alertsClient.find({ await alertsClient.find({
query: { match: { [ALERT_WORKFLOW_STATUS]: 'open' } }, query: { match: { [ALERT_WORKFLOW_STATUS]: 'open' } },
index: '.alerts-observability-apm', index: '.alerts-observability.apm.alerts',
}); });
expect(auditLogger.log).toHaveBeenCalledWith({ expect(auditLogger.log).toHaveBeenCalledWith({
@ -252,7 +255,7 @@ describe('find()', () => {
}); });
test('audit error access if user is unauthorized for given alert', async () => { test('audit error access if user is unauthorized for given alert', async () => {
const indexName = '.alerts-observability-apm'; const indexName = '.alerts-observability.apm.alerts';
const fakeAlertId = 'myfakeid1'; const fakeAlertId = 'myfakeid1';
// fakeRuleTypeId will cause authz to fail // fakeRuleTypeId will cause authz to fail
const fakeRuleTypeId = 'fake.rule'; const fakeRuleTypeId = 'fake.rule';
@ -296,7 +299,7 @@ describe('find()', () => {
await expect( await expect(
alertsClient.find({ alertsClient.find({
query: { match: { [ALERT_WORKFLOW_STATUS]: 'open' } }, query: { match: { [ALERT_WORKFLOW_STATUS]: 'open' } },
index: '.alerts-observability-apm', index: '.alerts-observability.apm.alerts',
}) })
).rejects.toThrowErrorMatchingInlineSnapshot(` ).rejects.toThrowErrorMatchingInlineSnapshot(`
"Unable to retrieve alert details for alert with id of \\"undefined\\" or with query \\"[object Object]\\" and operation find "Unable to retrieve alert details for alert with id of \\"undefined\\" or with query \\"[object Object]\\" and operation find
@ -326,7 +329,7 @@ describe('find()', () => {
await expect( await expect(
alertsClient.find({ alertsClient.find({
query: { match: { [ALERT_WORKFLOW_STATUS]: 'open' } }, query: { match: { [ALERT_WORKFLOW_STATUS]: 'open' } },
index: '.alerts-observability-apm', index: '.alerts-observability.apm.alerts',
}) })
).rejects.toThrowErrorMatchingInlineSnapshot(` ).rejects.toThrowErrorMatchingInlineSnapshot(`
"Unable to retrieve alert details for alert with id of \\"undefined\\" or with query \\"[object Object]\\" and operation find "Unable to retrieve alert details for alert with id of \\"undefined\\" or with query \\"[object Object]\\" and operation find
@ -354,7 +357,7 @@ describe('find()', () => {
{ {
found: true, found: true,
_type: 'alert', _type: 'alert',
_index: '.alerts-observability-apm', _index: '.alerts-observability.apm.alerts',
_id: 'NoxgpHkBqbdrfX07MqXV', _id: 'NoxgpHkBqbdrfX07MqXV',
_version: 1, _version: 1,
_seq_no: 362, _seq_no: 362,
@ -378,7 +381,7 @@ describe('find()', () => {
const alertsClient = new AlertsClient(alertsClientParams); const alertsClient = new AlertsClient(alertsClientParams);
const result = await alertsClient.find({ const result = await alertsClient.find({
query: { match: { [ALERT_WORKFLOW_STATUS]: 'open' } }, query: { match: { [ALERT_WORKFLOW_STATUS]: 'open' } },
index: '.alerts-observability-apm', index: '.alerts-observability.apm.alerts',
}); });
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
@ -393,7 +396,7 @@ describe('find()', () => {
"hits": Array [ "hits": Array [
Object { Object {
"_id": "NoxgpHkBqbdrfX07MqXV", "_id": "NoxgpHkBqbdrfX07MqXV",
"_index": ".alerts-observability-apm", "_index": ".alerts-observability.apm.alerts",
"_primary_term": 2, "_primary_term": 2,
"_seq_no": 362, "_seq_no": 362,
"_source": Object { "_source": Object {

View file

@ -18,6 +18,8 @@ import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mo
import { alertingAuthorizationMock } from '../../../../alerting/server/authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../../../alerting/server/authorization/alerting_authorization.mock';
import { AuditLogger } from '../../../../security/server'; import { AuditLogger } from '../../../../security/server';
import { AlertingAuthorizationEntity } from '../../../../alerting/server'; import { AlertingAuthorizationEntity } from '../../../../alerting/server';
import { ruleDataPluginServiceMock } from '../../rule_data_plugin_service/rule_data_plugin_service.mock';
import { RuleDataPluginService } from '../../rule_data_plugin_service';
const alertingAuthMock = alertingAuthorizationMock.create(); const alertingAuthMock = alertingAuthorizationMock.create();
const esClientMock = elasticsearchClientMock.createElasticsearchClient(); const esClientMock = elasticsearchClientMock.createElasticsearchClient();
@ -30,6 +32,7 @@ const alertsClientParams: jest.Mocked<ConstructorOptions> = {
authorization: alertingAuthMock, authorization: alertingAuthMock,
esClient: esClientMock, esClient: esClientMock,
auditLogger, auditLogger,
ruleDataService: (ruleDataPluginServiceMock.create() as unknown) as RuleDataPluginService,
}; };
const DEFAULT_SPACE = 'test_default_space_id'; const DEFAULT_SPACE = 'test_default_space_id';
@ -91,7 +94,7 @@ describe('get()', () => {
{ {
found: true, found: true,
_type: 'alert', _type: 'alert',
_index: '.alerts-observability-apm', _index: '.alerts-observability.apm.alerts',
_id: 'NoxgpHkBqbdrfX07MqXV', _id: 'NoxgpHkBqbdrfX07MqXV',
_version: 1, _version: 1,
_seq_no: 362, _seq_no: 362,
@ -109,7 +112,7 @@ describe('get()', () => {
}, },
}) })
); );
const result = await alertsClient.get({ id: '1', index: '.alerts-observability-apm' }); const result = await alertsClient.get({ id: '1', index: '.alerts-observability.apm.alerts' });
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Object { Object {
"kibana.alert.rule.consumer": "apm", "kibana.alert.rule.consumer": "apm",
@ -173,7 +176,7 @@ describe('get()', () => {
"track_total_hits": undefined, "track_total_hits": undefined,
}, },
"ignore_unavailable": true, "ignore_unavailable": true,
"index": ".alerts-observability-apm", "index": ".alerts-observability.apm.alerts",
"seq_no_primary_term": true, "seq_no_primary_term": true,
}, },
] ]
@ -200,7 +203,7 @@ describe('get()', () => {
{ {
found: true, found: true,
_type: 'alert', _type: 'alert',
_index: '.alerts-observability-apm', _index: '.alerts-observability.apm.alerts',
_id: 'NoxgpHkBqbdrfX07MqXV', _id: 'NoxgpHkBqbdrfX07MqXV',
_version: 1, _version: 1,
_seq_no: 362, _seq_no: 362,
@ -218,7 +221,10 @@ describe('get()', () => {
}, },
}) })
); );
await alertsClient.get({ id: 'NoxgpHkBqbdrfX07MqXV', index: '.alerts-observability-apm' }); await alertsClient.get({
id: 'NoxgpHkBqbdrfX07MqXV',
index: '.alerts-observability.apm.alerts',
});
expect(auditLogger.log).toHaveBeenCalledWith({ expect(auditLogger.log).toHaveBeenCalledWith({
error: undefined, error: undefined,
@ -228,7 +234,7 @@ describe('get()', () => {
}); });
test('audit error access if user is unauthorized for given alert', async () => { test('audit error access if user is unauthorized for given alert', async () => {
const indexName = '.alerts-observability-apm.alerts'; const indexName = '.alerts-observability.apm.alerts';
const fakeAlertId = 'myfakeid1'; const fakeAlertId = 'myfakeid1';
// fakeRuleTypeId will cause authz to fail // fakeRuleTypeId will cause authz to fail
const fakeRuleTypeId = 'fake.rule'; const fakeRuleTypeId = 'fake.rule';
@ -269,7 +275,7 @@ describe('get()', () => {
}) })
); );
await expect(alertsClient.get({ id: fakeAlertId, index: '.alerts-observability-apm.alerts' })) await expect(alertsClient.get({ id: fakeAlertId, index: '.alerts-observability.apm.alerts' }))
.rejects.toThrowErrorMatchingInlineSnapshot(` .rejects.toThrowErrorMatchingInlineSnapshot(`
"Unable to retrieve alert details for alert with id of \\"myfakeid1\\" or with query \\"undefined\\" and operation get "Unable to retrieve alert details for alert with id of \\"myfakeid1\\" or with query \\"undefined\\" and operation get
Error: Error: Unauthorized for fake.rule and apm" Error: Error: Unauthorized for fake.rule and apm"
@ -296,7 +302,7 @@ describe('get()', () => {
esClientMock.search.mockRejectedValue(error); esClientMock.search.mockRejectedValue(error);
await expect( await expect(
alertsClient.get({ id: 'NoxgpHkBqbdrfX07MqXV', index: '.alerts-observability-apm' }) alertsClient.get({ id: 'NoxgpHkBqbdrfX07MqXV', index: '.alerts-observability.apm.alerts' })
).rejects.toThrowErrorMatchingInlineSnapshot(` ).rejects.toThrowErrorMatchingInlineSnapshot(`
"Unable to retrieve alert details for alert with id of \\"NoxgpHkBqbdrfX07MqXV\\" or with query \\"undefined\\" and operation get "Unable to retrieve alert details for alert with id of \\"NoxgpHkBqbdrfX07MqXV\\" or with query \\"undefined\\" and operation get
Error: Error: something went wrong" Error: Error: something went wrong"
@ -323,7 +329,7 @@ describe('get()', () => {
{ {
found: true, found: true,
_type: 'alert', _type: 'alert',
_index: '.alerts-observability-apm', _index: '.alerts-observability.apm.alerts',
_id: 'NoxgpHkBqbdrfX07MqXV', _id: 'NoxgpHkBqbdrfX07MqXV',
_version: 1, _version: 1,
_seq_no: 362, _seq_no: 362,
@ -347,7 +353,7 @@ describe('get()', () => {
const alertsClient = new AlertsClient(alertsClientParams); const alertsClient = new AlertsClient(alertsClientParams);
const result = await alertsClient.get({ const result = await alertsClient.get({
id: 'NoxgpHkBqbdrfX07MqXV', id: 'NoxgpHkBqbdrfX07MqXV',
index: '.alerts-observability-apm', index: '.alerts-observability.apm.alerts',
}); });
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`

View file

@ -18,6 +18,8 @@ import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mo
import { alertingAuthorizationMock } from '../../../../alerting/server/authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../../../alerting/server/authorization/alerting_authorization.mock';
import { AuditLogger } from '../../../../security/server'; import { AuditLogger } from '../../../../security/server';
import { AlertingAuthorizationEntity } from '../../../../alerting/server'; import { AlertingAuthorizationEntity } from '../../../../alerting/server';
import { ruleDataPluginServiceMock } from '../../rule_data_plugin_service/rule_data_plugin_service.mock';
import { RuleDataPluginService } from '../../rule_data_plugin_service';
const alertingAuthMock = alertingAuthorizationMock.create(); const alertingAuthMock = alertingAuthorizationMock.create();
const esClientMock = elasticsearchClientMock.createElasticsearchClient(); const esClientMock = elasticsearchClientMock.createElasticsearchClient();
@ -30,6 +32,7 @@ const alertsClientParams: jest.Mocked<ConstructorOptions> = {
authorization: alertingAuthMock, authorization: alertingAuthMock,
esClient: esClientMock, esClient: esClientMock,
auditLogger, auditLogger,
ruleDataService: (ruleDataPluginServiceMock.create() as unknown) as RuleDataPluginService,
}; };
const DEFAULT_SPACE = 'test_default_space_id'; const DEFAULT_SPACE = 'test_default_space_id';
@ -91,7 +94,7 @@ describe('update()', () => {
{ {
found: true, found: true,
_type: 'alert', _type: 'alert',
_index: '.alerts-observability-apm', _index: '.alerts-observability.apm.alerts',
_id: 'NoxgpHkBqbdrfX07MqXV', _id: 'NoxgpHkBqbdrfX07MqXV',
_source: { _source: {
[ALERT_RULE_TYPE_ID]: 'apm.error_rate', [ALERT_RULE_TYPE_ID]: 'apm.error_rate',
@ -109,7 +112,7 @@ describe('update()', () => {
esClientMock.update.mockResolvedValueOnce( esClientMock.update.mockResolvedValueOnce(
elasticsearchClientMock.createApiResponse({ elasticsearchClientMock.createApiResponse({
body: { body: {
_index: '.alerts-observability-apm', _index: '.alerts-observability.apm.alerts',
_id: 'NoxgpHkBqbdrfX07MqXV', _id: 'NoxgpHkBqbdrfX07MqXV',
_version: 2, _version: 2,
result: 'updated', result: 'updated',
@ -123,12 +126,12 @@ describe('update()', () => {
id: '1', id: '1',
status: 'closed', status: 'closed',
_version: undefined, _version: undefined,
index: '.alerts-observability-apm', index: '.alerts-observability.apm.alerts',
}); });
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Object { Object {
"_id": "NoxgpHkBqbdrfX07MqXV", "_id": "NoxgpHkBqbdrfX07MqXV",
"_index": ".alerts-observability-apm", "_index": ".alerts-observability.apm.alerts",
"_primary_term": 1, "_primary_term": 1,
"_seq_no": 1, "_seq_no": 1,
"_shards": Object { "_shards": Object {
@ -150,7 +153,7 @@ describe('update()', () => {
}, },
}, },
"id": "1", "id": "1",
"index": ".alerts-observability-apm", "index": ".alerts-observability.apm.alerts",
"refresh": "wait_for", "refresh": "wait_for",
}, },
] ]
@ -177,7 +180,7 @@ describe('update()', () => {
{ {
found: true, found: true,
_type: 'alert', _type: 'alert',
_index: '.alerts-observability-apm', _index: '.alerts-observability.apm.alerts',
_id: 'NoxgpHkBqbdrfX07MqXV', _id: 'NoxgpHkBqbdrfX07MqXV',
_source: { _source: {
[ALERT_RULE_TYPE_ID]: 'apm.error_rate', [ALERT_RULE_TYPE_ID]: 'apm.error_rate',
@ -195,7 +198,7 @@ describe('update()', () => {
esClientMock.update.mockResolvedValueOnce( esClientMock.update.mockResolvedValueOnce(
elasticsearchClientMock.createApiResponse({ elasticsearchClientMock.createApiResponse({
body: { body: {
_index: '.alerts-observability-apm', _index: '.alerts-observability.apm.alerts',
_id: 'NoxgpHkBqbdrfX07MqXV', _id: 'NoxgpHkBqbdrfX07MqXV',
_version: 2, _version: 2,
result: 'updated', result: 'updated',
@ -209,7 +212,7 @@ describe('update()', () => {
id: 'NoxgpHkBqbdrfX07MqXV', id: 'NoxgpHkBqbdrfX07MqXV',
status: 'closed', status: 'closed',
_version: undefined, _version: undefined,
index: '.alerts-observability-apm', index: '.alerts-observability.apm.alerts',
}); });
expect(auditLogger.log).toHaveBeenCalledWith({ expect(auditLogger.log).toHaveBeenCalledWith({
@ -225,7 +228,7 @@ describe('update()', () => {
}); });
test('audit error update if user is unauthorized for given alert', async () => { test('audit error update if user is unauthorized for given alert', async () => {
const indexName = '.alerts-observability-apm.alerts'; const indexName = '.alerts-observability.apm.alerts';
const fakeAlertId = 'myfakeid1'; const fakeAlertId = 'myfakeid1';
// fakeRuleTypeId will cause authz to fail // fakeRuleTypeId will cause authz to fail
const fakeRuleTypeId = 'fake.rule'; const fakeRuleTypeId = 'fake.rule';
@ -271,7 +274,7 @@ describe('update()', () => {
id: fakeAlertId, id: fakeAlertId,
status: 'closed', status: 'closed',
_version: '1', _version: '1',
index: '.alerts-observability-apm', index: '.alerts-observability.apm.alerts',
}) })
).rejects.toThrowErrorMatchingInlineSnapshot(` ).rejects.toThrowErrorMatchingInlineSnapshot(`
"Unable to retrieve alert details for alert with id of \\"myfakeid1\\" or with query \\"undefined\\" and operation update "Unable to retrieve alert details for alert with id of \\"myfakeid1\\" or with query \\"undefined\\" and operation update
@ -303,7 +306,7 @@ describe('update()', () => {
id: 'NoxgpHkBqbdrfX07MqXV', id: 'NoxgpHkBqbdrfX07MqXV',
status: 'closed', status: 'closed',
_version: undefined, _version: undefined,
index: '.alerts-observability-apm', index: '.alerts-observability.apm.alerts',
}) })
).rejects.toThrowErrorMatchingInlineSnapshot(` ).rejects.toThrowErrorMatchingInlineSnapshot(`
"Unable to retrieve alert details for alert with id of \\"NoxgpHkBqbdrfX07MqXV\\" or with query \\"undefined\\" and operation update "Unable to retrieve alert details for alert with id of \\"NoxgpHkBqbdrfX07MqXV\\" or with query \\"undefined\\" and operation update
@ -332,7 +335,7 @@ describe('update()', () => {
{ {
found: true, found: true,
_type: 'alert', _type: 'alert',
_index: '.alerts-observability-apm', _index: '.alerts-observability.apm.alerts',
_id: 'NoxgpHkBqbdrfX07MqXV', _id: 'NoxgpHkBqbdrfX07MqXV',
_source: { _source: {
[ALERT_RULE_TYPE_ID]: 'apm.error_rate', [ALERT_RULE_TYPE_ID]: 'apm.error_rate',
@ -354,7 +357,7 @@ describe('update()', () => {
id: 'NoxgpHkBqbdrfX07MqXV', id: 'NoxgpHkBqbdrfX07MqXV',
status: 'closed', status: 'closed',
_version: undefined, _version: undefined,
index: '.alerts-observability-apm', index: '.alerts-observability.apm.alerts',
}) })
).rejects.toThrowErrorMatchingInlineSnapshot(`"something went wrong on update"`); ).rejects.toThrowErrorMatchingInlineSnapshot(`"something went wrong on update"`);
expect(auditLogger.log).toHaveBeenCalledWith({ expect(auditLogger.log).toHaveBeenCalledWith({
@ -389,7 +392,7 @@ describe('update()', () => {
{ {
found: true, found: true,
_type: 'alert', _type: 'alert',
_index: '.alerts-observability-apm', _index: '.alerts-observability.apm.alerts',
_id: 'NoxgpHkBqbdrfX07MqXV', _id: 'NoxgpHkBqbdrfX07MqXV',
_version: 2, _version: 2,
_seq_no: 362, _seq_no: 362,
@ -411,7 +414,7 @@ describe('update()', () => {
esClientMock.update.mockResolvedValueOnce( esClientMock.update.mockResolvedValueOnce(
elasticsearchClientMock.createApiResponse({ elasticsearchClientMock.createApiResponse({
body: { body: {
_index: '.alerts-observability-apm', _index: '.alerts-observability.apm.alerts',
_id: 'NoxgpHkBqbdrfX07MqXV', _id: 'NoxgpHkBqbdrfX07MqXV',
_version: 2, _version: 2,
result: 'updated', result: 'updated',
@ -429,13 +432,13 @@ describe('update()', () => {
id: 'NoxgpHkBqbdrfX07MqXV', id: 'NoxgpHkBqbdrfX07MqXV',
status: 'closed', status: 'closed',
_version: undefined, _version: undefined,
index: '.alerts-observability-apm', index: '.alerts-observability.apm.alerts',
}); });
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Object { Object {
"_id": "NoxgpHkBqbdrfX07MqXV", "_id": "NoxgpHkBqbdrfX07MqXV",
"_index": ".alerts-observability-apm", "_index": ".alerts-observability.apm.alerts",
"_primary_term": 1, "_primary_term": 1,
"_seq_no": 1, "_seq_no": 1,
"_shards": Object { "_shards": Object {

View file

@ -125,7 +125,7 @@ export class RuleRegistryPlugin
core: CoreStart, core: CoreStart,
plugins: RuleRegistryPluginStartDependencies plugins: RuleRegistryPluginStartDependencies
): RuleRegistryPluginStartContract { ): RuleRegistryPluginStartContract {
const { logger, alertsClientFactory, security } = this; const { logger, alertsClientFactory, ruleDataService, security } = this;
alertsClientFactory.initialize({ alertsClientFactory.initialize({
logger, logger,
@ -135,6 +135,7 @@ export class RuleRegistryPlugin
return plugins.alerting.getAlertingAuthorizationWithRequest(request); return plugins.alerting.getAlertingAuthorizationWithRequest(request);
}, },
securityPluginSetup: security, securityPluginSetup: security,
ruleDataService,
}); });
const getRacClientWithRequest = (request: KibanaRequest) => { const getRacClientWithRequest = (request: KibanaRequest) => {

View file

@ -29,6 +29,6 @@ export const getUpdateRequest = () =>
body: { body: {
status: 'closed', status: 'closed',
ids: ['alert-1'], ids: ['alert-1'],
index: '.alerts-observability-apm*', index: '.alerts-observability.apm.alerts*',
}, },
}); });

View file

@ -20,7 +20,7 @@ describe('updateAlertByIdRoute', () => {
({ clients, context } = requestContextMock.createTools()); ({ clients, context } = requestContextMock.createTools());
clients.rac.update.mockResolvedValue({ clients.rac.update.mockResolvedValue({
_index: '.alerts-observability-apm', _index: '.alerts-observability.apm.alerts',
_id: 'NoxgpHkBqbdrfX07MqXV', _id: 'NoxgpHkBqbdrfX07MqXV',
_version: 'WzM2MiwyXQ==', _version: 'WzM2MiwyXQ==',
result: 'updated', result: 'updated',
@ -37,7 +37,7 @@ describe('updateAlertByIdRoute', () => {
expect(response.status).toEqual(200); expect(response.status).toEqual(200);
expect(response.body).toEqual({ expect(response.body).toEqual({
_index: '.alerts-observability-apm', _index: '.alerts-observability.apm.alerts',
_id: 'NoxgpHkBqbdrfX07MqXV', _id: 'NoxgpHkBqbdrfX07MqXV',
_version: 'WzM2MiwyXQ==', _version: 'WzM2MiwyXQ==',
result: 'updated', result: 'updated',
@ -58,7 +58,7 @@ describe('updateAlertByIdRoute', () => {
body: { body: {
status: 'closed', status: 'closed',
ids: 'alert-1', ids: 'alert-1',
index: '.alerts-observability-apm*', index: '.alerts-observability.apm.alerts*',
}, },
}), }),
context context
@ -77,7 +77,7 @@ describe('updateAlertByIdRoute', () => {
body: { body: {
notStatus: 'closed', notStatus: 'closed',
ids: ['alert-1'], ids: ['alert-1'],
index: '.alerts-observability-apm*', index: '.alerts-observability.apm.alerts*',
}, },
}), }),
context context

View file

@ -12,18 +12,17 @@ type Schema = PublicMethodsOf<RuleDataPluginService>;
const createRuleDataPluginService = () => { const createRuleDataPluginService = () => {
const mocked: jest.Mocked<Schema> = { const mocked: jest.Mocked<Schema> = {
getRegisteredIndexInfo: jest.fn(),
getResourcePrefix: jest.fn(), getResourcePrefix: jest.fn(),
getResourceName: jest.fn(), getResourceName: jest.fn(),
isWriteEnabled: jest.fn(), isWriteEnabled: jest.fn(),
initializeService: jest.fn(), initializeService: jest.fn(),
initializeIndex: jest.fn(), initializeIndex: jest.fn(),
findIndexByName: jest.fn(),
findIndicesByFeature: jest.fn(),
}; };
return mocked; return mocked;
}; };
export const ruleDataPluginServiceMock: { export const ruleDataPluginServiceMock = {
create: () => jest.Mocked<PublicMethodsOf<RuleDataPluginService>>;
} = {
create: createRuleDataPluginService, create: createRuleDataPluginService,
}; };

View file

@ -6,12 +6,13 @@
*/ */
import { Either, isLeft, left, right } from 'fp-ts/lib/Either'; import { Either, isLeft, left, right } from 'fp-ts/lib/Either';
import { ValidFeatureId } from '@kbn/rule-data-utils';
import { ElasticsearchClient, Logger } from 'kibana/server'; import { ElasticsearchClient, Logger } from 'kibana/server';
import { IRuleDataClient, RuleDataClient, WaitResult } from '../rule_data_client'; import { IRuleDataClient, RuleDataClient, WaitResult } from '../rule_data_client';
import { IndexInfo } from './index_info'; import { IndexInfo } from './index_info';
import { IndexOptions } from './index_options'; import { Dataset, IndexOptions } from './index_options';
import { ResourceInstaller } from './resource_installer'; import { ResourceInstaller } from './resource_installer';
import { joinWithDash } from './utils'; import { joinWithDash } from './utils';
@ -26,12 +27,16 @@ interface ConstructorOptions {
* A service for creating and using Elasticsearch indices for alerts-as-data. * A service for creating and using Elasticsearch indices for alerts-as-data.
*/ */
export class RuleDataPluginService { export class RuleDataPluginService {
private readonly indicesByBaseName: Map<string, IndexInfo>;
private readonly indicesByFeatureId: Map<string, IndexInfo[]>;
private readonly resourceInstaller: ResourceInstaller; private readonly resourceInstaller: ResourceInstaller;
private installCommonResources: Promise<Either<Error, 'ok'>>; private installCommonResources: Promise<Either<Error, 'ok'>>;
private isInitialized: boolean; private isInitialized: boolean;
private registeredIndices: Map<string, IndexInfo> = new Map();
constructor(private readonly options: ConstructorOptions) { constructor(private readonly options: ConstructorOptions) {
this.indicesByBaseName = new Map();
this.indicesByFeatureId = new Map();
this.resourceInstaller = new ResourceInstaller({ this.resourceInstaller = new ResourceInstaller({
getResourceName: (name) => this.getResourceName(name), getResourceName: (name) => this.getResourceName(name),
getClusterClient: options.getClusterClient, getClusterClient: options.getClusterClient,
@ -106,7 +111,9 @@ export class RuleDataPluginService {
indexOptions, indexOptions,
}); });
this.registeredIndices.set(indexOptions.registrationContext, indexInfo); const indicesAssociatedWithFeature = this.indicesByFeatureId.get(indexOptions.feature) ?? [];
this.indicesByFeatureId.set(indexOptions.feature, [...indicesAssociatedWithFeature, indexInfo]);
this.indicesByBaseName.set(indexInfo.baseName, indexInfo);
const waitUntilClusterClientAvailable = async (): Promise<WaitResult> => { const waitUntilClusterClientAvailable = async (): Promise<WaitResult> => {
try { try {
@ -153,11 +160,19 @@ export class RuleDataPluginService {
} }
/** /**
* Looks up the index information associated with the given `registrationContext`. * Looks up the index information associated with the given registration context and dataset.
* @param registrationContext
* @returns the IndexInfo or undefined
*/ */
public getRegisteredIndexInfo(registrationContext: string): IndexInfo | undefined { public findIndexByName(registrationContext: string, dataset: Dataset): IndexInfo | null {
return this.registeredIndices.get(registrationContext); const baseName = this.getResourceName(`${registrationContext}.${dataset}`);
return this.indicesByBaseName.get(baseName) ?? null;
}
/**
* Looks up the index information associated with the given Kibana "feature".
* Note: features are used in RBAC.
*/
public findIndicesByFeature(featureId: ValidFeatureId, dataset?: Dataset): IndexInfo[] {
const foundIndices = this.indicesByFeatureId.get(featureId) ?? [];
return dataset ? foundIndices.filter((i) => i.indexOptions.dataset === dataset) : foundIndices;
} }
} }

View file

@ -25,6 +25,6 @@ curl -s -k \
-H 'kbn-xsrf: 123' \ -H 'kbn-xsrf: 123' \
-u observer:changeme \ -u observer:changeme \
-X POST ${KIBANA_URL}${SPACE_URL}/internal/rac/alerts/bulk_update \ -X POST ${KIBANA_URL}${SPACE_URL}/internal/rac/alerts/bulk_update \
-d "{\"ids\": $IDS, \"status\":\"$STATUS\", \"index\":\".alerts-observability-apm\"}" | jq . -d "{\"ids\": $IDS, \"status\":\"$STATUS\", \"index\":\".alerts-observability.apm.alerts\"}" | jq .
# -d "{\"ids\": $IDS, \"query\": \"kibana.rac.alert.status: open\", \"status\":\"$STATUS\", \"index\":\".alerts-observability-apm\"}" | jq . # -d "{\"ids\": $IDS, \"query\": \"kibana.rac.alert.status: open\", \"status\":\"$STATUS\", \"index\":\".alerts-observability.apm.alerts\"}" | jq .
# -d "{\"ids\": $IDS, \"status\":\"$STATUS\", \"index\":\".alerts-observability-apm\"}" | jq . # -d "{\"ids\": $IDS, \"status\":\"$STATUS\", \"index\":\".alerts-observability.apm.alerts\"}" | jq .

View file

@ -25,4 +25,4 @@ curl -s -k \
-H 'kbn-xsrf: 123' \ -H 'kbn-xsrf: 123' \
-u observer:changeme \ -u observer:changeme \
-X POST ${KIBANA_URL}${SPACE_URL}/internal/rac/alerts/bulk_update \ -X POST ${KIBANA_URL}${SPACE_URL}/internal/rac/alerts/bulk_update \
-d "{\"query\": \"$QUERY\", \"status\":\"$STATUS\", \"index\":\".alerts-observability-apm\"}" | jq . -d "{\"query\": \"$QUERY\", \"status\":\"$STATUS\", \"index\":\".alerts-observability.apm.alerts\"}" | jq .

View file

@ -26,4 +26,4 @@ curl -v \
-H 'kbn-xsrf: 123' \ -H 'kbn-xsrf: 123' \
-u observer:changeme \ -u observer:changeme \
-X POST ${KIBANA_URL}${SPACE_URL}/internal/rac/alerts/find \ -X POST ${KIBANA_URL}${SPACE_URL}/internal/rac/alerts/find \
-d "{\"query\": { \"match\": { \"kibana.alert.status\": \"open\" }}, \"index\":\".alerts-observability-apm\"}" | jq . -d "{\"query\": { \"match\": { \"kibana.alert.status\": \"open\" }}, \"index\":\".alerts-observability.apm.alerts\"}" | jq .

View file

@ -19,4 +19,4 @@ cd ..
# Example: ./get_observability_alert.sh hunter # Example: ./get_observability_alert.sh hunter
curl -v -k \ curl -v -k \
-u $USER:changeme \ -u $USER:changeme \
-X GET "${KIBANA_URL}${SPACE_URL}/internal/rac/alerts?id=$ID&index=.alerts-observability-apm" | jq . -X GET "${KIBANA_URL}${SPACE_URL}/internal/rac/alerts?id=$ID&index=.alerts-observability.apm.alerts" | jq .

View file

@ -25,4 +25,4 @@ curl -s -k \
-H 'kbn-xsrf: 123' \ -H 'kbn-xsrf: 123' \
-u observer:changeme \ -u observer:changeme \
-X POST ${KIBANA_URL}${SPACE_URL}/internal/rac/alerts \ -X POST ${KIBANA_URL}${SPACE_URL}/internal/rac/alerts \
-d "{\"ids\": $IDS, \"status\":\"$STATUS\", \"index\":\".alerts-observability-apm\"}" | jq . -d "{\"ids\": $IDS, \"status\":\"$STATUS\", \"index\":\".alerts-observability.apm.alerts\"}" | jq .

View file

@ -73,7 +73,7 @@ export const createRuleTypeMocks = (
}; };
}, },
isWriteEnabled: jest.fn(() => true), isWriteEnabled: jest.fn(() => true),
indexName: '.alerts-observability.synthetics.alerts', indexName: '.alerts-observability.uptime.alerts',
} as unknown) as IRuleDataClient, } as unknown) as IRuleDataClient,
}, },
services, services,

View file

@ -1,7 +1,7 @@
{ {
"type": "doc", "type": "doc",
"value": { "value": {
"index": ".alerts-observability-apm", "index": ".alerts-observability.apm.alerts",
"id": "NoxgpHkBqbdrfX07MqXV", "id": "NoxgpHkBqbdrfX07MqXV",
"source": { "source": {
"event.kind" : "signal", "event.kind" : "signal",
@ -18,7 +18,7 @@
{ {
"type": "doc", "type": "doc",
"value": { "value": {
"index": ".alerts-observability-apm", "index": ".alerts-observability.apm.alerts",
"id": "space1alert", "id": "space1alert",
"source": { "source": {
"event.kind" : "signal", "event.kind" : "signal",
@ -35,7 +35,7 @@
{ {
"type": "doc", "type": "doc",
"value": { "value": {
"index": ".alerts-observability-apm", "index": ".alerts-observability.apm.alerts",
"id": "space2alert", "id": "space2alert",
"source": { "source": {
"event.kind" : "signal", "event.kind" : "signal",

View file

@ -1,7 +1,7 @@
{ {
"type": "index", "type": "index",
"value": { "value": {
"index": ".alerts-observability-apm", "index": ".alerts-observability.apm.alerts",
"mappings": { "mappings": {
"properties": { "properties": {
"message": { "message": {

View file

@ -55,7 +55,7 @@ export default ({ getService }: FtrProviderContext) => {
const SPACE1 = 'space1'; const SPACE1 = 'space1';
const SPACE2 = 'space2'; const SPACE2 = 'space2';
const APM_ALERT_ID = 'NoxgpHkBqbdrfX07MqXV'; const APM_ALERT_ID = 'NoxgpHkBqbdrfX07MqXV';
const APM_ALERT_INDEX = '.alerts-observability-apm'; const APM_ALERT_INDEX = '.alerts-observability.apm.alerts';
const SECURITY_SOLUTION_ALERT_ID = '020202'; const SECURITY_SOLUTION_ALERT_ID = '020202';
const SECURITY_SOLUTION_ALERT_INDEX = '.alerts-security.alerts'; const SECURITY_SOLUTION_ALERT_INDEX = '.alerts-security.alerts';

View file

@ -54,7 +54,7 @@ export default ({ getService }: FtrProviderContext) => {
const SPACE1 = 'space1'; const SPACE1 = 'space1';
const SPACE2 = 'space2'; const SPACE2 = 'space2';
const APM_ALERT_ID = 'NoxgpHkBqbdrfX07MqXV'; const APM_ALERT_ID = 'NoxgpHkBqbdrfX07MqXV';
const APM_ALERT_INDEX = '.alerts-observability-apm'; const APM_ALERT_INDEX = '.alerts-observability.apm.alerts';
const SECURITY_SOLUTION_ALERT_ID = '020202'; const SECURITY_SOLUTION_ALERT_ID = '020202';
const SECURITY_SOLUTION_ALERT_INDEX = '.alerts-security.alerts'; const SECURITY_SOLUTION_ALERT_INDEX = '.alerts-security.alerts';

View file

@ -54,7 +54,7 @@ export default ({ getService }: FtrProviderContext) => {
const SPACE1 = 'space1'; const SPACE1 = 'space1';
const SPACE2 = 'space2'; const SPACE2 = 'space2';
const APM_ALERT_ID = 'NoxgpHkBqbdrfX07MqXV'; const APM_ALERT_ID = 'NoxgpHkBqbdrfX07MqXV';
const APM_ALERT_INDEX = '.alerts-observability-apm'; const APM_ALERT_INDEX = '.alerts-observability.apm.alerts';
const SECURITY_SOLUTION_ALERT_ID = '020202'; const SECURITY_SOLUTION_ALERT_ID = '020202';
const SECURITY_SOLUTION_ALERT_INDEX = '.alerts-security.alerts'; const SECURITY_SOLUTION_ALERT_INDEX = '.alerts-security.alerts';

View file

@ -53,7 +53,7 @@ export default ({ getService }: FtrProviderContext) => {
const SPACE1 = 'space1'; const SPACE1 = 'space1';
const SPACE2 = 'space2'; const SPACE2 = 'space2';
const APM_ALERT_ID = 'NoxgpHkBqbdrfX07MqXV'; const APM_ALERT_ID = 'NoxgpHkBqbdrfX07MqXV';
const APM_ALERT_INDEX = '.alerts-observability-apm'; const APM_ALERT_INDEX = '.alerts-observability.apm.alerts';
const SECURITY_SOLUTION_ALERT_ID = '020202'; const SECURITY_SOLUTION_ALERT_ID = '020202';
const SECURITY_SOLUTION_ALERT_INDEX = '.alerts-security.alerts'; const SECURITY_SOLUTION_ALERT_INDEX = '.alerts-security.alerts';
const ALERT_VERSION = Buffer.from(JSON.stringify([0, 1]), 'utf8').toString('base64'); // required for optimistic concurrency control const ALERT_VERSION = Buffer.from(JSON.stringify([0, 1]), 'utf8').toString('base64'); // required for optimistic concurrency control

View file

@ -38,9 +38,9 @@ export default ({ getService }: FtrProviderContext) => {
.set('kbn-xsrf', 'true') .set('kbn-xsrf', 'true')
.expect(200); .expect(200);
const observabilityIndex = indexNames?.index_name?.find( const observabilityIndex = indexNames?.index_name?.find(
(indexName) => indexName === '.alerts-observability-apm' (indexName) => indexName === '.alerts-observability.apm.alerts'
); );
expect(observabilityIndex).to.eql('.alerts-observability-apm'); expect(observabilityIndex).to.eql('.alerts-observability.apm.alerts');
return observabilityIndex; return observabilityIndex;
}; };

View file

@ -36,9 +36,9 @@ export default ({ getService }: FtrProviderContext) => {
.set('kbn-xsrf', 'true') .set('kbn-xsrf', 'true')
.expect(200); .expect(200);
const observabilityIndex = indexNames?.index_name?.find( const observabilityIndex = indexNames?.index_name?.find(
(indexName) => indexName === '.alerts-observability-apm' (indexName) => indexName === '.alerts-observability.apm.alerts'
); );
expect(observabilityIndex).to.eql('.alerts-observability-apm'); expect(observabilityIndex).to.eql('.alerts-observability.apm.alerts');
return observabilityIndex; return observabilityIndex;
}; };
@ -107,7 +107,7 @@ export default ({ getService }: FtrProviderContext) => {
.expect(200); .expect(200);
expect(omit(['_version', '_seq_no'], res.body)).to.eql({ expect(omit(['_version', '_seq_no'], res.body)).to.eql({
success: true, success: true,
_index: '.alerts-observability-apm', _index: '.alerts-observability.apm.alerts',
_id: 'NoxgpHkBqbdrfX07MqXV', _id: 'NoxgpHkBqbdrfX07MqXV',
result: 'updated', result: 'updated',
_shards: { total: 2, successful: 1, failed: 0 }, _shards: { total: 2, successful: 1, failed: 0 },

View file

@ -22,7 +22,7 @@ export default ({ getService }: FtrProviderContext) => {
const SPACE1 = 'space1'; const SPACE1 = 'space1';
const SPACE2 = 'space2'; const SPACE2 = 'space2';
const APM_ALERT_ID = 'NoxgpHkBqbdrfX07MqXV'; const APM_ALERT_ID = 'NoxgpHkBqbdrfX07MqXV';
const APM_ALERT_INDEX = '.alerts-observability-apm'; const APM_ALERT_INDEX = '.alerts-observability.apm.alerts';
const SECURITY_SOLUTION_ALERT_INDEX = '.alerts-security.alerts'; const SECURITY_SOLUTION_ALERT_INDEX = '.alerts-security.alerts';
const getAPMIndexName = async (user: User) => { const getAPMIndexName = async (user: User) => {

View file

@ -21,7 +21,7 @@ export default ({ getService }: FtrProviderContext) => {
const SPACE1 = 'space1'; const SPACE1 = 'space1';
const SPACE2 = 'space2'; const SPACE2 = 'space2';
const APM_ALERT_ID = 'NoxgpHkBqbdrfX07MqXV'; const APM_ALERT_ID = 'NoxgpHkBqbdrfX07MqXV';
const APM_ALERT_INDEX = '.alerts-observability-apm'; const APM_ALERT_INDEX = '.alerts-observability.apm.alerts';
const SECURITY_SOLUTION_ALERT_INDEX = '.alerts-security.alerts'; const SECURITY_SOLUTION_ALERT_INDEX = '.alerts-security.alerts';
const ALERT_VERSION = Buffer.from(JSON.stringify([0, 1]), 'utf8').toString('base64'); // required for optimistic concurrency control const ALERT_VERSION = Buffer.from(JSON.stringify([0, 1]), 'utf8').toString('base64'); // required for optimistic concurrency control