[IngestManager] Expose agent authentication using access key (#69650)

* [IngestManager] Expose agent authentication using access key

* Add unit tests to authenticateAgentWithAccessToken service
This commit is contained in:
Nicolas Chaulet 2020-06-24 15:53:38 -04:00 committed by GitHub
parent a104e5ab0e
commit 14f975c899
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 193 additions and 10 deletions

View file

@ -54,7 +54,7 @@ import {
AgentService,
datasourceService,
} from './services';
import { getAgentStatusById } from './services/agents';
import { getAgentStatusById, authenticateAgentWithAccessToken } from './services/agents';
import { CloudSetup } from '../../cloud/server';
import { agentCheckinState } from './services/agents/checkin/state';
@ -256,6 +256,7 @@ export class IngestManagerPlugin
esIndexPatternService: new ESIndexPatternSavedObjectService(),
agentService: {
getAgentStatusById,
authenticateAgentWithAccessToken,
},
datasourceService,
registerExternalCallback: (...args: ExternalCallback) => {

View file

@ -77,7 +77,7 @@ describe('test acks handlers', () => {
id: 'action1',
},
]),
getAgentByAccessAPIKeyId: jest.fn().mockReturnValueOnce({
authenticateAgentWithAccessToken: jest.fn().mockReturnValueOnce({
id: 'agent',
}),
getSavedObjectsClientContract: jest.fn().mockReturnValueOnce(mockSavedObjectsClient),

View file

@ -9,7 +9,6 @@
import { RequestHandler } from 'kibana/server';
import { TypeOf } from '@kbn/config-schema';
import { PostAgentAcksRequestSchema } from '../../types/rest_spec';
import * as APIKeyService from '../../services/api_keys';
import { AcksService } from '../../services/agents';
import { AgentEvent } from '../../../common/types/models';
import { PostAgentAcksResponse } from '../../../common/types/rest_spec';
@ -24,8 +23,7 @@ export const postAgentAcksHandlerBuilder = function (
return async (context, request, response) => {
try {
const soClient = ackService.getSavedObjectsClientContract(request);
const res = APIKeyService.parseApiKeyFromHeaders(request.headers);
const agent = await ackService.getAgentByAccessAPIKeyId(soClient, res.apiKeyId as string);
const agent = await ackService.authenticateAgentWithAccessToken(soClient, request);
const agentEvents = request.body.events as AgentEvent[];
// validate that all events are for the authorized agent obtained from the api key

View file

@ -171,8 +171,7 @@ export const postAgentCheckinHandler: RequestHandler<
> = async (context, request, response) => {
try {
const soClient = appContextService.getInternalUserSOClient(request);
const res = APIKeyService.parseApiKeyFromHeaders(request.headers);
const agent = await AgentService.getAgentByAccessAPIKeyId(soClient, res.apiKeyId);
const agent = await AgentService.authenticateAgentWithAccessToken(soClient, request);
const abortController = new AbortController();
request.events.aborted$.subscribe(() => {
abortController.abort();

View file

@ -109,7 +109,7 @@ export const registerRoutes = (router: IRouter) => {
},
postAgentAcksHandlerBuilder({
acknowledgeAgentActions: AgentService.acknowledgeAgentActions,
getAgentByAccessAPIKeyId: AgentService.getAgentByAccessAPIKeyId,
authenticateAgentWithAccessToken: AgentService.authenticateAgentWithAccessToken,
getSavedObjectsClientContract: appContextService.getInternalUserSOClient.bind(
appContextService
),

View file

@ -140,9 +140,9 @@ export interface AcksService {
actionIds: AgentEvent[]
) => Promise<AgentAction[]>;
getAgentByAccessAPIKeyId: (
authenticateAgentWithAccessToken: (
soClient: SavedObjectsClientContract,
accessAPIKeyId: string
request: KibanaRequest
) => Promise<Agent>;
getSavedObjectsClientContract: (kibanaRequest: KibanaRequest) => SavedObjectsClientContract;

View file

@ -0,0 +1,154 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { KibanaRequest } from 'kibana/server';
import { savedObjectsClientMock } from 'src/core/server/mocks';
import { authenticateAgentWithAccessToken } from './authenticate';
describe('test agent autenticate services', () => {
it('should succeed with a valid API key and an active agent', async () => {
const mockSavedObjectsClient = savedObjectsClientMock.create();
mockSavedObjectsClient.find.mockReturnValue(
Promise.resolve({
page: 1,
per_page: 100,
total: 1,
saved_objects: [
{
id: 'agent1',
type: 'agent',
references: [],
score: 0,
attributes: {
active: true,
access_api_key_id: 'pedTuHIBTEDt93wW0Fhr',
},
},
],
})
);
await authenticateAgentWithAccessToken(mockSavedObjectsClient, {
auth: { isAuthenticated: true },
headers: {
authorization: 'ApiKey cGVkVHVISUJURUR0OTN3VzBGaHI6TnU1U0JtbHJSeC12Rm9qQWpoSHlUZw==',
},
} as KibanaRequest);
});
it('should throw if the request is not authenticated', async () => {
const mockSavedObjectsClient = savedObjectsClientMock.create();
mockSavedObjectsClient.find.mockReturnValue(
Promise.resolve({
page: 1,
per_page: 100,
total: 1,
saved_objects: [
{
id: 'agent1',
type: 'agent',
references: [],
score: 0,
attributes: {
active: true,
access_api_key_id: 'pedTuHIBTEDt93wW0Fhr',
},
},
],
})
);
expect(
authenticateAgentWithAccessToken(mockSavedObjectsClient, {
auth: { isAuthenticated: false },
headers: {
authorization: 'ApiKey cGVkVHVISUJURUR0OTN3VzBGaHI6TnU1U0JtbHJSeC12Rm9qQWpoSHlUZw==',
},
} as KibanaRequest)
).rejects.toThrow(/Request not authenticated/);
});
it('should throw if the ApiKey headers is malformed', async () => {
const mockSavedObjectsClient = savedObjectsClientMock.create();
mockSavedObjectsClient.find.mockReturnValue(
Promise.resolve({
page: 1,
per_page: 100,
total: 1,
saved_objects: [
{
id: 'agent1',
type: 'agent',
references: [],
score: 0,
attributes: {
active: false,
access_api_key_id: 'pedTuHIBTEDt93wW0Fhr',
},
},
],
})
);
expect(
authenticateAgentWithAccessToken(mockSavedObjectsClient, {
auth: { isAuthenticated: true },
headers: {
authorization: 'aaaa',
},
} as KibanaRequest)
).rejects.toThrow(/Authorization header is malformed/);
});
it('should throw if the agent is not active', async () => {
const mockSavedObjectsClient = savedObjectsClientMock.create();
mockSavedObjectsClient.find.mockReturnValue(
Promise.resolve({
page: 1,
per_page: 100,
total: 1,
saved_objects: [
{
id: 'agent1',
type: 'agent',
references: [],
score: 0,
attributes: {
active: false,
access_api_key_id: 'pedTuHIBTEDt93wW0Fhr',
},
},
],
})
);
expect(
authenticateAgentWithAccessToken(mockSavedObjectsClient, {
auth: { isAuthenticated: true },
headers: {
authorization: 'ApiKey cGVkVHVISUJURUR0OTN3VzBGaHI6TnU1U0JtbHJSeC12Rm9qQWpoSHlUZw==',
},
} as KibanaRequest)
).rejects.toThrow(/Agent inactive/);
});
it('should throw if there is no agent matching the API key', async () => {
const mockSavedObjectsClient = savedObjectsClientMock.create();
mockSavedObjectsClient.find.mockReturnValue(
Promise.resolve({
page: 1,
per_page: 100,
total: 1,
saved_objects: [],
})
);
expect(
authenticateAgentWithAccessToken(mockSavedObjectsClient, {
auth: { isAuthenticated: true },
headers: {
authorization: 'ApiKey cGVkVHVISUJURUR0OTN3VzBGaHI6TnU1U0JtbHJSeC12Rm9qQWpoSHlUZw==',
},
} as KibanaRequest)
).rejects.toThrow(/Agent not found/);
});
});

View file

@ -0,0 +1,30 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import Boom from 'boom';
import { KibanaRequest, SavedObjectsClientContract } from 'src/core/server';
import { Agent } from '../../types';
import * as APIKeyService from '../api_keys';
import { getAgentByAccessAPIKeyId } from './crud';
export async function authenticateAgentWithAccessToken(
soClient: SavedObjectsClientContract,
request: KibanaRequest
): Promise<Agent> {
if (!request.auth.isAuthenticated) {
throw Boom.unauthorized('Request not authenticated');
}
let res: { apiKey: string; apiKeyId: string };
try {
res = APIKeyService.parseApiKeyFromHeaders(request.headers);
} catch (err) {
throw Boom.unauthorized(err.message);
}
const agent = await getAgentByAccessAPIKeyId(soClient, res.apiKeyId);
return agent;
}

View file

@ -14,3 +14,4 @@ export * from './crud';
export * from './update';
export * from './actions';
export * from './reassign';
export * from './authenticate';