Skip to content

Commit

Permalink
[Alerting] Hides the alert SavedObjects type (#66719)
Browse files Browse the repository at this point in the history
* make alert saved object type hidden

* fix support for hidden alert type in alerting tests

* updated api docs

* fixed some missing types and unused imports

* fixed test broken by field rename

* added support for including hidden types in saved objects client

* fixed merge conflict

* cleaned up some test descriptions

* adds a getClient api to Encrypted Saved Objects

* fixed alerts fixture

* added missing plugin type in alerting

* removed unused field

* chaged ESO api to an options object as per Security teams request

* fixed usage of eso client

* fixed typos and oversights

* split alerts file into two - for actions and alerts
  • Loading branch information
gmmorris committed May 21, 2020
1 parent a71e22f commit 6a499d4
Show file tree
Hide file tree
Showing 18 changed files with 648 additions and 507 deletions.
40 changes: 28 additions & 12 deletions x-pack/plugins/alerting/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ export interface AlertingPluginsSetup {
}
export interface AlertingPluginsStart {
actions: ActionsPluginStartContract;
encryptedSavedObjects: EncryptedSavedObjectsPluginStart;
taskManager: TaskManagerStartContract;
encryptedSavedObjects: EncryptedSavedObjectsPluginStart;
}

export class AlertingPlugin {
Expand Down Expand Up @@ -126,6 +126,7 @@ export class AlertingPlugin {
this.licenseState = new LicenseState(plugins.licensing.license$);
this.spaces = plugins.spaces?.spacesService;
this.security = plugins.security;

this.isESOUsingEphemeralEncryptionKey =
plugins.encryptedSavedObjects.usingEphemeralEncryptionKey;

Expand Down Expand Up @@ -164,7 +165,7 @@ export class AlertingPlugin {
});
}

core.http.registerRouteHandlerContext('alerting', this.createRouteHandlerContext());
core.http.registerRouteHandlerContext('alerting', this.createRouteHandlerContext(core));

// Routes
const router = core.http.createRouter();
Expand Down Expand Up @@ -201,7 +202,9 @@ export class AlertingPlugin {
security,
} = this;

const encryptedSavedObjectsClient = plugins.encryptedSavedObjects.getClient();
const encryptedSavedObjectsClient = plugins.encryptedSavedObjects.getClient({
includedHiddenTypes: ['alert'],
});

alertsClientFactory.initialize({
alertTypeRegistry: alertTypeRegistry!,
Expand Down Expand Up @@ -231,26 +234,32 @@ export class AlertingPlugin {
return {
listTypes: alertTypeRegistry!.list.bind(this.alertTypeRegistry!),
// Ability to get an alerts client from legacy code
getAlertsClientWithRequest(request: KibanaRequest) {
getAlertsClientWithRequest: (request: KibanaRequest) => {
if (isESOUsingEphemeralEncryptionKey === true) {
throw new Error(
`Unable to create alerts client due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml`
);
}
return alertsClientFactory!.create(request, core.savedObjects.getScopedClient(request));
return alertsClientFactory!.create(
request,
this.getScopedClientWithAlertSavedObjectType(core.savedObjects, request)
);
},
};
}

private createRouteHandlerContext = (): IContextProvider<
RequestHandler<unknown, unknown, unknown>,
'alerting'
> => {
private createRouteHandlerContext = (
core: CoreSetup
): IContextProvider<RequestHandler<unknown, unknown, unknown>, 'alerting'> => {
const { alertTypeRegistry, alertsClientFactory } = this;
return async function alertsRouteHandlerContext(context, request) {
return async (context, request) => {
const [{ savedObjects }] = await core.getStartServices();
return {
getAlertsClient: () => {
return alertsClientFactory!.create(request, context.core.savedObjects.client);
return alertsClientFactory!.create(
request,
this.getScopedClientWithAlertSavedObjectType(savedObjects, request)
);
},
listTypes: alertTypeRegistry!.list.bind(alertTypeRegistry!),
};
Expand All @@ -263,7 +272,7 @@ export class AlertingPlugin {
): (request: KibanaRequest) => Services {
return request => ({
callCluster: elasticsearch.legacy.client.asScoped(request).callAsCurrentUser,
savedObjectsClient: savedObjects.getScopedClient(request),
savedObjectsClient: this.getScopedClientWithAlertSavedObjectType(savedObjects, request),
getScopedCallCluster(clusterClient: IClusterClient) {
return clusterClient.asScoped(request).callAsCurrentUser;
},
Expand All @@ -278,6 +287,13 @@ export class AlertingPlugin {
return this.spaces && spaceId ? this.spaces.getBasePath(spaceId) : this.serverBasePath!;
};

private getScopedClientWithAlertSavedObjectType(
savedObjects: SavedObjectsServiceStart,
request: KibanaRequest
) {
return savedObjects.getScopedClient(request, { includedHiddenTypes: ['alert'] });
}

public stop() {
if (this.licenseState) {
this.licenseState.clean();
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/alerting/server/saved_objects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function setupSavedObjects(
) {
savedObjects.registerType({
name: 'alert',
hidden: false,
hidden: true,
namespaceType: 'single',
mappings: mappings.alert,
});
Expand Down
16 changes: 14 additions & 2 deletions x-pack/plugins/encrypted_saved_objects/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,19 @@ router.get(
...
```

5. To retrieve Saved Object with decrypted content use the dedicated `getDecryptedAsInternalUser` API method.
5. Instantiate an EncryptedSavedObjects client so that you can interact with Saved Objects whose content has been encrypted.

```typescript
const esoClient = encryptedSavedObjects.getClient();
```

If your SavedObject type is a _hidden_ type, then you will have to specify it as an included type:

```typescript
const esoClient = encryptedSavedObjects.getClient({ includedHiddenTypes: ['myHiddenType'] });
```

6. To retrieve Saved Object with decrypted content use the dedicated `getDecryptedAsInternalUser` API method.

**Note:** As name suggests the method will retrieve the encrypted values and decrypt them on behalf of the internal Kibana
user to make it possible to use this method even when user request context is not available (e.g. in background tasks).
Expand All @@ -77,7 +89,7 @@ and preferably only as a part of the Kibana server routines that are outside of
user has control over.

```typescript
const savedObjectWithDecryptedContent = await encryptedSavedObjects.getDecryptedAsInternalUser(
const savedObjectWithDecryptedContent = await esoClient.getDecryptedAsInternalUser(
'my-saved-object-type',
'saved-object-id'
);
Expand Down
6 changes: 3 additions & 3 deletions x-pack/plugins/encrypted_saved_objects/server/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import { EncryptedSavedObjectsPluginSetup, EncryptedSavedObjectsPluginStart } from './plugin';
import { EncryptedSavedObjectsClient } from './saved_objects';
import { EncryptedSavedObjectsClient, EncryptedSavedObjectsClientOptions } from './saved_objects';

function createEncryptedSavedObjectsSetupMock() {
return {
Expand All @@ -18,11 +18,11 @@ function createEncryptedSavedObjectsSetupMock() {
function createEncryptedSavedObjectsStartMock() {
return {
isEncryptionError: jest.fn(),
getClient: jest.fn(() => createEncryptedSavedObjectsClienttMock()),
getClient: jest.fn(opts => createEncryptedSavedObjectsClienttMock(opts)),
} as jest.Mocked<EncryptedSavedObjectsPluginStart>;
}

function createEncryptedSavedObjectsClienttMock() {
function createEncryptedSavedObjectsClienttMock(opts?: EncryptedSavedObjectsClientOptions) {
return {
getDecryptedAsInternalUser: jest.fn(),
} as jest.Mocked<EncryptedSavedObjectsClient>;
Expand Down
8 changes: 4 additions & 4 deletions x-pack/plugins/encrypted_saved_objects/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
EncryptionError,
} from './crypto';
import { EncryptedSavedObjectsAuditLogger } from './audit';
import { SavedObjectsSetup, setupSavedObjects } from './saved_objects';
import { setupSavedObjects, ClientInstanciator } from './saved_objects';

export interface PluginsSetup {
security?: SecurityPluginSetup;
Expand All @@ -28,7 +28,7 @@ export interface EncryptedSavedObjectsPluginSetup {

export interface EncryptedSavedObjectsPluginStart {
isEncryptionError: (error: Error) => boolean;
getClient: SavedObjectsSetup;
getClient: ClientInstanciator;
}

/**
Expand All @@ -46,7 +46,7 @@ export interface LegacyAPI {
*/
export class Plugin {
private readonly logger: Logger;
private savedObjectsSetup!: SavedObjectsSetup;
private savedObjectsSetup!: ClientInstanciator;

private legacyAPI?: LegacyAPI;
private readonly getLegacyAPI = () => {
Expand Down Expand Up @@ -95,7 +95,7 @@ export class Plugin {
this.logger.debug('Starting plugin');
return {
isEncryptionError: (error: Error) => error instanceof EncryptionError,
getClient: (includedHiddenTypes?: string[]) => this.savedObjectsSetup(includedHiddenTypes),
getClient: (options = {}) => this.savedObjectsSetup(options),
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { SavedObjectsSetup, setupSavedObjects } from '.';
import { ClientInstanciator, setupSavedObjects } from '.';

import {
coreMock,
Expand All @@ -24,7 +24,7 @@ import {
import { EncryptedSavedObjectsService } from '../crypto';

describe('#setupSavedObjects', () => {
let setupContract: SavedObjectsSetup;
let setupContract: ClientInstanciator;
let coreStartMock: ReturnType<typeof coreMock.createStart>;
let coreSetupMock: ReturnType<typeof coreMock.createSetup>;
let mockSavedObjectsRepository: jest.Mocked<ISavedObjectsRepository>;
Expand Down Expand Up @@ -91,7 +91,7 @@ describe('#setupSavedObjects', () => {

describe('#setupContract', () => {
it('includes hiddenTypes when specified', async () => {
await setupContract(['hiddenType']);
await setupContract({ includedHiddenTypes: ['hiddenType'] });
expect(coreStartMock.savedObjects.createInternalRepository).toHaveBeenCalledWith([
'hiddenType',
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@ interface SetupSavedObjectsParams {
getStartServices: StartServicesAccessor;
}

export type SavedObjectsSetup = (includedHiddenTypes?: string[]) => EncryptedSavedObjectsClient;
export type ClientInstanciator = (
options?: EncryptedSavedObjectsClientOptions
) => EncryptedSavedObjectsClient;

export interface EncryptedSavedObjectsClientOptions {
includedHiddenTypes?: string[];
}

export interface EncryptedSavedObjectsClient {
getDecryptedAsInternalUser: <T = unknown>(
Expand All @@ -38,7 +44,7 @@ export function setupSavedObjects({
savedObjects,
security,
getStartServices,
}: SetupSavedObjectsParams): SavedObjectsSetup {
}: SetupSavedObjectsParams): ClientInstanciator {
// Register custom saved object client that will encrypt, decrypt and strip saved object
// attributes where appropriate for any saved object repository request. We choose max possible
// priority for this wrapper to allow all other wrappers to set proper `namespace` for the Saved
Expand All @@ -56,11 +62,11 @@ export function setupSavedObjects({
})
);

return (includedHiddenTypes?: string[]) => {
return clientOpts => {
const internalRepositoryAndTypeRegistryPromise = getStartServices().then(
([core]) =>
[
core.savedObjects.createInternalRepository(includedHiddenTypes),
core.savedObjects.createInternalRepository(clientOpts?.includedHiddenTypes),
core.savedObjects.getTypeRegistry(),
] as [ISavedObjectsRepository, ISavedObjectTypeRegistry]
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import { SpacesPluginSetup } from '../../../../../../../plugins/spaces/server';
interface FixtureSetupDeps {
spaces?: SpacesPluginSetup;
}

interface FixtureStartDeps {
encryptedSavedObjects: EncryptedSavedObjectsPluginStart;
}
Expand All @@ -44,12 +43,14 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
): Promise<IKibanaResponse<any>> {
try {
let namespace: string | undefined;
const [, { encryptedSavedObjects }] = await core.getStartServices();
if (spaces && req.body.spaceId) {
namespace = spaces.spacesService.spaceIdToNamespace(req.body.spaceId);
}
const [, { encryptedSavedObjects }] = await core.getStartServices();
await encryptedSavedObjects
.getClient()
.getClient({
includedHiddenTypes: ['alert'],
})
.getDecryptedAsInternalUser(req.body.type, req.body.id, {
namespace,
});
Expand Down
Loading

0 comments on commit 6a499d4

Please sign in to comment.