Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fleet] Remove our custom API key authentication #59212

Merged
Show file tree
Hide file tree
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
18 changes: 3 additions & 15 deletions x-pack/plugins/ingest_manager/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ import {
CoreStart,
Plugin,
PluginInitializerContext,
SavedObjectsLegacyService,
SavedObjectsServiceStart,
} from 'kibana/server';
import { SavedObjectsClient } from '../../../../src/core/server';
import { LicensingPluginSetup } from '../../licensing/server';
import { EncryptedSavedObjectsPluginStart } from '../../encrypted_saved_objects/server';
import { SecurityPluginSetup } from '../../security/server';
Expand Down Expand Up @@ -43,19 +42,11 @@ export interface IngestManagerSetupDeps {
features?: FeaturesPluginSetup;
}

/**
* Describes a set of APIs that is available in the legacy platform only and required by this plugin
* to function properly.
*/
export interface LegacyAPI {
savedObjects: SavedObjectsLegacyService;
}

export interface IngestManagerAppContext {
encryptedSavedObjects: EncryptedSavedObjectsPluginStart;
security?: SecurityPluginSetup;
config$?: Observable<IngestManagerConfigType>;
internalSavedObjectsClient: SavedObjectsClient;
savedObjects: SavedObjectsServiceStart;
}

export class IngestManagerPlugin implements Plugin {
Expand Down Expand Up @@ -139,14 +130,11 @@ export class IngestManagerPlugin implements Plugin {
encryptedSavedObjects: EncryptedSavedObjectsPluginStart;
}
) {
const internalSavedObjectsClient = new SavedObjectsClient(
core.savedObjects.createInternalRepository()
);
appContextService.start({
encryptedSavedObjects: plugins.encryptedSavedObjects,
security: this.security,
config$: this.config$,
internalSavedObjectsClient,
savedObjects: core.savedObjects,
});
}

