Skip to content

Commit

Permalink
Implements API key authentication for serverless API integration tests (
Browse files Browse the repository at this point in the history
#178558)

Closes #177022

## Summary

This PR introduces two new functions to the user manager serverless test
service to facilitate authentication via API keys for API integration
tests:
- createApiKeyForRole: gets a session cookie for a specific role (by
name) and uses it to generate an API key for that role.
- invalidateApiKeyForRole: invalidates the API key generated in
createApiKeyForRole, given a valid cookie and API key.

As an example/reference, I have updated the common management spaces API
integration tests to remove basic authentication, and use API key
authentication instead. See:
`x-pack/test_serverless/api_integration/test_suites/common/management/spaces.ts`

cc: @pheyos & @dmlemeshko
  • Loading branch information
jeramysoucy authored Mar 19, 2024
1 parent 8364ce8 commit 7975fab
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,45 @@

import expect from 'expect';
import { FtrProviderContext } from '../../../ftr_provider_context';
import { RoleCredentials } from '../../../../shared/services';

export default function ({ getService }: FtrProviderContext) {
const svlCommonApi = getService('svlCommonApi');
const supertest = getService('supertest');
const svlUserManager = getService('svlUserManager');
const supertestWithoutAuth = getService('supertestWithoutAuth');
let commonRequestHeader: Record<string, string>;
let internalRequestHeader: Record<string, string>;
let roleAuthc: RoleCredentials;

describe('spaces', function () {
before(async () => {
// admin is the only predefined role that will work for all 3 solutions
roleAuthc = await svlUserManager.createApiKeyForRole('admin');
commonRequestHeader = svlCommonApi.getCommonRequestHeader();
internalRequestHeader = svlCommonApi.getInternalRequestHeader();
});

after(async () => {
await svlUserManager.invalidateApiKeyForRole(roleAuthc);
});

describe('route access', () => {
describe('disabled', () => {
it('#delete', async () => {
const { body, status } = await supertest
const { body, status } = await supertestWithoutAuth
.delete('/api/spaces/space/default')
.set(svlCommonApi.getCommonRequestHeader());
.set(commonRequestHeader)
.set(roleAuthc.apiKeyHeader);

// normally we'd get a 400 bad request if we tried to delete the default space
svlCommonApi.assertApiNotFound(body, status);
});

it('#create', async () => {
const { body, status } = await supertest
const { body, status } = await supertestWithoutAuth
.post('/api/spaces/space')
.set(svlCommonApi.getCommonRequestHeader())
.set(commonRequestHeader)
.set(roleAuthc.apiKeyHeader)
.send({
id: 'custom',
name: 'Custom',
Expand All @@ -38,9 +56,10 @@ export default function ({ getService }: FtrProviderContext) {
});

it('#update requires internal header', async () => {
const { body, status } = await supertest
const { body, status } = await supertestWithoutAuth
.put('/api/spaces/space/default')
.set(svlCommonApi.getCommonRequestHeader())
.set(commonRequestHeader)
.set(roleAuthc.apiKeyHeader)
.send({
id: 'default',
name: 'UPDATED!',
Expand All @@ -51,45 +70,50 @@ export default function ({ getService }: FtrProviderContext) {
});

it('#copyToSpace', async () => {
const { body, status } = await supertest
const { body, status } = await supertestWithoutAuth
.post('/api/spaces/_copy_saved_objects')
.set(svlCommonApi.getCommonRequestHeader());
.set(commonRequestHeader)
.set(roleAuthc.apiKeyHeader);

// without a request body we would normally a 400 bad request if the endpoint was registered
svlCommonApi.assertApiNotFound(body, status);
});

it('#resolveCopyToSpaceErrors', async () => {
const { body, status } = await supertest
const { body, status } = await supertestWithoutAuth
.post('/api/spaces/_resolve_copy_saved_objects_errors')
.set(svlCommonApi.getCommonRequestHeader());
.set(commonRequestHeader)
.set(roleAuthc.apiKeyHeader);

// without a request body we would normally a 400 bad request if the endpoint was registered
svlCommonApi.assertApiNotFound(body, status);
});

it('#updateObjectsSpaces', async () => {
const { body, status } = await supertest
const { body, status } = await supertestWithoutAuth
.post('/api/spaces/_update_objects_spaces')
.set(svlCommonApi.getCommonRequestHeader());
.set(commonRequestHeader)
.set(roleAuthc.apiKeyHeader);

// without a request body we would normally a 400 bad request if the endpoint was registered
svlCommonApi.assertApiNotFound(body, status);
});

it('#getShareableReferences', async () => {
const { body, status } = await supertest
const { body, status } = await supertestWithoutAuth
.post('/api/spaces/_get_shareable_references')
.set(svlCommonApi.getCommonRequestHeader());
.set(commonRequestHeader)
.set(roleAuthc.apiKeyHeader);

// without a request body we would normally a 400 bad request if the endpoint was registered
svlCommonApi.assertApiNotFound(body, status);
});

it('#disableLegacyUrlAliases', async () => {
const { body, status } = await supertest
const { body, status } = await supertestWithoutAuth
.post('/api/spaces/_disable_legacy_url_aliases')
.set(svlCommonApi.getCommonRequestHeader());
.set(commonRequestHeader)
.set(roleAuthc.apiKeyHeader);

// without a request body we would normally a 400 bad request if the endpoint was registered
svlCommonApi.assertApiNotFound(body, status);
Expand All @@ -101,9 +125,10 @@ export default function ({ getService }: FtrProviderContext) {
let body: any;
let status: number;

({ body, status } = await supertest
({ body, status } = await supertestWithoutAuth
.get('/api/spaces/space/default')
.set(svlCommonApi.getCommonRequestHeader()));
.set(commonRequestHeader)
.set(roleAuthc.apiKeyHeader));
// expect a rejection because we're not using the internal header
expect(body).toEqual({
statusCode: 400,
Expand All @@ -114,9 +139,10 @@ export default function ({ getService }: FtrProviderContext) {
});
expect(status).toBe(400);

({ body, status } = await supertest
({ body, status } = await supertestWithoutAuth
.get('/api/spaces/space/default')
.set(svlCommonApi.getInternalRequestHeader()));
.set(internalRequestHeader)
.set(roleAuthc.apiKeyHeader));
// expect success because we're using the internal header
expect(body).toEqual(
expect.objectContaining({
Expand All @@ -130,9 +156,10 @@ export default function ({ getService }: FtrProviderContext) {
let body: any;
let status: number;

({ body, status } = await supertest
({ body, status } = await supertestWithoutAuth
.get('/api/spaces/space')
.set(svlCommonApi.getCommonRequestHeader()));
.set(commonRequestHeader)
.set(roleAuthc.apiKeyHeader));
// expect a rejection because we're not using the internal header
expect(body).toEqual({
statusCode: 400,
Expand All @@ -143,9 +170,10 @@ export default function ({ getService }: FtrProviderContext) {
});
expect(status).toBe(400);

({ body, status } = await supertest
({ body, status } = await supertestWithoutAuth
.get('/api/spaces/space')
.set(svlCommonApi.getInternalRequestHeader()));
.set(internalRequestHeader)
.set(roleAuthc.apiKeyHeader));
// expect success because we're using the internal header
expect(body).toEqual(
expect.arrayContaining([
Expand All @@ -161,9 +189,10 @@ export default function ({ getService }: FtrProviderContext) {
let body: any;
let status: number;

({ body, status } = await supertest
({ body, status } = await supertestWithoutAuth
.get('/internal/spaces/_active_space')
.set(svlCommonApi.getCommonRequestHeader()));
.set(commonRequestHeader)
.set(roleAuthc.apiKeyHeader));
// expect a rejection because we're not using the internal header
expect(body).toEqual({
statusCode: 400,
Expand All @@ -174,9 +203,10 @@ export default function ({ getService }: FtrProviderContext) {
});
expect(status).toBe(400);

({ body, status } = await supertest
({ body, status } = await supertestWithoutAuth
.get('/internal/spaces/_active_space')
.set(svlCommonApi.getInternalRequestHeader()));
.set(internalRequestHeader)
.set(roleAuthc.apiKeyHeader));
// expect success because we're using the internal header
expect(body).toEqual(
expect.objectContaining({
Expand Down
2 changes: 2 additions & 0 deletions x-pack/test_serverless/shared/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { SvlCommonApiServiceProvider } from './svl_common_api';
import { SvlReportingServiceProvider } from './svl_reporting';
import { SvlUserManagerProvider } from './svl_user_manager';

export type { RoleCredentials } from './svl_user_manager';

export const services = {
supertest: SupertestProvider,
supertestWithoutAuth: SupertestWithoutAuthProvider,
Expand Down
47 changes: 47 additions & 0 deletions x-pack/test_serverless/shared/services/svl_user_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,20 @@ import { readRolesFromResource } from '@kbn/es';
import { resolve } from 'path';
import { Role } from '@kbn/test/src/auth/types';
import { isServerlessProjectType } from '@kbn/es/src/utils';
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../functional/ftr_provider_context';

export interface RoleCredentials {
apiKey: { id: string; name: string };
apiKeyHeader: { Authorization: string };
cookieHeader: { Cookie: string };
}

export function SvlUserManagerProvider({ getService }: FtrProviderContext) {
const config = getService('config');
const log = getService('log');
const svlCommonApi = getService('svlCommonApi');
const supertestWithoutAuth = getService('supertestWithoutAuth');
const isCloud = !!process.env.TEST_CLOUD;
const kbnServerArgs = config.get('kbnTestServer.serverArgs') as string[];
const projectType = kbnServerArgs
Expand Down Expand Up @@ -72,6 +81,44 @@ export function SvlUserManagerProvider({ getService }: FtrProviderContext) {
async getUserData(role: string) {
return sessionManager.getUserData(role);
},
async createApiKeyForRole(role: string): Promise<RoleCredentials> {
const cookieHeader = await this.getApiCredentialsForRole(role);

const { body, status } = await supertestWithoutAuth
.post('/internal/security/api_key')
.set(svlCommonApi.getInternalRequestHeader())
.set(cookieHeader)
.send({
name: 'myTestApiKey',
metadata: {},
role_descriptors: {},
});
expect(status).to.be(200);

const apiKey = body;
const apiKeyHeader = { Authorization: 'ApiKey ' + apiKey.encoded };

return { apiKey, apiKeyHeader, cookieHeader };
},
async invalidateApiKeyForRole(roleCredentials: RoleCredentials) {
const requestBody = {
apiKeys: [
{
id: roleCredentials.apiKey.id,
name: roleCredentials.apiKey.name,
},
],
isAdmin: true,
};

const { status } = await supertestWithoutAuth
.post('/internal/security/api_key/invalidate')
.set(svlCommonApi.getInternalRequestHeader())
.set(roleCredentials.cookieHeader)
.send(requestBody);

expect(status).to.be(200);
},
DEFAULT_ROLE,
};
}

0 comments on commit 7975fab

Please sign in to comment.