Skip to content

Commit

Permalink
[Fleet] Add base Fleet authz logic and API (#119199)
Browse files Browse the repository at this point in the history
* Add base Fleet authz logic and API

* Fix linter error

* Fix ts checks

* Fix ts checks again

Co-authored-by: criamico <[email protected]>
Co-authored-by: Kibana Machine <[email protected]>
# Conflicts:
#	x-pack/plugins/fleet/storybook/context/index.tsx
  • Loading branch information
joshdover authored and criamico committed Nov 29, 2021
1 parent ae1e5b0 commit 24bb56f
Show file tree
Hide file tree
Showing 12 changed files with 249 additions and 20 deletions.
64 changes: 64 additions & 0 deletions x-pack/plugins/fleet/common/authz.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

export interface FleetAuthz {
fleet: {
all: boolean;
setup: boolean;
readEnrollmentTokens: boolean;
};

integrations: {
readPackageInfo: boolean;
readInstalledPackages: boolean;
installPackages: boolean;
upgradePackages: boolean;
removePackages: boolean;

readPackageSettings: boolean;
writePackageSettings: boolean;

readIntegrationPolicies: boolean;
writeIntegrationPolicies: boolean;
};
}

interface CalculateParams {
fleet: {
all: boolean;
setup: boolean;
};

integrations: {
all: boolean;
read: boolean;
};
}

export const calculateAuthz = ({ fleet, integrations }: CalculateParams): FleetAuthz => ({
fleet: {
all: fleet.all && (integrations.all || integrations.read),

// These are currently used by Fleet Server setup
setup: fleet.all || fleet.setup,
readEnrollmentTokens: fleet.all || fleet.setup,
},

integrations: {
readPackageInfo: fleet.all || fleet.setup || integrations.all || integrations.read,
readInstalledPackages: integrations.all || integrations.read,
installPackages: fleet.all && integrations.all,
upgradePackages: fleet.all && integrations.all,
removePackages: fleet.all && integrations.all,

readPackageSettings: fleet.all && integrations.all,
writePackageSettings: fleet.all && integrations.all,

readIntegrationPolicies: fleet.all && integrations.all,
writeIntegrationPolicies: fleet.all && integrations.all,
},
});
2 changes: 2 additions & 0 deletions x-pack/plugins/fleet/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@
export * from './constants';
export * from './services';
export * from './types';
export type { FleetAuthz } from './authz';
export { calculateAuthz } from './authz';
22 changes: 22 additions & 0 deletions x-pack/plugins/fleet/public/mock/fleet_start_services.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import type { IStorage } from '../../../../../src/plugins/kibana_utils/public';
import { Storage } from '../../../../../src/plugins/kibana_utils/public';
import { setHttpClient } from '../hooks/use_request';

import type { FleetAuthz } from '../../common';

import { createStartDepsMock } from './plugin_dependencies';
import type { MockedFleetStartServices } from './types';

Expand All @@ -28,6 +30,25 @@ const createMockStore = (): MockedKeys<IStorage> => {
};
};

const fleetAuthzMock: FleetAuthz = {
fleet: {
all: true,
setup: true,
readEnrollmentTokens: true,
},
integrations: {
readPackageInfo: true,
readInstalledPackages: true,
installPackages: true,
upgradePackages: true,
removePackages: true,
readPackageSettings: true,
writePackageSettings: true,
readIntegrationPolicies: true,
writeIntegrationPolicies: true,
},
};

const configureStartServices = (services: MockedFleetStartServices): void => {
// Store the http for use by useRequest
setHttpClient(services.http);
Expand All @@ -52,6 +73,7 @@ export const createStartServices = (basePath: string = '/mock'): MockedFleetStar
...coreMock.createStart({ basePath }),
...createStartDepsMock(),
storage: new Storage(createMockStore()) as jest.Mocked<Storage>,
authz: fleetAuthzMock,
};

