Skip to content

Commit

Permalink
[Fleet] Unenrolling agent invalidate related ES API keys (#61630) (#6…
Browse files Browse the repository at this point in the history
  • Loading branch information
nchaulet committed Apr 3, 2020
1 parent 6c31c33 commit 43caa4a
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const postAgentAcksHandlerBuilder = function(
return async (context, request, response) => {
try {
const soClient = ackService.getSavedObjectsClientContract(request);
const res = APIKeyService.parseApiKey(request.headers);
const res = APIKeyService.parseApiKeyFromHeaders(request.headers);
const agent = await ackService.getAgentByAccessAPIKeyId(soClient, res.apiKeyId as string);
const agentEvents = request.body.events as AgentEvent[];

Expand Down
4 changes: 2 additions & 2 deletions x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ export const postAgentCheckinHandler: RequestHandler<
> = async (context, request, response) => {
try {
const soClient = getInternalUserSOClient(request);
const res = APIKeyService.parseApiKey(request.headers);
const res = APIKeyService.parseApiKeyFromHeaders(request.headers);
const agent = await AgentService.getAgentByAccessAPIKeyId(soClient, res.apiKeyId);
const { actions } = await AgentService.agentCheckin(
soClient,
Expand Down Expand Up @@ -216,7 +216,7 @@ export const postAgentEnrollHandler: RequestHandler<
> = async (context, request, response) => {
try {
const soClient = getInternalUserSOClient(request);
const { apiKeyId } = APIKeyService.parseApiKey(request.headers);
const { apiKeyId } = APIKeyService.parseApiKeyFromHeaders(request.headers);
const enrollmentAPIKey = await APIKeyService.getEnrollmentAPIKeyById(soClient, apiKeyId);

if (!enrollmentAPIKey || !enrollmentAPIKey.active) {
Expand Down
25 changes: 22 additions & 3 deletions x-pack/plugins/ingest_manager/server/services/agents/unenroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import { SavedObjectsClientContract } from 'src/core/server';
import { AgentSOAttributes } from '../../types';
import { AGENT_SAVED_OBJECT_TYPE } from '../../constants';
import { getAgent } from './crud';
import * as APIKeyService from '../api_keys';

export async function unenrollAgents(
soClient: SavedObjectsClientContract,
Expand All @@ -15,9 +17,7 @@ export async function unenrollAgents(
const response = [];
for (const id of toUnenrollIds) {
try {
await soClient.update<AgentSOAttributes>(AGENT_SAVED_OBJECT_TYPE, id, {
active: false,
});
await unenrollAgent(soClient, id);
response.push({
id,
success: true,
Expand All @@ -33,3 +33,22 @@ export async function unenrollAgents(

return response;
}

async function unenrollAgent(soClient: SavedObjectsClientContract, agentId: string) {
const agent = await getAgent(soClient, agentId);

await Promise.all([
agent.access_api_key_id
? APIKeyService.invalidateAPIKey(soClient, agent.access_api_key_id)
: undefined,
agent.default_api_key
? APIKeyService.invalidateAPIKey(
soClient,
APIKeyService.parseApiKey(agent.default_api_key).apiKeyId
)
: undefined,
]);
await soClient.update<AgentSOAttributes>(AGENT_SAVED_OBJECT_TYPE, agentId, {
active: false,
});
}
11 changes: 7 additions & 4 deletions x-pack/plugins/ingest_manager/server/services/api_keys/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE } from '../../constants';
import { EnrollmentAPIKeySOAttributes, EnrollmentAPIKey } from '../../types';
import { createAPIKey } from './security';

export { invalidateAPIKey } from './security';
export * from './enrollment_api_key';

export async function generateOutputApiKey(
Expand Down Expand Up @@ -77,7 +78,7 @@ export async function getEnrollmentAPIKeyById(
return enrollmentAPIKey;
}

export function parseApiKey(headers: KibanaRequest['headers']) {
export function parseApiKeyFromHeaders(headers: KibanaRequest['headers']) {
const authorizationHeader = headers.authorization;

if (!authorizationHeader) {
Expand All @@ -93,9 +94,11 @@ export function parseApiKey(headers: KibanaRequest['headers']) {
}

const apiKey = authorizationHeader.split(' ')[1];
if (!apiKey) {
throw new Error('Authorization header is malformed');
}

return parseApiKey(apiKey);
}

export function parseApiKey(apiKey: string) {
const apiKeyId = Buffer.from(apiKey, 'base64')
.toString('utf8')
.split(':')[0];
Expand Down
68 changes: 67 additions & 1 deletion x-pack/test/api_integration/apis/fleet/unenroll_agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,58 @@
*/

import expect from '@kbn/expect';
import uuid from 'uuid';

import { FtrProviderContext } from '../../ftr_provider_context';
import { setupIngest } from './agents/services';

export default function({ getService }: FtrProviderContext) {
export default function(providerContext: FtrProviderContext) {
const { getService } = providerContext;
const esArchiver = getService('esArchiver');
const supertest = getService('supertest');
const esClient = getService('es');

describe('fleet_unenroll_agent', () => {
let accessAPIKeyId: string;
let outputAPIKeyId: string;
before(async () => {
await esArchiver.loadIfNeeded('fleet/agents');
});
setupIngest(providerContext);
beforeEach(async () => {
const { body: accessAPIKeyBody } = await esClient.security.createApiKey({
body: {
name: `test access api key: ${uuid.v4()}`,
},
});
accessAPIKeyId = accessAPIKeyBody.id;
const { body: outputAPIKeyBody } = await esClient.security.createApiKey({
body: {
name: `test output api key: ${uuid.v4()}`,
},
});
outputAPIKeyId = outputAPIKeyBody.id;
const {
body: { _source: agentDoc },
} = await esClient.get({
index: '.kibana',
id: 'agents:agent1',
});
// @ts-ignore
agentDoc.agents.access_api_key_id = accessAPIKeyId;
agentDoc.agents.default_api_key = Buffer.from(
`${outputAPIKeyBody.id}:${outputAPIKeyBody.api_key}`
).toString('base64');

await esClient.update({
index: '.kibana',
id: 'agents:agent1',
refresh: 'true',
body: {
doc: agentDoc,
},
});
});
after(async () => {
await esArchiver.unload('fleet/agents');
});
Expand Down Expand Up @@ -54,6 +95,31 @@ export default function({ getService }: FtrProviderContext) {
expect(body.results[0].success).to.be(true);
});

it('should invalidate related API keys', async () => {
const { body } = await supertest
.post(`/api/ingest_manager/fleet/agents/unenroll`)
.set('kbn-xsrf', 'xxx')
.send({
ids: ['agent1'],
})
.expect(200);

expect(body).to.have.keys('results', 'success');
expect(body.success).to.be(true);

const {
body: { api_keys: accessAPIKeys },
} = await esClient.security.getApiKey({ id: accessAPIKeyId });
expect(accessAPIKeys).length(1);
expect(accessAPIKeys[0].invalidated).eql(true);

const {
body: { api_keys: outputAPIKeys },
} = await esClient.security.getApiKey({ id: outputAPIKeyId });
expect(outputAPIKeys).length(1);
expect(outputAPIKeys[0].invalidated).eql(true);
});

it('allow to unenroll using a kibana query', async () => {
const { body } = await supertest
.post(`/api/ingest_manager/fleet/agents/unenroll`)
Expand Down

0 comments on commit 43caa4a

Please sign in to comment.