mirror of
https://github.com/elastic/kibana.git
synced 2025-04-18 23:21:39 -04:00
[Response Ops][Reporting] Add health API to inform whether conditions are sufficient for scheduled reports (#216857)
Resolves https://github.com/elastic/kibana/issues/216319 ## Summary Adds an internal reporting health API to return whether conditions are sufficient to support scheduled reports. For scheduled reporting, we need for security and API keys to be enabled in Elasticsearch and for a permanent encryption key to be set for the encrypted saved objects plugin. ``` GET kbn:/internal/reporting/_health Response { "has_permanent_encryption_key": true, "is_sufficiently_secure": true } ``` The issue also mentions returning whether a preconfigured email service is configured, but that will be done as part of the main scheduled reporting task. ## To Verify 1. Run kibana and ES with no special flags, both flags should be `true` 2. Run ES with `-E xpack.security.enabled=false`. `is_sufficiently_secure` should be set to `false` 3. Run ES With `-E xpack.security.authc.api_key.enabled=false`. `is_sufficient_secure` should be set to `false` Note that in dev mode, an encryption key is auto-set if not defined in the Kibana yml so `has_permanent_encryption_key` will always return `true` in dev mode. --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
04a3d3308f
commit
3d54923123
10 changed files with 116 additions and 6 deletions
|
@ -24,6 +24,7 @@ export const INTERNAL_ROUTES = {
|
|||
DELETE_PREFIX: prefixInternalPath + '/jobs/delete', // docId is added to the final path
|
||||
DOWNLOAD_PREFIX: prefixInternalPath + '/jobs/download', // docId is added to the final path
|
||||
},
|
||||
HEALTH: prefixInternalPath + '/_health',
|
||||
GENERATE_PREFIX: prefixInternalPath + '/generate', // exportTypeId is added to the final path
|
||||
};
|
||||
|
||||
|
|
|
@ -109,6 +109,11 @@ export interface ReportingServerInfo {
|
|||
uuid: string;
|
||||
}
|
||||
|
||||
export interface ReportingHealthInfo {
|
||||
isSufficientlySecure: boolean;
|
||||
hasPermanentEncryptionKey: boolean;
|
||||
}
|
||||
|
||||
export type IlmPolicyMigrationStatus = 'policy-not-found' | 'indices-not-managed-by-policy' | 'ok';
|
||||
|
||||
export interface IlmPolicyStatusResponse {
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
"requiredPlugins": [
|
||||
"data",
|
||||
"discover",
|
||||
"encryptedSavedObjects",
|
||||
"fieldFormats",
|
||||
"home",
|
||||
"management",
|
||||
|
|
|
@ -28,7 +28,7 @@ import type { DiscoverServerPluginStart } from '@kbn/discover-plugin/server';
|
|||
import type { FeaturesPluginSetup } from '@kbn/features-plugin/server';
|
||||
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/server';
|
||||
import type { LicensingPluginStart } from '@kbn/licensing-plugin/server';
|
||||
import type { ReportingServerInfo } from '@kbn/reporting-common/types';
|
||||
import type { ReportingHealthInfo, ReportingServerInfo } from '@kbn/reporting-common/types';
|
||||
import { CsvSearchSourceExportType, CsvV2ExportType } from '@kbn/reporting-export-types-csv';
|
||||
import { PdfExportType, PdfV1ExportType } from '@kbn/reporting-export-types-pdf';
|
||||
import { PngExportType } from '@kbn/reporting-export-types-png';
|
||||
|
@ -46,6 +46,7 @@ import type { UsageCounter } from '@kbn/usage-collection-plugin/server';
|
|||
|
||||
import { checkLicense } from '@kbn/reporting-server/check_license';
|
||||
import { ExportTypesRegistry } from '@kbn/reporting-server/export_types_registry';
|
||||
import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server';
|
||||
import type { ReportingSetup } from '.';
|
||||
import { createConfig } from './config';
|
||||
import { reportingEventLoggerFactory } from './lib/event_logger/logger';
|
||||
|
@ -56,15 +57,16 @@ import { EventTracker } from './usage';
|
|||
|
||||
export interface ReportingInternalSetup {
|
||||
basePath: Pick<IBasePath, 'set'>;
|
||||
router: ReportingPluginRouter;
|
||||
docLinks: DocLinksServiceSetup;
|
||||
encryptedSavedObjects: EncryptedSavedObjectsPluginSetup;
|
||||
features: FeaturesPluginSetup;
|
||||
logger: Logger;
|
||||
router: ReportingPluginRouter;
|
||||
security?: SecurityPluginSetup;
|
||||
spaces?: SpacesPluginSetup;
|
||||
usageCounter?: UsageCounter;
|
||||
taskManager: TaskManagerSetupContract;
|
||||
logger: Logger;
|
||||
status: StatusServiceSetup;
|
||||
docLinks: DocLinksServiceSetup;
|
||||
taskManager: TaskManagerSetupContract;
|
||||
usageCounter?: UsageCounter;
|
||||
}
|
||||
|
||||
export interface ReportingInternalStart {
|
||||
|
@ -247,6 +249,32 @@ export class ReportingCore {
|
|||
};
|
||||
}
|
||||
|
||||
public async getHealthInfo(): Promise<ReportingHealthInfo> {
|
||||
const { encryptedSavedObjects } = this.getPluginSetupDeps();
|
||||
const { securityService } = await this.getPluginStartDeps();
|
||||
const isSecurityEnabled = await this.isSecurityEnabled();
|
||||
|
||||
let isSufficientlySecure: boolean;
|
||||
const apiKeysAreEnabled = (await securityService.authc.apiKeys.areAPIKeysEnabled()) ?? false;
|
||||
|
||||
if (isSecurityEnabled === null) {
|
||||
isSufficientlySecure = false;
|
||||
} else {
|
||||
// if esSecurityIsEnabled = true, then areApiKeysEnabled must be true to be considered secure
|
||||
// if esSecurityIsEnabled = false, then it does not matter what areApiKeysEnabled is
|
||||
if (!isSecurityEnabled) {
|
||||
isSufficientlySecure = false;
|
||||
} else {
|
||||
isSufficientlySecure = apiKeysAreEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isSufficientlySecure,
|
||||
hasPermanentEncryptionKey: encryptedSavedObjects.canEncrypt,
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Gives synchronous access to the config
|
||||
*/
|
||||
|
@ -307,6 +335,22 @@ export class ReportingCore {
|
|||
);
|
||||
}
|
||||
|
||||
public async isSecurityEnabled() {
|
||||
const { license$ } = (await this.getPluginStartDeps()).licensing;
|
||||
return await Rx.firstValueFrom(
|
||||
license$.pipe(
|
||||
map((license) => {
|
||||
if (!license || !license?.isAvailable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { isEnabled } = license.getFeature('security');
|
||||
return isEnabled;
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Gives synchronous access to the setupDeps
|
||||
*/
|
||||
|
|
|
@ -9,6 +9,7 @@ import type { Logger } from '@kbn/core/server';
|
|||
import { ReportingCore } from '..';
|
||||
import { registerDeprecationsRoutes } from './internal/deprecations/deprecations';
|
||||
import { registerDiagnosticRoutes } from './internal/diagnostic';
|
||||
import { registerHealthRoute } from './internal/health';
|
||||
import { registerGenerationRoutesInternal } from './internal/generate/generate_from_jobparams';
|
||||
import { registerJobInfoRoutesInternal } from './internal/management/jobs';
|
||||
import { registerGenerationRoutesPublic } from './public/generate_from_jobparams';
|
||||
|
@ -16,6 +17,7 @@ import { registerJobInfoRoutesPublic } from './public/jobs';
|
|||
|
||||
export function registerRoutes(reporting: ReportingCore, logger: Logger) {
|
||||
registerDeprecationsRoutes(reporting, logger);
|
||||
registerHealthRoute(reporting, logger);
|
||||
registerDiagnosticRoutes(reporting, logger);
|
||||
registerGenerationRoutesInternal(reporting, logger);
|
||||
registerJobInfoRoutesInternal(reporting);
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { Logger } from '@kbn/core/server';
|
||||
import { INTERNAL_ROUTES } from '@kbn/reporting-common';
|
||||
import type { ReportingCore } from '../../..';
|
||||
import { authorizedUserPreRouting } from '../../common';
|
||||
|
||||
const path = INTERNAL_ROUTES.HEALTH;
|
||||
export const registerHealthRoute = (reporting: ReportingCore, logger: Logger) => {
|
||||
const { router } = reporting.getPluginSetupDeps();
|
||||
|
||||
router.get(
|
||||
{
|
||||
path,
|
||||
security: {
|
||||
authz: {
|
||||
enabled: false,
|
||||
reason: 'This route is opted out from authorization',
|
||||
},
|
||||
},
|
||||
validate: false,
|
||||
options: { access: 'internal' },
|
||||
},
|
||||
authorizedUserPreRouting(reporting, async (_user, _context, req, res) => {
|
||||
try {
|
||||
const healthInfo = await reporting.getHealthInfo();
|
||||
return res.ok({
|
||||
body: {
|
||||
has_permanent_encryption_key: healthInfo.hasPermanentEncryptionKey,
|
||||
is_sufficiently_secure: healthInfo.isSufficientlySecure,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return res.custom({ statusCode: 500 });
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export { registerHealthRoute } from './health';
|
|
@ -19,6 +19,7 @@ import {
|
|||
} from '@kbn/core/server/mocks';
|
||||
import { dataPluginMock } from '@kbn/data-plugin/server/mocks';
|
||||
import { discoverPluginMock } from '@kbn/discover-plugin/server/mocks';
|
||||
import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks';
|
||||
import { featuresPluginMock } from '@kbn/features-plugin/server/mocks';
|
||||
import { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common';
|
||||
import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks';
|
||||
|
@ -38,6 +39,7 @@ export const createMockPluginSetup = (
|
|||
setupMock: Partial<Record<keyof ReportingInternalSetup, any>>
|
||||
): ReportingInternalSetup => {
|
||||
return {
|
||||
encryptedSavedObjects: encryptedSavedObjectsMock.createSetup(),
|
||||
features: featuresPluginMock.createSetup(),
|
||||
basePath: { set: jest.fn() },
|
||||
router: { get: jest.fn(), post: jest.fn(), put: jest.fn(), delete: jest.fn() },
|
||||
|
|
|
@ -31,6 +31,7 @@ import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
|
|||
|
||||
import { ExportTypesRegistry } from '@kbn/reporting-server/export_types_registry';
|
||||
import type { AuthenticatedUser } from '@kbn/core-security-common';
|
||||
import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server';
|
||||
|
||||
/**
|
||||
* Plugin Setup Contract
|
||||
|
@ -48,6 +49,7 @@ export type ReportingUser = { username: AuthenticatedUser['username'] } | false;
|
|||
export type ScrollConfig = ReportingConfigType['csv']['scroll'];
|
||||
|
||||
export interface ReportingSetupDeps {
|
||||
encryptedSavedObjects: EncryptedSavedObjectsPluginSetup;
|
||||
features: FeaturesPluginSetup;
|
||||
screenshotMode: ScreenshotModePluginSetup;
|
||||
taskManager: TaskManagerSetupContract;
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
"@kbn/screenshot-mode-plugin",
|
||||
"@kbn/share-plugin",
|
||||
"@kbn/ui-actions-plugin",
|
||||
"@kbn/encrypted-saved-objects-plugin",
|
||||
"@kbn/usage-collection-plugin",
|
||||
"@kbn/field-formats-plugin",
|
||||
"@kbn/features-plugin",
|
||||
|
|
Loading…
Add table
Reference in a new issue