Skip to content

Commit

Permalink
Adds API tests for APM agent keys (elastic#125302)
Browse files Browse the repository at this point in the history
* Add API tests for apm agent keys

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
gbamparop and kibanamachine authored Feb 11, 2022
1 parent 0f57c23 commit 4027f91
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 9 deletions.
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`);
}
}

0 comments on commit 4027f91

Please sign in to comment.