configureStartServices(startServices);
Expand Down
18 changes: 18 additions & 0 deletions x-pack/plugins/fleet/public/mock/plugin_interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,23 @@ export const createStartMock = (extensionsStorage: UIExtensionsStorage = {}): Mo
return {
isInitialized: jest.fn().mockResolvedValue(true),
registerExtension: createExtensionRegistrationCallback(extensionsStorage),
authz: {
fleet: {
all: true,
setup: true,
readEnrollmentTokens: true,
},
integrations: {
readPackageInfo: true,
readInstalledPackages: true,
installPackages: true,
upgradePackages: true,
removePackages: true,
readPackageSettings: true,
writePackageSettings: true,
readIntegrationPolicies: true,
writeIntegrationPolicies: true,
},
},
};
};
45 changes: 32 additions & 13 deletions x-pack/plugins/fleet/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,14 @@ import { Storage } from '../../../../src/plugins/kibana_utils/public';
import type { LicensingPluginSetup } from '../../licensing/public';
import type { CloudSetup } from '../../cloud/public';
import type { GlobalSearchPluginSetup } from '../../global_search/public';
import { PLUGIN_ID, INTEGRATIONS_PLUGIN_ID, setupRouteService, appRoutesService } from '../common';
import type { CheckPermissionsResponse, PostFleetSetupResponse } from '../common';
import {
PLUGIN_ID,
INTEGRATIONS_PLUGIN_ID,
setupRouteService,
appRoutesService,
calculateAuthz,
} from '../common';
import type { CheckPermissionsResponse, PostFleetSetupResponse, FleetAuthz } from '../common';

import type { FleetConfigType } from '../common/types';

Expand All @@ -65,6 +71,8 @@ export interface FleetSetup {}
* Describes public Fleet plugin contract returned at the `start` stage.
*/
export interface FleetStart {
/** Authorization for the current user */
authz: FleetAuthz;
registerExtension: UIExtensionRegistrationCallback;
isInitialized: () => Promise<true>;
}
Expand All @@ -90,6 +98,7 @@ export interface FleetStartServices extends CoreStart, FleetStartDeps {
storage: Storage;
share: SharePluginStart;
cloud?: CloudSetup;
authz: FleetAuthz;
}

export class FleetPlugin implements Plugin<FleetSetup, FleetStart, FleetSetupDeps, FleetStartDeps> {
Expand All @@ -103,7 +112,7 @@ export class FleetPlugin implements Plugin<FleetSetup, FleetStart, FleetSetupDep
this.kibanaVersion = initializerContext.env.packageInfo.version;
}

