Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds API tests for APM agent keys #125302

Merged
merged 4 commits into from
Feb 11, 2022
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
36 changes: 35 additions & 1 deletion x-pack/test/apm_api_integration/common/authentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
* 2.0.
*/

import { Client } from '@elastic/elasticsearch';
import { SecurityServiceProvider } from '../../../../test/common/services/security';
import { PrivilegeType } from '../../../plugins/apm/common/privilege_type';

type SecurityService = Awaited<ReturnType<typeof SecurityServiceProvider>>;

Expand All @@ -15,6 +17,8 @@ export enum ApmUser {
apmWriteUser = 'apm_write_user',
apmAnnotationsWriteUser = 'apm_annotations_write_user',
apmReadUserWithoutMlAccess = 'apm_read_user_without_ml_access',
apmManageOwnAgentKeys = 'apm_manage_own_agent_keys',
apmManageOwnAndCreateAgentKeys = 'apm_manage_own_and_create_agent_keys',
}

// TODO: Going forward we want to use the built-in roles `viewer` and `editor`. However ML privileges are not included in the built-in roles
Expand Down Expand Up @@ -75,6 +79,20 @@ const roles = {
],
},
},
[ApmUser.apmManageOwnAgentKeys]: {
elasticsearch: {
cluster: ['manage_own_api_key'],
},
},
[ApmUser.apmManageOwnAndCreateAgentKeys]: {
applications: [
{
application: 'apm',
privileges: [PrivilegeType.AGENT_CONFIG, PrivilegeType.EVENT, PrivilegeType.SOURCEMAP],
resources: ['*'],
},
],
},
};

