Skip to content
Closed
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
220 changes: 118 additions & 102 deletions x-pack/test/api_integration/apis/apm/feature_controls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,142 +19,201 @@ export default function featureControlsTests({ getService }: FtrProviderContext)

const expect404 = (result: any) => {
expect(result.error).to.be(undefined);
expect(result.response).not.to.be(undefined);
expect(result.response).to.have.property('statusCode', 404);
expect(result.response.statusCode).to.be(404);
Copy link
Member Author

Choose a reason for hiding this comment

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

This makes the output much cleaner. Instead of outputting Expected {...hugeObject} to have property statusCode 404 it will now output Expected 500 to be 404

};

const expect200 = (result: any) => {
expect(result.error).to.be(undefined);
expect(result.response).not.to.be(undefined);
expect(result.response).to.have.property('statusCode', 200);
expect(result.response.statusCode).to.be(200);
};

const endpoints = [
interface Endpoint {
req: {
url: string;
method?: 'get' | 'post';
body?: any;
};
expectForbidden: (result: any) => void;
expectResponse: (result: any) => void;
}
const endpoints: Endpoint[] = [
{
req: { url: `/api/apm/services/foo/errors?start=${start}&end=${end}&uiFilters=%7B%7D` },
expectForbidden: expect404,
expectResponse: expect200,
},
{
url: `/api/apm/services/foo/errors?start=${start}&end=${end}&uiFilters=%7B%7D&_debug=true`,
req: { url: `/api/apm/services/foo/errors/bar?start=${start}&end=${end}&uiFilters=%7B%7D` },
expectForbidden: expect404,
expectResponse: expect200,
},
{
url: `/api/apm/services/foo/errors/bar?start=${start}&end=${end}&uiFilters=%7B%7D`,
req: {
url: `/api/apm/services/foo/errors/distribution?start=${start}&end=${end}&groupId=bar&uiFilters=%7B%7D`,
},
expectForbidden: expect404,
expectResponse: expect200,
},
{
url: `/api/apm/services/foo/errors/distribution?start=${start}&end=${end}&groupId=bar&uiFilters=%7B%7D`,
req: {
url: `/api/apm/services/foo/errors/distribution?start=${start}&end=${end}&uiFilters=%7B%7D`,
},
expectForbidden: expect404,
expectResponse: expect200,
},
{
url: `/api/apm/services/foo/errors/distribution?start=${start}&end=${end}&uiFilters=%7B%7D`,
req: {
url: `/api/apm/services/foo/metrics/charts?start=${start}&end=${end}&agentName=cool-agent&uiFilters=%7B%7D`,
},
expectForbidden: expect404,
expectResponse: expect200,
},
{
url: `/api/apm/services/foo/metrics/charts?start=${start}&end=${end}&agentName=cool-agent&uiFilters=%7B%7D`,
req: { url: `/api/apm/services?start=${start}&end=${end}&uiFilters=%7B%7D` },
expectForbidden: expect404,
expectResponse: expect200,
},
{
url: `/api/apm/services?start=${start}&end=${end}&uiFilters=%7B%7D`,
req: { url: `/api/apm/services/foo/agent_name?start=${start}&end=${end}` },
expectForbidden: expect404,
expectResponse: expect200,
},
{
url: `/api/apm/services/foo/agent_name?start=${start}&end=${end}`,
req: { url: `/api/apm/services/foo/transaction_types?start=${start}&end=${end}` },
expectForbidden: expect404,
expectResponse: expect200,
},
{
url: `/api/apm/services/foo/transaction_types?start=${start}&end=${end}`,
req: { url: `/api/apm/traces?start=${start}&end=${end}&uiFilters=%7B%7D` },
expectForbidden: expect404,
expectResponse: expect200,
},
{
url: `/api/apm/traces?start=${start}&end=${end}&uiFilters=%7B%7D`,
req: { url: `/api/apm/traces/foo?start=${start}&end=${end}` },
expectForbidden: expect404,
expectResponse: expect200,
},
{
url: `/api/apm/traces/foo?start=${start}&end=${end}`,
req: {
url: `/api/apm/services/foo/transaction_groups?start=${start}&end=${end}&transactionType=bar&uiFilters=%7B%7D`,
},
expectForbidden: expect404,
expectResponse: expect200,
},
{
url: `/api/apm/services/foo/transaction_groups?start=${start}&end=${end}&transactionType=bar&uiFilters=%7B%7D`,
req: {
url: `/api/apm/services/foo/transaction_groups/charts?start=${start}&end=${end}&transactionType=bar&uiFilters=%7B%7D`,
},
expectForbidden: expect404,
expectResponse: expect200,
},
{
url: `/api/apm/services/foo/transaction_groups/charts?start=${start}&end=${end}&transactionType=bar&uiFilters=%7B%7D`,
req: {
url: `/api/apm/services/foo/transaction_groups/charts?start=${start}&end=${end}&uiFilters=%7B%7D`,
},
expectForbidden: expect404,
expectResponse: expect200,
},
{
url: `/api/apm/services/foo/transaction_groups/charts?start=${start}&end=${end}&uiFilters=%7B%7D`,
req: {
url: `/api/apm/services/foo/transaction_groups/charts?start=${start}&end=${end}&transactionType=bar&transactionName=baz&uiFilters=%7B%7D`,
},
expectForbidden: expect404,
expectResponse: expect200,
},
{
url: `/api/apm/services/foo/transaction_groups/charts?start=${start}&end=${end}&transactionType=bar&transactionName=baz&uiFilters=%7B%7D`,
req: {
url: `/api/apm/services/foo/transaction_groups/distribution?start=${start}&end=${end}&transactionType=bar&transactionName=baz&uiFilters=%7B%7D`,
},
expectForbidden: expect404,
expectResponse: expect200,
},
{
url: `/api/apm/services/foo/transaction_groups/distribution?start=${start}&end=${end}&transactionType=bar&transactionName=baz&uiFilters=%7B%7D`,
req: {
method: 'post',
url: `/api/apm/settings/agent-configuration/search`,
body: { service: { name: 'test-service' } },
Copy link
Member Author

Choose a reason for hiding this comment

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

Added support for specifying method and body

},
expectForbidden: expect404,
expectResponse: expect200,
},
];

const elasticsearchRole = {
indices: [
{ names: ['apm-*', '.apm-agent-configuration'], privileges: ['read', 'view_index_metadata'] },
],
};

async function executeRequest(
endpoint: string,
{ method = 'get', url, body }: Endpoint['req'],
username: string,
password: string,
spaceId?: string
) {
const basePath = spaceId ? `/s/${spaceId}` : '';

return await supertest
.get(`${basePath}${endpoint}`)
let request = supertest[method](`${basePath}${url}`);

// send body as json
if (body) {
request = request
.send({
service: {
name: 'test-service',
},
})
.set('Content-Type', 'application/json');
}

return await request
.auth(username, password)
.set('kbn-xsrf', 'foo')
.then((response: any) => ({ error: undefined, response }))
.catch((error: any) => ({ error, response: undefined }));
}

async function executeRequests(
username: string,
password: string,
spaceId: string,
expectation: 'forbidden' | 'response'
) {
async function executeRequests({
username,
password,
expectation,
spaceId,
}: {
username: string;
password: string;
expectation: 'forbidden' | 'response';
spaceId?: string;
}) {
for (const endpoint of endpoints) {
log.debug(`hitting ${endpoint}`);
const result = await executeRequest(endpoint.url, username, password, spaceId);
if (expectation === 'forbidden') {
endpoint.expectForbidden(result);
} else {
endpoint.expectResponse(result);
log.debug(`hitting ${endpoint.req.url}`);
const result = await executeRequest(endpoint.req, username, password, spaceId);
try {
if (expectation === 'forbidden') {
endpoint.expectForbidden(result);
} else {
endpoint.expectResponse(result);
}
} catch (e) {
const { body, req } = result.response;
throw new Error(
`Endpoint: ${req.method} ${req.path}
Status code: ${body.statusCode}
Response: ${body.message}

${e.message}`
);
}
}
}

describe('feature controls', () => {
it(`APIs can't be accessed by apm-* read privileges role`, async () => {
describe('apm feature controls', () => {
it(`APIs can't be accessed by logstash_read user`, async () => {
const username = 'logstash_read';
const roleName = 'logstash_read';
const password = `${username}-password`;
try {
await security.role.create(roleName, {
elasticsearch: {
indices: [
{
names: ['apm-*'],
privileges: ['read', 'view_index_metadata'],
},
],
},
elasticsearch: elasticsearchRole,
});

await security.user.create(username, {
Expand All @@ -163,33 +222,21 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
full_name: 'a kibana user',
});

await executeRequests(username, password, '', 'forbidden');
await executeRequests({ username, password, expectation: 'forbidden' });
} finally {
await security.role.delete(roleName);
await security.user.delete(username);
}
});

it('APIs can be accessed global all with apm-* read privileges role', async () => {
it('APIs can be accessed by global_all user', async () => {
const username = 'global_all';
const roleName = 'global_all';
const password = `${username}-password`;
try {
await security.role.create(roleName, {
elasticsearch: {
indices: [
{
names: ['apm-*'],
privileges: ['read', 'view_index_metadata'],
},
],
},
kibana: [
{
base: ['all'],
spaces: ['*'],
},
],
elasticsearch: elasticsearchRole,
kibana: [{ base: ['all'], spaces: ['*'] }],
});

await security.user.create(username, {
Expand All @@ -198,36 +245,22 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
full_name: 'a kibana user',
});

await executeRequests(username, password, '', 'response');
await executeRequests({ username, password, expectation: 'response' });
Copy link
Member Author

Choose a reason for hiding this comment

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

Destructuring instead of positional arguments (the third argument is optional)

} finally {
await security.role.delete(roleName);
await security.user.delete(username);
}
});

// this could be any role which doesn't have access to the APM feature
it(`APIs can't be accessed by dashboard all with apm-* read privileges role`, async () => {
it(`APIs can't be accessed by dashboard_all user`, async () => {
const username = 'dashboard_all';
const roleName = 'dashboard_all';
const password = `${username}-password`;
try {
await security.role.create(roleName, {
elasticsearch: {
indices: [
{
names: ['apm-*'],
privileges: ['read', 'view_index_metadata'],
},
],
},
kibana: [
{
feature: {
dashboard: ['all'],
},
spaces: ['*'],
},
],
elasticsearch: elasticsearchRole,
kibana: [{ feature: { dashboard: ['all'] }, spaces: ['*'] }],
Copy link
Member Author

Choose a reason for hiding this comment

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

Removed some excessive line spacing

});

await security.user.create(username, {
Expand All @@ -236,7 +269,7 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
full_name: 'a kibana user',
});

await executeRequests(username, password, '', 'forbidden');
await executeRequests({ username, password, expectation: 'forbidden' });
} finally {
await security.role.delete(roleName);
await security.user.delete(username);
Expand Down Expand Up @@ -264,27 +297,10 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
disabledFeatures: [],
});
await security.role.create(roleName, {
elasticsearch: {
indices: [
{
names: ['apm-*'],
privileges: ['read', 'view_index_metadata'],
},
],
},
elasticsearch: elasticsearchRole,
kibana: [
{
feature: {
apm: ['read'],
},
spaces: [space1Id],
},
{
feature: {
dashboard: ['all'],
},
spaces: [space2Id],
},
{ feature: { apm: ['read'] }, spaces: [space1Id] },
{ feature: { dashboard: ['all'] }, spaces: [space2Id] },
],
});
await security.user.create(username, {
Expand All @@ -301,11 +317,11 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
});

it('user_1 can access APIs in space_1', async () => {
await executeRequests(username, password, space1Id, 'response');
await executeRequests({ username, password, expectation: 'response', spaceId: space1Id });
});

it(`user_1 can't access APIs in space_2`, async () => {
await executeRequests(username, password, space2Id, 'forbidden');
await executeRequests({ username, password, expectation: 'forbidden', spaceId: space2Id });
});
});
});
Expand Down