public setup(core: CoreSetup, deps: FleetSetupDeps) {
public setup(core: CoreSetup<FleetStartDeps, FleetStart>, deps: FleetSetupDeps) {
const config = this.config;
const kibanaVersion = this.kibanaVersion;
const extensions = this.extensions;
Expand All @@ -129,16 +138,13 @@ export class FleetPlugin implements Plugin<FleetSetup, FleetStart, FleetSetupDep
order: 9019,
euiIconType: 'logoElastic',
mount: async (params: AppMountParameters) => {
const [coreStartServices, startDepsServices] = (await core.getStartServices()) as [
CoreStart,
FleetStartDeps,
FleetStart
];
const [coreStartServices, startDepsServices, fleetStart] = await core.getStartServices();
const startServices: FleetStartServices = {
...coreStartServices,
...startDepsServices,
storage: this.storage,
cloud: deps.cloud,
authz: fleetStart.authz,
};
const { renderApp, teardownIntegrations } = await import('./applications/integrations');

Expand Down Expand Up @@ -169,16 +175,13 @@ export class FleetPlugin implements Plugin<FleetSetup, FleetStart, FleetSetupDep
euiIconType: 'logoElastic',
appRoute: '/app/fleet',
mount: async (params: AppMountParameters) => {
const [coreStartServices, startDepsServices] = (await core.getStartServices()) as [
CoreStart,
FleetStartDeps,
FleetStart
];
const [coreStartServices, startDepsServices, fleetStart] = await core.getStartServices();
const startServices: FleetStartServices = {
...coreStartServices,
...startDepsServices,
storage: this.storage,
cloud: deps.cloud,
authz: fleetStart.authz,
};
const { renderApp, teardownFleet } = await import('./applications/fleet');
const unmount = renderApp(startServices, params, config, kibanaVersion, extensions);
Expand Down Expand Up @@ -243,7 +246,23 @@ export class FleetPlugin implements Plugin<FleetSetup, FleetStart, FleetSetupDep
Component: LazyCustomLogsAssetsExtension,
});

const { capabilities } = core.application;
const authz = calculateAuthz({
fleet: {
// Once we have a split privilege, this should be using fleetv2
// all: capabilities.fleetv2.all as boolean,
all: capabilities.fleet.all as boolean,
setup: false, // browser users will never have setup privileges
},

integrations: {
all: capabilities.fleet.all as boolean,
read: capabilities.fleet.read as boolean,
},
});

return {
authz,
isInitialized: () => {
if (!successPromise) {
successPromise = Promise.resolve().then(async () => {
Expand Down
23 changes: 23 additions & 0 deletions x-pack/plugins/fleet/server/mocks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type { PackagePolicyServiceInterface } from '../services/package_policy';
import type { AgentPolicyServiceInterface, AgentService } from '../services';
import type { FleetAppContext } from '../plugin';
import { createMockTelemetryEventsSender } from '../telemetry/__mocks__';
import type { FleetAuthz } from '../../common';

// Export all mocks from artifacts
export * from '../services/artifacts/mocks';
Expand Down Expand Up @@ -120,3 +121,25 @@ export const createMockAgentService = (): jest.Mocked<AgentService> => {
listAgents: jest.fn(),
};
};

/**
* Creates mock `authz` object
*/
export const fleetAuthzMock: FleetAuthz = {
fleet: {
all: true,
setup: true,
readEnrollmentTokens: true,
},
integrations: {
readPackageInfo: true,
readInstalledPackages: true,
installPackages: true,
upgradePackages: true,
removePackages: true,
readPackageSettings: true,
writePackageSettings: true,
readIntegrationPolicies: true,
writeIntegrationPolicies: true,
},
};
20 changes: 14 additions & 6 deletions x-pack/plugins/fleet/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {
PluginInitializerContext,
SavedObjectsServiceStart,
HttpServiceSetup,
KibanaRequest,
} from 'kibana/server';
import type { UsageCollectionSetup } from 'src/plugins/usage_collection/server';

Expand All @@ -29,7 +30,7 @@ import type {
} from '../../encrypted_saved_objects/server';
import type { SecurityPluginSetup, SecurityPluginStart } from '../../security/server';
import type { PluginSetupContract as FeaturesPluginSetup } from '../../features/server';
import type { FleetConfigType } from '../common';
import type { FleetConfigType, FleetAuthz } from '../common';
import { INTEGRATIONS_PLUGIN_ID } from '../common';
import type { CloudSetup } from '../../cloud/server';

Expand Down Expand Up @@ -79,7 +80,7 @@ import {
} from './services/agents';
import { registerFleetUsageCollector } from './collectors/register';
import { getInstallation, ensureInstalledPackage } from './services/epm/packages';
import { RouterWrappers } from './routes/security';
import { getAuthzFromRequest, RouterWrappers } from './routes/security';
import { FleetArtifactsClient } from './services/artifacts';
import type { FleetRouter } from './types/request_context';
import { TelemetryEventsSender } from './telemetry/sender';
Expand Down Expand Up @@ -142,6 +143,9 @@ export interface FleetStartContract {
* services
*/
fleetSetupCompleted: () => Promise<void>;
authz: {
fromRequest(request: KibanaRequest): Promise<FleetAuthz>;
};
esIndexPatternService: ESIndexPatternService;
packageService: PackageService;
agentService: AgentService;
Expand Down Expand Up @@ -205,7 +209,7 @@ export class FleetPlugin
// TODO: Flesh out privileges
if (deps.features) {
deps.features.registerKibanaFeature({
id: PLUGIN_ID,
id: 'fleet',
name: 'Fleet and Integrations',
category: DEFAULT_APP_CATEGORIES.management,
app: [PLUGIN_ID, INTEGRATIONS_PLUGIN_ID, 'kibana'],
Expand All @@ -230,7 +234,7 @@ export class FleetPlugin
},
privileges: {
all: {
api: [`${PLUGIN_ID}-read`, `${PLUGIN_ID}-all`],
api: [`fleet-read`, `fleet-all`, `integrations-all`, `integrations-read`],
app: [PLUGIN_ID, INTEGRATIONS_PLUGIN_ID, 'kibana'],
catalogue: ['fleet'],
savedObject: {
Expand All @@ -240,7 +244,7 @@ export class FleetPlugin
ui: ['show', 'read', 'write'],
},
read: {
api: [`${PLUGIN_ID}-read`],
api: [`fleet-read`, `integrations-read`],
app: [PLUGIN_ID, INTEGRATIONS_PLUGIN_ID, 'kibana'],
catalogue: ['fleet'], // TODO: check if this is actually available to read user
savedObject: {
Expand All @@ -255,7 +259,8 @@ export class FleetPlugin

core.http.registerRouteHandlerContext<FleetRequestHandlerContext, 'fleet'>(
'fleet',
(coreContext, request) => ({
async (coreContext, request) => ({
authz: await getAuthzFromRequest(request),
epm: {
// Use a lazy getter to avoid constructing this client when not used by a request handler
get internalSoClient() {
Expand Down Expand Up @@ -348,6 +353,9 @@ export class FleetPlugin
})();

return {
authz: {
fromRequest: getAuthzFromRequest,
},
fleetSetupCompleted: () => fleetSetupPromise,
esIndexPatternService: new ESIndexPatternSavedObjectService(),
packageService: {
Expand Down
Loading

0 comments on commit 24bb56f

Please sign in to comment.