Skip to content

Commit

Permalink
[Fields Metadata] Restrict access to integration fields by privileges (
Browse files Browse the repository at this point in the history
…elastic#199774)

## 📓 Summary

Related to elastic#198349 

Disabling authorization on fields metadata `find API` implies every user
would have access to the integration fields since we use the internal
user to retrieve the package information.

On the other hand, requiring API root-level privileges for `fleet` and
`fleetv2` would restrict more use cases since other apps might rely on
this service to consume field metadata from ECS only, with no need for
integration permissions (Discover, etc.).

To keep the door open to all these use cases, we'll check the available
user privileges on a per-request basis and allow integration fields only
when they have access to fleet and integrations, without fully
restricting the service.


https://github.com/user-attachments/assets/49b9953a-f1e1-410a-8c7f-c38d87408fcc

---------

Co-authored-by: Marco Antonio Ghiani <[email protected]>
  • Loading branch information
2 people authored and CAWilson94 committed Dec 12, 2024
1 parent c6e48ec commit 5c89491
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 13 deletions.
4 changes: 2 additions & 2 deletions x-pack/plugins/fields_metadata/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ export class FieldsMetadataPlugin
};
}

public start(_core: CoreStart, _plugins: FieldsMetadataServerPluginStartDeps) {
const fieldsMetadata = this.fieldsMetadataService.start();
public start(core: CoreStart, _plugins: FieldsMetadataServerPluginStartDeps) {
const fieldsMetadata = this.fieldsMetadataService.start(core);

return { getClient: fieldsMetadata.getClient };
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ export const initFindFieldsMetadataRoute = ({
security: {
authz: {
enabled: false,
reason: 'This route is opted out from authorization',
reason:
'This route is opted out from authorization to keep available the access to static fields metadata such as ECS fields. For other sources (fleet integrations), appropriate checks are performed at the API level.',
},
},
validate: {
Expand All @@ -38,9 +39,9 @@ export const initFindFieldsMetadataRoute = ({
},
async (_requestContext, request, response) => {
const { attributes, fieldNames, integration, dataset } = request.query;

const [_core, _startDeps, startContract] = await getStartServices();
const fieldsMetadataClient = startContract.getClient();

const fieldsMetadataClient = await startContract.getClient(request);

try {
const fieldsDictionary = await fieldsMetadataClient.find({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,11 @@ describe('FieldsMetadataClient class', () => {
integrationListExtractor,
});
fieldsMetadataClient = FieldsMetadataClient.create({
capabilities: { fleet: { read: true }, fleetv2: { read: true } },
logger,
ecsFieldsRepository,
integrationFieldsRepository,
metadataFieldsRepository,
logger,
});
});

Expand Down Expand Up @@ -184,6 +185,21 @@ describe('FieldsMetadataClient class', () => {
expect(integrationFieldsExtractor).not.toHaveBeenCalled();
expect(unknownFieldInstance).toBeUndefined();
});

it('should not resolve the field from an integration if the user has not the fleet privileges to access it', async () => {
const clientWithouthPrivileges = FieldsMetadataClient.create({
capabilities: { fleet: { read: false }, fleetv2: { read: false } },
logger,
ecsFieldsRepository,
integrationFieldsRepository,
metadataFieldsRepository,
});

const fieldInstance = await clientWithouthPrivileges.getByName('mysql.slowlog.filesort');

expect(integrationFieldsExtractor).not.toHaveBeenCalled();
expect(fieldInstance).toBeUndefined();
});
});

describe('#find', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,21 @@
* 2.0.
*/

import { Logger } from '@kbn/core/server';
import { Capabilities, Logger } from '@kbn/core/server';
import { FieldName, FieldMetadata, FieldsMetadataDictionary } from '../../../common';
import { EcsFieldsRepository } from './repositories/ecs_fields_repository';
import { IntegrationFieldsRepository } from './repositories/integration_fields_repository';
import { MetadataFieldsRepository } from './repositories/metadata_fields_repository';
import { IntegrationFieldsSearchParams } from './repositories/types';
import { FindFieldsMetadataOptions, IFieldsMetadataClient } from './types';

interface FleetCapabilities {
fleet: Capabilities[string];
fleetv2: Capabilities[string];
}

interface FieldsMetadataClientDeps {
capabilities: FleetCapabilities;
logger: Logger;
ecsFieldsRepository: EcsFieldsRepository;
metadataFieldsRepository: MetadataFieldsRepository;
Expand All @@ -22,6 +28,7 @@ interface FieldsMetadataClientDeps {

export class FieldsMetadataClient implements IFieldsMetadataClient {
private constructor(
private readonly capabilities: FleetCapabilities,
private readonly logger: Logger,
private readonly ecsFieldsRepository: EcsFieldsRepository,
private readonly metadataFieldsRepository: MetadataFieldsRepository,
Expand All @@ -43,7 +50,7 @@ export class FieldsMetadataClient implements IFieldsMetadataClient {
}

// 2. Try searching for the fiels in the Elastic Package Registry
if (!field) {
if (!field && this.hasFleetPermissions(this.capabilities)) {
field = await this.integrationFieldsRepository.getByName(fieldName, { integration, dataset });
}

Expand Down Expand Up @@ -74,13 +81,21 @@ export class FieldsMetadataClient implements IFieldsMetadataClient {
return FieldsMetadataDictionary.create(fields);
}

private hasFleetPermissions(capabilities: FleetCapabilities) {
const { fleet, fleetv2 } = capabilities;

return fleet.read && fleetv2.read;
}

public static create({
capabilities,
logger,
ecsFieldsRepository,
metadataFieldsRepository,
integrationFieldsRepository,
}: FieldsMetadataClientDeps) {
return new FieldsMetadataClient(
capabilities,
logger,
ecsFieldsRepository,
metadataFieldsRepository,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

import { KibanaRequest } from '@kbn/core-http-server';
import { createFieldsMetadataClientMock } from './fields_metadata_client.mock';
import { FieldsMetadataServiceSetup, FieldsMetadataServiceStart } from './types';

Expand All @@ -16,5 +17,7 @@ export const createFieldsMetadataServiceSetupMock =

export const createFieldsMetadataServiceStartMock =
(): jest.Mocked<FieldsMetadataServiceStart> => ({
getClient: jest.fn(() => createFieldsMetadataClientMock()),
getClient: jest.fn((_request: KibanaRequest) =>
Promise.resolve(createFieldsMetadataClientMock())
),
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import { EcsFlat as ecsFields } from '@elastic/ecs';
import { Logger } from '@kbn/core/server';
import { CoreStart, Logger } from '@kbn/core/server';
import { FieldsMetadataClient } from './fields_metadata_client';
import { EcsFieldsRepository } from './repositories/ecs_fields_repository';
import { IntegrationFieldsRepository } from './repositories/integration_fields_repository';
Expand All @@ -32,7 +32,7 @@ export class FieldsMetadataService {
};
}

public start(): FieldsMetadataServiceStart {
public start(core: CoreStart): FieldsMetadataServiceStart {
const { logger, integrationFieldsExtractor, integrationListExtractor } = this;

const ecsFieldsRepository = EcsFieldsRepository.create({ ecsFields });
Expand All @@ -43,8 +43,13 @@ export class FieldsMetadataService {
});

return {
getClient() {
getClient: async (request) => {
const { fleet, fleetv2 } = await core.capabilities.resolveCapabilities(request, {
capabilityPath: '*',
});

return FieldsMetadataClient.create({
capabilities: { fleet, fleetv2 },
logger,
ecsFieldsRepository,
metadataFieldsRepository,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

import { KibanaRequest } from '@kbn/core/server';
import { FieldName, FieldMetadata, FieldsMetadataDictionary } from '../../../common';
import {
IntegrationFieldsExtractor,
Expand All @@ -23,7 +24,7 @@ export interface FieldsMetadataServiceSetup {
}

export interface FieldsMetadataServiceStart {
getClient(): IFieldsMetadataClient;
getClient(request: KibanaRequest): Promise<IFieldsMetadataClient>;
}

export interface FindFieldsMetadataOptions extends Partial<IntegrationFieldsSearchParams> {
Expand Down

0 comments on commit 5c89491

Please sign in to comment.