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] Add base Fleet authz logic and API #119199

Merged
merged 6 commits into from
Nov 24, 2021
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
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 => ({
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function allows us to have common logic shared across client and server for enforcing access to specific features.

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,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This value is available via the useKibana hook inside our React apps

};
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am kind of confused by this as we currently call the setup from Fleet UI, should we set it to true and change that when we remove that call?

},

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