Expand Down
55 changes: 21 additions & 34 deletions x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { RequestHandler } from 'kibana/server';
import { RequestHandler, KibanaRequest } from 'kibana/server';
import { TypeOf } from '@kbn/config-schema';
import {
GetAgentsResponse,
Expand All @@ -30,6 +30,13 @@ import * as AgentService from '../../services/agents';
import * as APIKeyService from '../../services/api_keys';
import { appContextService } from '../../services/app_context';

function getInternalUserSOClient(request: KibanaRequest) {
// soClient as kibana internal users, be carefull on how you use it, security is not enabled
return appContextService.getSavedObjects().getScopedClient(request, {
excludedWrappers: ['security'],
});
}

export const getAgentHandler: RequestHandler<TypeOf<
typeof GetOneAgentRequestSchema.params
>> = async (context, request, response) => {
Expand Down Expand Up @@ -167,18 +174,9 @@ export const postAgentCheckinHandler: RequestHandler<
TypeOf<typeof PostAgentCheckinRequestSchema.body>
> = async (context, request, response) => {
try {
const soClient = appContextService.getInternalSavedObjectsClient();
const callCluster = context.core.elasticsearch.adminClient.callAsCurrentUser;
const res = await APIKeyService.verifyAccessApiKey({ headers: request.headers, callCluster });
if (!res.valid) {
return response.unauthorized({
body: { message: 'Invalid Access API Key' },
});
}
const agent = await AgentService.getAgentByAccessAPIKeyId(
soClient,
res.accessApiKeyId as string
);
const soClient = getInternalUserSOClient(request);
const res = APIKeyService.parseApiKey(request.headers);
const agent = await AgentService.getAgentByAccessAPIKeyId(soClient, res.apiKeyId);
const { actions } = await AgentService.agentCheckin(
soClient,
agent,
Expand Down Expand Up @@ -217,18 +215,9 @@ export const postAgentAcksHandler: RequestHandler<
TypeOf<typeof PostAgentAcksRequestSchema.body>
> = async (context, request, response) => {
try {
const soClient = appContextService.getInternalSavedObjectsClient();
const callCluster = context.core.elasticsearch.adminClient.callAsCurrentUser;
const res = await APIKeyService.verifyAccessApiKey({ headers: request.headers, callCluster });
if (!res.valid) {
return response.unauthorized({
body: { message: 'Invalid Access API Key' },
});
}
const agent = await AgentService.getAgentByAccessAPIKeyId(
soClient,
res.accessApiKeyId as string
);
const soClient = getInternalUserSOClient(request);
const res = APIKeyService.parseApiKey(request.headers);
const agent = await AgentService.getAgentByAccessAPIKeyId(soClient, res.apiKeyId as string);

await AgentService.acknowledgeAgentActions(soClient, agent, request.body.action_ids);

Expand Down Expand Up @@ -259,22 +248,20 @@ export const postAgentEnrollHandler: RequestHandler<
TypeOf<typeof PostAgentEnrollRequestSchema.body>
> = async (context, request, response) => {
try {
const soClient = appContextService.getInternalSavedObjectsClient();
const callCluster = context.core.elasticsearch.adminClient.callAsCurrentUser;
const res = await APIKeyService.verifyEnrollmentAPIKey({
soClient,
headers: request.headers,
callCluster,
});
if (!res.valid || !res.enrollmentAPIKey) {
const soClient = getInternalUserSOClient(request);
const { apiKeyId } = APIKeyService.parseApiKey(request.headers);
const enrollmentAPIKey = await APIKeyService.getEnrollmentAPIKeyById(soClient, apiKeyId);

if (!enrollmentAPIKey || !enrollmentAPIKey.active) {
return response.unauthorized({
body: { message: 'Invalid Enrollment API Key' },
});
}

const agent = await AgentService.enroll(
soClient,
request.body.type,
res.enrollmentAPIKey.config_id as string,
enrollmentAPIKey.config_id as string,
{
userProvided: request.body.metadata.user_provided,
local: request.body.metadata.local,
Expand Down
6 changes: 3 additions & 3 deletions x-pack/plugins/ingest_manager/server/routes/agent/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export const registerRoutes = (router: IRouter) => {
{
path: AGENT_API_ROUTES.CHECKIN_PATTERN,
validate: PostAgentCheckinRequestSchema,
options: { tags: [], authRequired: false },
options: { tags: [] },
},
postAgentCheckinHandler
);
Expand All @@ -89,7 +89,7 @@ export const registerRoutes = (router: IRouter) => {
{
path: AGENT_API_ROUTES.ENROLL_PATTERN,
validate: PostAgentEnrollRequestSchema,
options: { tags: [], authRequired: false },
options: { tags: [] },
},
postAgentEnrollHandler
);
Expand All @@ -99,7 +99,7 @@ export const registerRoutes = (router: IRouter) => {
{
path: AGENT_API_ROUTES.ACKS_PATTERN,
validate: PostAgentAcksRequestSchema,
options: { tags: [], authRequired: false },
options: { tags: [] },
},
postAgentAcksHandler
);
Expand Down
82 changes: 16 additions & 66 deletions x-pack/plugins/ingest_manager/server/services/api_keys/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

import { SavedObjectsClientContract, SavedObject, KibanaRequest } from 'kibana/server';
import { ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE } from '../../constants';
import { CallESAsCurrentUser, EnrollmentAPIKeySOAttributes, EnrollmentAPIKey } from '../../types';
import { createAPIKey, authenticate } from './security';
import { EnrollmentAPIKeySOAttributes, EnrollmentAPIKey } from '../../types';
import { createAPIKey } from './security';

export * from './enrollment_api_key';

Expand Down Expand Up @@ -52,72 +52,22 @@ export async function generateAccessApiKey(
return { id: key.id, key: Buffer.from(`${key.id}:${key.api_key}`).toString('base64') };
}

/**
* Verify if an an access api key is valid
*/
export async function verifyAccessApiKey({
headers,
callCluster,
}: {
headers: KibanaRequest['headers'];
callCluster: CallESAsCurrentUser;
}): Promise<{ valid: boolean; accessApiKeyId?: string; reason?: string }> {
try {
const { apiKeyId } = _parseApiKey(headers);

await authenticate(callCluster);

return {
valid: true,
accessApiKeyId: apiKeyId,
};
} catch (error) {
return {
valid: false,
reason: error.message || 'ApiKey is not valid',
};
}
}

export async function verifyEnrollmentAPIKey({
soClient,
headers,
callCluster,
}: {
soClient: SavedObjectsClientContract;
headers: KibanaRequest['headers'];
callCluster: CallESAsCurrentUser;
}) {
try {
const { apiKeyId } = _parseApiKey(headers);

await authenticate(callCluster);

const [enrollmentAPIKey] = (
await soClient.find<EnrollmentAPIKeySOAttributes>({
type: ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE,
searchFields: ['api_key_id'],
search: apiKeyId,
})
).saved_objects.map(_savedObjectToEnrollmentApiKey);

if (!enrollmentAPIKey || !enrollmentAPIKey.active) {
throw new Error('Enrollement api key does not exists or is not active');
}

return {
valid: true,
enrollmentAPIKey,
};
} catch (error) {
return {
valid: false,
reason: error.message || 'ApiKey is not valid',
};
}
export async function getEnrollmentAPIKeyById(
soClient: SavedObjectsClientContract,
apiKeyId: string
) {
const [enrollmentAPIKey] = (
await soClient.find<EnrollmentAPIKeySOAttributes>({
type: ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE,
searchFields: ['api_key_id'],
search: apiKeyId,
})
).saved_objects.map(_savedObjectToEnrollmentApiKey);

return enrollmentAPIKey;
}

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

if (!authorizationHeader) {
Expand Down
14 changes: 7 additions & 7 deletions x-pack/plugins/ingest_manager/server/services/app_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/
import { BehaviorSubject, Observable } from 'rxjs';
import { first } from 'rxjs/operators';
import { SavedObjectsClientContract } from 'kibana/server';
import { SavedObjectsServiceStart } from 'kibana/server';
import { EncryptedSavedObjectsPluginStart } from '../../../encrypted_saved_objects/server';
import { SecurityPluginSetup } from '../../../security/server';
import { IngestManagerConfigType } from '../../common';
Expand All @@ -16,12 +16,12 @@ class AppContextService {
private security: SecurityPluginSetup | undefined;
private config$?: Observable<IngestManagerConfigType>;
private configSubject$?: BehaviorSubject<IngestManagerConfigType>;
private internalSavedObjectsClient: SavedObjectsClientContract | undefined;
private savedObjects: SavedObjectsServiceStart | undefined;

public async start(appContext: IngestManagerAppContext) {
this.encryptedSavedObjects = appContext.encryptedSavedObjects;
this.security = appContext.security;
this.internalSavedObjectsClient = appContext.internalSavedObjectsClient;
this.savedObjects = appContext.savedObjects;

if (appContext.config$) {
this.config$ = appContext.config$;
Expand Down Expand Up @@ -49,11 +49,11 @@ class AppContextService {
return this.config$;
}

public getInternalSavedObjectsClient() {
if (!this.internalSavedObjectsClient) {
throw new Error('No internal savedObjectsClient');
public getSavedObjects() {
if (!this.savedObjects) {
throw new Error('Saved objects start service not set.');
}
return this.internalSavedObjectsClient;
return this.savedObjects;
}
}

Expand Down
4 changes: 1 addition & 3 deletions x-pack/test/api_integration/apis/fleet/agents/enroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default function(providerContext: FtrProviderContext) {
});

it('should not allow to enroll an agent with a invalid enrollment', async () => {
const { body: apiResponse } = await supertest
await supertest
.post(`/api/ingest_manager/fleet/agents/enroll`)
.set('kbn-xsrf', 'xxx')
.set('Authorization', 'ApiKey NOTAVALIDKEY')
Expand All @@ -63,8 +63,6 @@ export default function(providerContext: FtrProviderContext) {
},
})
.expect(401);

expect(apiResponse.message).to.match(/Invalid Enrollment API Key/);
});

it('should not allow to enroll an agent with a shared id if it already exists ', async () => {
Expand Down