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
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe('api_key_utils', () => {
api_key: 'apiKey',
});

const result = await createApiKey([mockTask], request, true, coreStart.security);
const result = await createApiKey([mockTask], request, coreStart.security);
const apiKeyResult = result.get('task');
const decodedApiKey = Buffer.from(apiKeyResult!.apiKey, 'base64').toString();
expect(decodedApiKey).toEqual('apiKeyId:apiKey');
Expand Down Expand Up @@ -107,7 +107,7 @@ describe('api_key_utils', () => {
coreStart.security.authc.apiKeys.areAPIKeysEnabled = jest.fn().mockReturnValueOnce(true);
coreStart.security.authc.getCurrentUser = jest.fn().mockReturnValue(mockUser);

const result = await createApiKey([mockTask], request, true, coreStart.security);
const result = await createApiKey([mockTask], request, coreStart.security);
const apiKeyResult = result.get('task');
const decodedApiKey = Buffer.from(apiKeyResult!.apiKey, 'base64').toString();
expect(decodedApiKey).toEqual('apiKeyId:apiKey');
Expand All @@ -117,26 +117,13 @@ describe('api_key_utils', () => {
expect(coreStart.security.authc.apiKeys.grantAsInternalUser).not.toHaveBeenCalled();
});

test('should throw if canEncryptSo is false', async () => {
const request = httpServerMock.createKibanaRequest();
const coreStart = coreMock.createStart();
await expect(
createApiKey([mockTask], request, false, coreStart.security)
).rejects.toMatchObject({
message:
'Unable to create API keys because the Encrypted Saved Objects plugin has not been registered or is missing encryption key.',
});
});

test('should throw if API keys are not enabled', async () => {
const request = httpServerMock.createKibanaRequest();
const coreStart = coreMock.createStart();
coreStart.security.authc.apiKeys.areAPIKeysEnabled = jest.fn().mockReturnValueOnce(false);
coreStart.security.authc.getCurrentUser = jest.fn().mockReturnValue(null);

await expect(
createApiKey([mockTask], request, true, coreStart.security)
).rejects.toMatchObject({
await expect(createApiKey([mockTask], request, coreStart.security)).rejects.toMatchObject({
message: 'API keys are not enabled, cannot create API key.',
});
});
Expand All @@ -146,9 +133,7 @@ describe('api_key_utils', () => {
const coreStart = coreMock.createStart();
coreStart.security.authc.apiKeys.areAPIKeysEnabled = jest.fn().mockReturnValueOnce(true);

await expect(
createApiKey([mockTask], request, true, coreStart.security)
).rejects.toMatchObject({
await expect(createApiKey([mockTask], request, coreStart.security)).rejects.toMatchObject({
message: 'Cannot authenticate current user.',
});
});
Expand All @@ -163,9 +148,7 @@ describe('api_key_utils', () => {
coreStart.security.authc.apiKeys.areAPIKeysEnabled = jest.fn().mockReturnValueOnce(true);
coreStart.security.authc.getCurrentUser = jest.fn().mockReturnValueOnce(mockUser);
coreStart.security.authc.apiKeys.grantAsInternalUser = jest.fn().mockResolvedValueOnce(null);
await expect(
createApiKey([mockTask], request, true, coreStart.security)
).rejects.toMatchObject({
await expect(createApiKey([mockTask], request, coreStart.security)).rejects.toMatchObject({
message: 'Could not create API key.',
});
});
Expand Down Expand Up @@ -198,7 +181,6 @@ describe('api_key_utils', () => {
const result = await getApiKeyAndUserScope(
[mockTask],
request,
true,
coreStart.security,
spacesStart
);
Expand Down Expand Up @@ -235,7 +217,6 @@ describe('api_key_utils', () => {
const result = await getApiKeyAndUserScope(
[mockTask],
request,
true,
coreStart.security,
spacesStart
);
Expand Down Expand Up @@ -271,7 +252,6 @@ describe('api_key_utils', () => {
const result = await getApiKeyAndUserScope(
[mockTask],
request,
true,
coreStart.security,
spacesStart
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,8 @@ export const getApiKeyFromRequest = (request: KibanaRequest) => {
export const createApiKey = async (
taskInstances: TaskInstance[],
request: KibanaRequest,
canEncryptSo: boolean,
security: SecurityServiceStart
) => {
if (!canEncryptSo) {
throw Error(
'Unable to create API keys because the Encrypted Saved Objects plugin has not been registered or is missing encryption key.'
);
}

if (!(await security.authc.apiKeys.areAPIKeysEnabled())) {
throw Error('API keys are not enabled, cannot create API key.');
}
Expand Down Expand Up @@ -132,11 +125,10 @@ export const createApiKey = async (
export const getApiKeyAndUserScope = async (
taskInstances: TaskInstance[],
request: KibanaRequest,
canEncryptSo: boolean,
security: SecurityServiceStart,
spaces?: SpacesPluginStart
): Promise<Map<string, ApiKeyAndUserScope>> => {
const apiKeyByTaskIdMap = await createApiKey(taskInstances, request, canEncryptSo, security);
const apiKeyByTaskIdMap = await createApiKey(taskInstances, request, security);
const space = await spaces?.spacesService.getActiveSpace(request);
const user = security.authc.getCurrentUser(request);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ describe('TaskStore', () => {
describe('schedule', () => {
let store: TaskStore;

beforeAll(() => {
beforeEach(() => {
store = new TaskStore({
logger: mockLogger(),
index: 'tasky',
Expand Down Expand Up @@ -322,7 +322,6 @@ describe('TaskStore', () => {
expect(getApiKeyAndUserScope).toHaveBeenCalledWith(
[task],
request,
true,
coreStart.security,
spacesStart
);
Expand All @@ -348,6 +347,41 @@ describe('TaskStore', () => {
});
});

test('errors when scheduling a task with API key if unable to encrypt SO', async () => {
store = new TaskStore({
logger: mockLogger(),
index: 'tasky',
taskManagerId: '',
serializer,
esClient: elasticsearchServiceMock.createClusterClient().asInternalUser,
definitions: taskDefinitions,
savedObjectsRepository: savedObjectsClient,
adHocTaskCounter,
allowReadingInvalidState: false,
requestTimeouts: {
update_by_query: 1000,
},
savedObjectsService: coreStart.savedObjects,
security: coreStart.security,
spaces: spacesStart,
canEncryptSavedObjects: false,
});

const task = {
id: 'id',
params: { hello: 'world' },
state: { foo: 'bar' },
taskType: 'report',
traceparent: 'apmTraceparent',
};

const request = httpServerMock.createKibanaRequest();

await expect(store.schedule(task as TaskInstance, { request })).rejects.toThrow(
'Unable to schedule task(s) with API keys because the Encrypted Saved Objects plugin has not been registered or is missing encryption key.'
);
});

test('errors if the task type is unknown', async () => {
await expect(testSchedule({ taskType: 'nope', params: {}, state: {} })).rejects.toThrow(
/Unsupported task type "nope"/i
Expand Down Expand Up @@ -2073,7 +2107,7 @@ describe('TaskStore', () => {
describe('bulkSchedule', () => {
let store: TaskStore;

beforeAll(() => {
beforeEach(() => {
store = new TaskStore({
logger: mockLogger(),
index: 'tasky',
Expand Down Expand Up @@ -2296,7 +2330,6 @@ describe('TaskStore', () => {
expect(getApiKeyAndUserScope).toHaveBeenCalledWith(
[task1, task2],
request,
true,
coreStart.security,
spacesStart
);
Expand Down Expand Up @@ -2343,6 +2376,40 @@ describe('TaskStore', () => {
]);
});

test('errors when bulk scheduling a task with API key if unable to encrypt SO', async () => {
store = new TaskStore({
logger: mockLogger(),
index: 'tasky',
taskManagerId: '',
serializer,
esClient: elasticsearchServiceMock.createClusterClient().asInternalUser,
definitions: taskDefinitions,
savedObjectsRepository: savedObjectsClient,
adHocTaskCounter,
allowReadingInvalidState: false,
requestTimeouts: {
update_by_query: 1000,
},
savedObjectsService: coreStart.savedObjects,
security: coreStart.security,
spaces: spacesStart,
canEncryptSavedObjects: false,
});

const task1 = {
id: 'task1',
params: { hello: 'world' },
state: { foo: 'bar' },
taskType: 'report',
};

const request = httpServerMock.createKibanaRequest();

await expect(store.bulkSchedule([task1 as TaskInstance], { request })).rejects.toThrow(
'Unable to schedule task(s) with API keys because the Encrypted Saved Objects plugin has not been registered or is missing encryption key.'
);
});

test('errors if API key could not be created', async () => {
const task = {
id: 'id',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,17 @@ export class TaskStore {
return !!(this.esoClient && this.canEncryptSavedObjects);
}

private validateCanEncryptSavedObjects(request?: KibanaRequest) {
if (!request) {
return;
}
if (!this.canEncryptSo()) {
throw Error(
'Unable to schedule task(s) with API keys because the Encrypted Saved Objects plugin has not been registered or is missing encryption key.'
);
}
}

private getSoClientForCreate(options: ApiKeyOptions) {
if (options.request) {
return this.savedObjectsService.getScopedClient(options.request, {
Expand All @@ -206,7 +217,7 @@ export class TaskStore {
return this.savedObjectsRepository;
}

private async maybeGetApiKeyFromRequest(taskInstances: TaskInstance[], request?: KibanaRequest) {
private async getApiKeyFromRequest(taskInstances: TaskInstance[], request?: KibanaRequest) {
if (!request) {
return null;
}
Expand All @@ -216,7 +227,6 @@ export class TaskStore {
userScopeAndApiKey = await getApiKeyAndUserScope(
taskInstances,
request,
this.canEncryptSo(),
this.security,
this.spaces
);
Expand Down Expand Up @@ -303,10 +313,16 @@ export class TaskStore {
taskInstance: TaskInstance,
options?: ApiKeyOptions
): Promise<ConcreteTaskInstance> {
try {
this.validateCanEncryptSavedObjects(options?.request);
} catch (e) {
this.errors$.next(e);
throw e;
}
this.definitions.ensureHas(taskInstance.taskType);

const apiKeyAndUserScopeMap =
(await this.maybeGetApiKeyFromRequest([taskInstance], options?.request)) || new Map();
(await this.getApiKeyFromRequest([taskInstance], options?.request)) || new Map();
const { apiKey, userScope } = apiKeyAndUserScopeMap.get(taskInstance.id) || {};

const soClient = this.getSoClientForCreate(options || {});
Expand Down Expand Up @@ -351,8 +367,14 @@ export class TaskStore {
taskInstances: TaskInstance[],
options?: ApiKeyOptions
): Promise<ConcreteTaskInstance[]> {
try {
this.validateCanEncryptSavedObjects(options?.request);
} catch (e) {
this.errors$.next(e);
throw e;
}
const apiKeyAndUserScopeMap =
(await this.maybeGetApiKeyFromRequest(taskInstances, options?.request)) || new Map();
(await this.getApiKeyFromRequest(taskInstances, options?.request)) || new Map();

const soClient = this.getSoClientForCreate(options || {});

Expand Down