Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/kbn-check-saved-objects-cli/moon.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ dependsOn:
- '@kbn/migrator-test-kit'
- '@kbn/config-schema'
- '@kbn/repo-info'
- '@kbn/encrypted-saved-objects-plugin'
tags:
- shared-common
- package
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export function runCheckSavedObjectsCli() {
ctx.kibanaServer = await setupKibana();
const coreStart = await ctx.kibanaServer.start();
ctx.registeredTypes = coreStart!.savedObjects.getTypeRegistry().getAllTypes();
ctx.encryptedSavedObjects = coreStart._plugins?.get('encryptedSavedObjects');
},
enabled: !server && !test,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import type { ListrTask } from 'listr2';
import { defaultKibanaIndex, getKibanaMigratorTestKit } from '@kbn/migrator-test-kit';
import type { SavedObjectsBulkCreateObject } from '@kbn/core-saved-objects-api-server';
import type { Task, TaskContext } from '../types';
import { encryptionOverrides, type Task, type TaskContext } from '../types';
import { getPreviousVersionType } from '../../migrations';
import { checkDocuments } from './check_documents';
import type { FixtureTemplate } from '../../migrations/fixtures';
Expand All @@ -26,7 +26,16 @@ export const createBaseline: Task = async (ctx, task) => {
client,
runMigrations: initSystemIndex,
savedObjectsRepository,
} = await getKibanaMigratorTestKit({ types: previousVersionTypes });
} = await getKibanaMigratorTestKit({
types: previousVersionTypes,
encryptionExtensionFactory: ctx.encryptedSavedObjects
? (typeRegistry) =>
ctx.encryptedSavedObjects!.__testCreateDangerousExtension(
typeRegistry,
encryptionOverrides
)
: undefined,
});
const subtasks: ListrTask<TaskContext>[] = [
{
title: `Delete pre-existing '${defaultKibanaIndex}' index`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import type { ListrTask } from 'listr2';
import { getKibanaMigratorTestKit } from '@kbn/migrator-test-kit';
import type { Task, TaskContext } from '../types';
import { encryptionOverrides, type Task, type TaskContext } from '../types';
import { getPreviousVersionType } from '../../migrations';
import { checkDocuments } from './check_documents';

Expand All @@ -21,7 +21,16 @@ export const testRollback: Task = async (ctx, task) => {
);

const { runMigrations: performRollback, savedObjectsRepository } = await getKibanaMigratorTestKit(
{ types: previousVersionTypes }
{
types: previousVersionTypes,
encryptionExtensionFactory: ctx.encryptedSavedObjects
? (typeRegistry) =>
ctx.encryptedSavedObjects!.__testCreateDangerousExtension(
typeRegistry,
encryptionOverrides
)
: undefined,
}
);

const subtasks: ListrTask<TaskContext>[] = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import { checkDocuments } from './check_documents';
export const testUpgrade: Task = async (ctx, task) => {
const { runMigrations, savedObjectsRepository } = await getKibanaMigratorTestKit({
types: ctx.updatedTypes,
encryptionExtensionFactory: ctx.encryptedSavedObjects
? (typeRegistry) => ctx.encryptedSavedObjects!.__testCreateDangerousExtension(typeRegistry)
: undefined,
});

const subtasks: ListrTask<TaskContext>[] = [
Expand Down
21 changes: 21 additions & 0 deletions packages/kbn-check-saved-objects-cli/src/commands/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import type { SavedObjectsType } from '@kbn/core-saved-objects-server';
import type { SavedObjectsTypeMappingDefinitions } from '@kbn/core-saved-objects-base-server-internal';
import type { TestElasticsearchUtils } from '@kbn/core-test-helpers-kbn-server';
import type { Root } from '@kbn/core-root-server-internal';
import type {
EncryptedSavedObjectsPluginStart,
EncryptedSavedObjectTypeRegistration,
} from '@kbn/encrypted-saved-objects-plugin/server';
import type { MigrationSnapshot } from '../types';
import type { TypeVersionFixtures } from '../migrations/fixtures/types';

Expand All @@ -24,6 +28,7 @@ export interface TaskContext {
esServer?: TestElasticsearchUtils;
kibanaServer?: Root;
registeredTypes?: SavedObjectsType<any>[];
encryptedSavedObjects?: EncryptedSavedObjectsPluginStart;
from?: MigrationSnapshot;
to?: MigrationSnapshot;
updatedTypes: SavedObjectsType<any>[];
Expand All @@ -37,3 +42,19 @@ export interface TaskContext {
test: boolean; // whether the script is running with TEST data
fix: boolean;
}

export const encryptionOverrides: EncryptedSavedObjectTypeRegistration[] = [
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure someone will inject something soon! 😆

// Placeholder for manually specifying any previous versions of ESO registrations
// Example:
// {
// type: 'connector_token',
// attributesToEncrypt: new Set(['token']),
// attributesToIncludeInAAD: new Set([
// 'connectorId',
// 'tokenType',
// 'expiresAt',
// 'createdAt',
// 'updatedAt',
// ]),
// },
];
1 change: 1 addition & 0 deletions packages/kbn-check-saved-objects-cli/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@
"@kbn/migrator-test-kit",
"@kbn/config-schema",
"@kbn/repo-info",
"@kbn/encrypted-saved-objects-plugin",
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,5 @@ export interface InternalCoreStart {
pricing: PricingServiceStart;
injection: InternalCoreDiServiceStart;
dataStreams: DataStreamsStart;
_plugins?: Map<string, any>;
}
3 changes: 2 additions & 1 deletion src/core/packages/root/server-internal/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,8 @@ export class Server {

this.coreApp.start(this.coreStart);

await this.plugins.start(this.coreStart);
const { contracts } = await this.plugins.start(this.coreStart);
this.coreStart._plugins = contracts;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is OK given this is all internal to Core.


await this.http.start();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ import {
import { AgentManager, configureClient } from '@kbn/core-elasticsearch-client-server-internal';
import { type LoggingConfigType, LoggingSystem } from '@kbn/core-logging-server-internal';

import type { ISavedObjectTypeRegistry, SavedObjectsType } from '@kbn/core-saved-objects-server';
import type {
ISavedObjectTypeRegistry,
ISavedObjectsEncryptionExtension,
SavedObjectsType,
} from '@kbn/core-saved-objects-server';
import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server';
import { esTestConfig, kibanaServerTestUser } from '@kbn/test';
import type { LoggerFactory } from '@kbn/logging';
Expand Down Expand Up @@ -81,6 +85,9 @@ export interface KibanaMigratorTestKitParams {
hashToVersionMap?: Record<string, string>;
logFilePath?: string;
clientWrapperFactory?: ElasticsearchClientWrapperFactory;
encryptionExtensionFactory?: (
typeRegistry: ISavedObjectTypeRegistry
) => ISavedObjectsEncryptionExtension;
}

export interface KibanaMigratorTestKit {
Expand Down Expand Up @@ -145,6 +152,7 @@ export const getKibanaMigratorTestKit = async ({
logFilePath = defaultLogFilePath,
nodeRoles = defaultNodeRoles,
clientWrapperFactory,
encryptionExtensionFactory,
}: KibanaMigratorTestKitParams = {}): Promise<KibanaMigratorTestKit> => {
let hasRun = false;
const loggingSystem = new LoggingSystem();
Expand Down Expand Up @@ -190,6 +198,8 @@ export const getKibanaMigratorTestKit = async ({
}
};

const encryptionExtension = encryptionExtensionFactory?.(typeRegistry);

const savedObjectsRepository = SavedObjectsRepository.createRepository(
migrator,
typeRegistry,
Expand All @@ -200,7 +210,10 @@ export const getKibanaMigratorTestKit = async ({
typeRegistry
.getAllTypes()
.filter(({ hidden }) => hidden)
.map(({ name }) => name)
.map(({ name }) => name),
{
encryptionExtension,
}
);

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import type { EncryptedSavedObjectTypeRegistration } from './encrypted_saved_obj
*/
export class EncryptedSavedObjectAttributesDefinition {
public readonly attributesToEncrypt: ReadonlySet<string>;
private readonly attributesToIncludeInAAD: ReadonlySet<string> | undefined;
public readonly attributesToIncludeInAAD: ReadonlySet<string> | undefined;
private readonly attributesToStrip: ReadonlySet<string>;
public readonly enforceRandomId: boolean;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2426,3 +2426,175 @@ describe('#shouldEnforceRandomId', () => {
expect(service.shouldEnforceRandomId('known-type-2')).toBe(true);
});
});

describe('#__dangerousClone', () => {
it('returns a new EncryptedSavedObjectsService instance', () => {
const clone = service.__dangerousClone();
expect(clone).toBeInstanceOf(EncryptedSavedObjectsService);
expect(clone).not.toBe(service);
});

it('clones existing registered types from the original service', () => {
service.registerType({
type: 'existing-type',
attributesToEncrypt: new Set(['password']),
});

const clone = service.__dangerousClone();
expect(clone.isRegistered('existing-type')).toBe(true);
});

it('registers type registration overrides', () => {
const clone = service.__dangerousClone([
{ type: 'override-type', attributesToEncrypt: new Set(['secret']) },
]);

expect(clone.isRegistered('override-type')).toBe(true);
});

it('skips existing type registrations when override has matching type', () => {
service.registerType({
type: 'shared-type',
attributesToEncrypt: new Set(['oldSecret']),
});

const clone = service.__dangerousClone([
{ type: 'shared-type', attributesToEncrypt: new Set(['newSecret']) },
]);

expect(clone.isRegistered('shared-type')).toBe(true);
const registeredTypes = clone.getRegisteredTypes();
expect(registeredTypes.filter((t) => t === 'shared-type').length).toBe(1);
});

it('registers both overrides and non-conflicting existing types', () => {
service.registerType({
type: 'existing-type-1',
attributesToEncrypt: new Set(['secret1']),
});
service.registerType({
type: 'existing-type-2',
attributesToEncrypt: new Set(['secret2']),
});

const clone = service.__dangerousClone([
{ type: 'override-type', attributesToEncrypt: new Set(['overrideSecret']) },
]);

expect(clone.isRegistered('existing-type-1')).toBe(true);
expect(clone.isRegistered('existing-type-2')).toBe(true);
expect(clone.isRegistered('override-type')).toBe(true);
});

it('dangerously exposes string attributes from overrides', () => {
const registerTypeSpy = jest.spyOn(EncryptedSavedObjectsService.prototype, 'registerType');

service.__dangerousClone([
{
type: 'test-type',
attributesToEncrypt: new Set(['secret', 'apiKey']),
},
]);

const registeredArgs = registerTypeSpy.mock.calls.find(([reg]) => reg.type === 'test-type');
expect(registeredArgs).toBeDefined();
const registeredAttrs = [...registeredArgs![0].attributesToEncrypt];
expect(registeredAttrs).toEqual(
expect.arrayContaining([
{ key: 'secret', dangerouslyExposeValue: true },
{ key: 'apiKey', dangerouslyExposeValue: true },
])
);

registerTypeSpy.mockRestore();
});

it('dangerously exposes object attributes without dangerouslyExposeValue from overrides', () => {
const registerTypeSpy = jest.spyOn(EncryptedSavedObjectsService.prototype, 'registerType');

service.__dangerousClone([
{
type: 'test-type',
attributesToEncrypt: new Set([{ key: 'token' }, { key: 'password' }]),
},
]);

const registeredArgs = registerTypeSpy.mock.calls.find(([reg]) => reg.type === 'test-type');
expect(registeredArgs).toBeDefined();
const registeredAttrs = [...registeredArgs![0].attributesToEncrypt];
expect(registeredAttrs).toEqual(
expect.arrayContaining([
{ key: 'token', dangerouslyExposeValue: true },
{ key: 'password', dangerouslyExposeValue: true },
])
);

registerTypeSpy.mockRestore();
});

it('dangerously exposes attributes from existing registrations', () => {
service.registerType({
type: 'existing-type',
attributesToEncrypt: new Set(['secret']),
});

const registerTypeSpy = jest.spyOn(EncryptedSavedObjectsService.prototype, 'registerType');

service.__dangerousClone();

const registeredArgs = registerTypeSpy.mock.calls.find(([reg]) => reg.type === 'existing-type');
expect(registeredArgs).toBeDefined();
const registeredAttrs = [...registeredArgs![0].attributesToEncrypt];
expect(registeredAttrs).toEqual([{ key: 'secret', dangerouslyExposeValue: true }]);

registerTypeSpy.mockRestore();
});

it('preserves dangerouslyExposeValue when already set to true', () => {
const registerTypeSpy = jest.spyOn(EncryptedSavedObjectsService.prototype, 'registerType');

service.__dangerousClone([
{
type: 'test-type',
attributesToEncrypt: new Set([{ key: 'token', dangerouslyExposeValue: true }]),
},
]);

const registeredArgs = registerTypeSpy.mock.calls.find(([reg]) => reg.type === 'test-type');
expect(registeredArgs).toBeDefined();
const registeredAttrs = [...registeredArgs![0].attributesToEncrypt];
expect(registeredAttrs).toEqual([{ key: 'token', dangerouslyExposeValue: true }]);

registerTypeSpy.mockRestore();
});

it('sets dangerouslyExposeValue to true if it was already set to false', () => {
const registerTypeSpy = jest.spyOn(EncryptedSavedObjectsService.prototype, 'registerType');

service.__dangerousClone([
{
type: 'test-type',
attributesToEncrypt: new Set([{ key: 'token', dangerouslyExposeValue: false }]),
},
]);

const registeredArgs = registerTypeSpy.mock.calls.find(([reg]) => reg.type === 'test-type');
expect(registeredArgs).toBeDefined();
const registeredAttrs = [...registeredArgs![0].attributesToEncrypt];
expect(registeredAttrs).toEqual([{ key: 'token', dangerouslyExposeValue: true }]);

registerTypeSpy.mockRestore();
});

it('clones without any overrides when called with no arguments', () => {
service.registerType({
type: 'type-a',
attributesToEncrypt: new Set(['attr1']),
});

const clone = service.__dangerousClone();

expect(clone.isRegistered('type-a')).toBe(true);
expect(clone.getRegisteredTypes()).toEqual(['type-a']);
});
});
Loading
Loading