const users = {
Expand All @@ -93,16 +111,32 @@ const users = {
[ApmUser.apmAnnotationsWriteUser]: {
roles: ['editor', ApmUser.apmWriteUser, ApmUser.apmAnnotationsWriteUser],
},
[ApmUser.apmManageOwnAgentKeys]: {
roles: ['editor', ApmUser.apmManageOwnAgentKeys],
},
[ApmUser.apmManageOwnAndCreateAgentKeys]: {
roles: ['editor', ApmUser.apmManageOwnAgentKeys, ApmUser.apmManageOwnAndCreateAgentKeys],
},
};

export async function createApmUser(security: SecurityService, apmUser: ApmUser) {
export async function createApmUser(security: SecurityService, apmUser: ApmUser, es: Client) {
const role = roles[apmUser];
const user = users[apmUser];

if (!role || !user) {
throw new Error(`No configuration found for ${apmUser}`);
}

if ('applications' in role) {
// Add application privileges with es client as they are not supported by
// security.user.create. They are preserved when updating the role below
await es.security.putRole({
name: apmUser,
body: role,
});
delete (role as any).applications;
}

await security.role.create(apmUser, role);

await security.user.create(apmUser, {
Expand Down
34 changes: 26 additions & 8 deletions x-pack/test/apm_api_integration/common/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { FtrConfigProviderContext } from '@kbn/test';
import supertest from 'supertest';
import { format, UrlObject } from 'url';
import { SecurityServiceProvider } from 'test/common/services/security';
import { Client } from '@elastic/elasticsearch';
import { InheritedFtrProviderContext, InheritedServices } from './ftr_provider_context';
import { createApmUser, APM_TEST_PASSWORD, ApmUser } from './authentication';
import { APMFtrConfigName } from '../configs';
Expand All @@ -28,9 +29,10 @@ type SecurityService = Awaited<ReturnType<typeof SecurityServiceProvider>>;
function getLegacySupertestClient(kibanaServer: UrlObject, apmUser: ApmUser) {
return async (context: InheritedFtrProviderContext) => {
const security = context.getService('security');
const es = context.getService('es');
await security.init();

await createApmUser(security, apmUser);
await createApmUser(security, apmUser, es);

const url = format({
...kibanaServer,
Expand All @@ -44,9 +46,10 @@ function getLegacySupertestClient(kibanaServer: UrlObject, apmUser: ApmUser) {
async function getApmApiClient(
kibanaServer: UrlObject,
security: SecurityService,
apmUser: ApmUser
apmUser: ApmUser,
es: Client
) {
await createApmUser(security, apmUser);
await createApmUser(security, apmUser, es);

const url = format({
...kibanaServer,
Expand Down Expand Up @@ -81,21 +84,36 @@ export function createTestConfig(config: ApmFtrConfig) {
synthtraceEsClient: synthtraceEsClientService,
apmApiClient: async (context: InheritedFtrProviderContext) => {
const security = context.getService('security');
const es = context.getService('es');
await security.init();

return {
noAccessUser: await getApmApiClient(servers.kibana, security, ApmUser.noAccessUser),
readUser: await getApmApiClient(servers.kibana, security, ApmUser.apmReadUser),
writeUser: await getApmApiClient(servers.kibana, security, ApmUser.apmWriteUser),
noAccessUser: await getApmApiClient(servers.kibana, security, ApmUser.noAccessUser, es),
readUser: await getApmApiClient(servers.kibana, security, ApmUser.apmReadUser, es),
writeUser: await getApmApiClient(servers.kibana, security, ApmUser.apmWriteUser, es),
annotationWriterUser: await getApmApiClient(
servers.kibana,
security,
ApmUser.apmAnnotationsWriteUser
ApmUser.apmAnnotationsWriteUser,
es
),
noMlAccessUser: await getApmApiClient(
servers.kibana,
security,
ApmUser.apmReadUserWithoutMlAccess
ApmUser.apmReadUserWithoutMlAccess,
es
),
manageOwnAgentKeysUser: await getApmApiClient(
servers.kibana,
security,
ApmUser.apmManageOwnAgentKeys,
es
),
createAndAllAgentKeysUser: await getApmApiClient(
servers.kibana,
security,
ApmUser.apmManageOwnAndCreateAgentKeys,
es
),
};
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/*
* 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 expect from '@kbn/expect';
import { first } from 'lodash';
import { FtrProviderContext } from '../../../common/ftr_provider_context';
import { PrivilegeType } from '../../../../../plugins/apm/common/privilege_type';
import { ApmApiError, ApmApiSupertest } from '../../../common/apm_api_supertest';
import { ApmUser } from '../../../common/authentication';

export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
const esClient = getService('es');

const agentKeyName = 'test';
const allApplicationPrivileges = [
PrivilegeType.AGENT_CONFIG,
PrivilegeType.EVENT,
PrivilegeType.SOURCEMAP,
];

async function createAgentKey(apiClient: ApmApiSupertest, privileges = allApplicationPrivileges) {
return await apiClient({
endpoint: 'POST /api/apm/agent_keys',
params: {
body: {
name: agentKeyName,
privileges,
},
},
});
}

async function invalidateAgentKey(apiClient: ApmApiSupertest, id: string) {
return await apiClient({
endpoint: 'POST /internal/apm/api_key/invalidate',
params: {
body: { id },
},
});
}

async function getAgentKeys(apiClient: ApmApiSupertest) {
return await apiClient({ endpoint: 'GET /internal/apm/agent_keys' });
}

registry.when(
'When the user does not have the required privileges',
{ config: 'basic', archives: [] },
() => {
describe('When the user does not have the required cluster privileges', () => {
it('should return an error when creating an agent key', async () => {
const error = await expectToReject(() => createAgentKey(apmApiClient.writeUser));
expect(error.res.status).to.be(500);
expect(error.res.body.message).contain('is missing the following requested privilege');
});

it('should return an error when invalidating an agent key', async () => {
const error = await expectToReject(() =>
invalidateAgentKey(apmApiClient.writeUser, agentKeyName)
);
expect(error.res.status).to.be(500);
});

it('should return an error when getting a list of agent keys', async () => {
const error = await expectToReject(() => getAgentKeys(apmApiClient.writeUser));
expect(error.res.status).to.be(500);
});
});

describe('When the user does not have the required application privileges', () => {
allApplicationPrivileges.map((privilege) => {
it(`should return an error when creating an agent key with ${privilege} privilege`, async () => {
const error = await expectToReject(() =>
createAgentKey(apmApiClient.manageOwnAgentKeysUser, [privilege])
);
expect(error.res.status).to.be(500);
expect(error.res.body.message).contain('is missing the following requested privilege');
});
});
});
}
);

registry.when(
'When the user has the required privileges',
{ config: 'basic', archives: [] },
() => {
afterEach(async () => {
await esClient.security.invalidateApiKey({
username: ApmUser.apmManageOwnAndCreateAgentKeys,
});
});

it('should be able to create an agent key', async () => {
const { status, body } = await createAgentKey(apmApiClient.createAndAllAgentKeysUser);
expect(status).to.be(200);
expect(body).to.have.property('agentKey');
expect(body.agentKey).to.have.property('id');
expect(body.agentKey).to.have.property('api_key');
expect(body.agentKey).to.have.property('encoded');
expect(body.agentKey.name).to.be(agentKeyName);

const { api_keys: apiKeys } = await esClient.security.getApiKey({});
expect(
apiKeys.filter((key) => !key.invalidated && key.metadata?.application === 'apm')
).to.have.length(1);
});

it('should be able to invalidate an agent key', async () => {
// Create
const { body: createAgentKeyBody } = await createAgentKey(
apmApiClient.createAndAllAgentKeysUser
);
const {
agentKey: { id },
} = createAgentKeyBody;

// Invalidate
const { status, body } = await invalidateAgentKey(
apmApiClient.createAndAllAgentKeysUser,
id
);
expect(status).to.be(200);
expect(body).to.have.property('invalidatedAgentKeys');
expect(body.invalidatedAgentKeys).to.eql([id]);

// Get
const { api_keys: apiKeys } = await esClient.security.getApiKey({});
expect(
apiKeys.filter((key) => !key.invalidated && key.metadata?.application === 'apm')
).to.be.empty();
});

it('should be able to get a list of agent keys', async () => {
// Create
const { body: createAgentKeyBody } = await createAgentKey(
apmApiClient.createAndAllAgentKeysUser
);
const {
agentKey: { id },
} = createAgentKeyBody;

// Get
const {
status,
body: { agentKeys },
} = await getAgentKeys(apmApiClient.createAndAllAgentKeysUser);

expect(status).to.be(200);
const agentKey = first(agentKeys);
expect(agentKey?.id).to.be(id);
expect(agentKey?.name).to.be(agentKeyName);
expect(agentKey).to.have.property('creation');
expect(agentKey?.invalidated).to.be(false);
expect(agentKey).to.have.property('username');
expect(agentKey).to.have.property('realm');
expect(agentKey?.metadata.application).to.be('apm');
});
}
);

async function expectToReject(fn: () => Promise<any>): Promise<ApmApiError> {
try {
await fn();
} catch (e) {
return e;
}
throw new Error(`Expected fn to throw`);
}
}