mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Fleet] remove superuser requirement in PackageService (#163727)
## Summary Remove superuser requirement in PackageService and replacing it with the same privilege requirement as the API uses. `PackageService` was introduced in https://github.com/elastic/kibana/pull/121589 @joeypoon Is it okay for security team to change these privileges? WIP, added only for `ensureInstalledPackage` for now. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
This commit is contained in:
parent
11e57be842
commit
feb72cd69f
4 changed files with 76 additions and 53 deletions
|
@ -14,6 +14,7 @@ import {
|
|||
type FleetAuthzRouter,
|
||||
getRouteRequiredAuthz,
|
||||
} from '../../services/security';
|
||||
import type { FleetAuthzRouteConfig } from '../../services/security/types';
|
||||
|
||||
import type {
|
||||
DeletePackageResponse,
|
||||
|
@ -68,14 +69,20 @@ import {
|
|||
|
||||
const MAX_FILE_SIZE_BYTES = 104857600; // 100MB
|
||||
|
||||
export const INSTALL_PACKAGES_AUTHZ: FleetAuthzRouteConfig['fleetAuthz'] = {
|
||||
integrations: { installPackages: true },
|
||||
};
|
||||
|
||||
export const READ_PACKAGE_INFO_AUTHZ: FleetAuthzRouteConfig['fleetAuthz'] = {
|
||||
integrations: { readPackageInfo: true },
|
||||
};
|
||||
|
||||
export const registerRoutes = (router: FleetAuthzRouter) => {
|
||||
router.get(
|
||||
{
|
||||
path: EPM_API_ROUTES.CATEGORIES_PATTERN,
|
||||
validate: GetCategoriesRequestSchema,
|
||||
fleetAuthz: {
|
||||
integrations: { readPackageInfo: true },
|
||||
},
|
||||
fleetAuthz: READ_PACKAGE_INFO_AUTHZ,
|
||||
},
|
||||
getCategoriesHandler
|
||||
);
|
||||
|
@ -84,9 +91,7 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
|
|||
{
|
||||
path: EPM_API_ROUTES.LIST_PATTERN,
|
||||
validate: GetPackagesRequestSchema,
|
||||
fleetAuthz: {
|
||||
integrations: { readPackageInfo: true },
|
||||
},
|
||||
fleetAuthz: READ_PACKAGE_INFO_AUTHZ,
|
||||
},
|
||||
getListHandler
|
||||
);
|
||||
|
@ -95,9 +100,7 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
|
|||
{
|
||||
path: EPM_API_ROUTES.INSTALLED_LIST_PATTERN,
|
||||
validate: GetInstalledPackagesRequestSchema,
|
||||
fleetAuthz: {
|
||||
integrations: { readPackageInfo: true },
|
||||
},
|
||||
fleetAuthz: READ_PACKAGE_INFO_AUTHZ,
|
||||
},
|
||||
getInstalledListHandler
|
||||
);
|
||||
|
@ -106,9 +109,7 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
|
|||
{
|
||||
path: EPM_API_ROUTES.LIMITED_LIST_PATTERN,
|
||||
validate: false,
|
||||
fleetAuthz: {
|
||||
integrations: { readPackageInfo: true },
|
||||
},
|
||||
fleetAuthz: READ_PACKAGE_INFO_AUTHZ,
|
||||
},
|
||||
getLimitedListHandler
|
||||
);
|
||||
|
@ -117,9 +118,7 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
|
|||
{
|
||||
path: EPM_API_ROUTES.STATS_PATTERN,
|
||||
validate: GetStatsRequestSchema,
|
||||
fleetAuthz: {
|
||||
integrations: { readPackageInfo: true },
|
||||
},
|
||||
fleetAuthz: READ_PACKAGE_INFO_AUTHZ,
|
||||
},
|
||||
getStatsHandler
|
||||
);
|
||||
|
@ -128,9 +127,7 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
|
|||
{
|
||||
path: EPM_API_ROUTES.FILEPATH_PATTERN,
|
||||
validate: GetFileRequestSchema,
|
||||
fleetAuthz: {
|
||||
integrations: { readPackageInfo: true },
|
||||
},
|
||||
fleetAuthz: READ_PACKAGE_INFO_AUTHZ,
|
||||
},
|
||||
getFileHandler
|
||||
);
|
||||
|
@ -161,9 +158,7 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
|
|||
{
|
||||
path: EPM_API_ROUTES.INSTALL_FROM_REGISTRY_PATTERN,
|
||||
validate: InstallPackageFromRegistryRequestSchema,
|
||||
fleetAuthz: {
|
||||
integrations: { installPackages: true },
|
||||
},
|
||||
fleetAuthz: INSTALL_PACKAGES_AUTHZ,
|
||||
},
|
||||
installPackageFromRegistryHandler
|
||||
);
|
||||
|
@ -202,9 +197,7 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
|
|||
{
|
||||
path: EPM_API_ROUTES.CUSTOM_INTEGRATIONS_PATTERN,
|
||||
validate: CreateCustomIntegrationRequestSchema,
|
||||
fleetAuthz: {
|
||||
integrations: { installPackages: true },
|
||||
},
|
||||
fleetAuthz: INSTALL_PACKAGES_AUTHZ,
|
||||
},
|
||||
createCustomIntegrationHandler
|
||||
);
|
||||
|
@ -224,9 +217,7 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
|
|||
{
|
||||
path: EPM_API_ROUTES.VERIFICATION_KEY_ID,
|
||||
validate: false,
|
||||
fleetAuthz: {
|
||||
integrations: { readPackageInfo: true },
|
||||
},
|
||||
fleetAuthz: READ_PACKAGE_INFO_AUTHZ,
|
||||
},
|
||||
getVerificationKeyIdHandler
|
||||
);
|
||||
|
@ -235,9 +226,7 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
|
|||
{
|
||||
path: EPM_API_ROUTES.DATA_STREAMS_PATTERN,
|
||||
validate: GetDataStreamsRequestSchema,
|
||||
fleetAuthz: {
|
||||
integrations: { readPackageInfo: true },
|
||||
},
|
||||
fleetAuthz: READ_PACKAGE_INFO_AUTHZ,
|
||||
},
|
||||
getDataStreamsHandler
|
||||
);
|
||||
|
@ -246,9 +235,7 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
|
|||
{
|
||||
path: EPM_API_ROUTES.BULK_ASSETS_PATTERN,
|
||||
validate: GetBulkAssetsRequestSchema,
|
||||
fleetAuthz: {
|
||||
integrations: { readPackageInfo: true },
|
||||
},
|
||||
fleetAuthz: READ_PACKAGE_INFO_AUTHZ,
|
||||
},
|
||||
getBulkAssetsHandler
|
||||
);
|
||||
|
@ -305,9 +292,7 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
|
|||
{
|
||||
path: EPM_API_ROUTES.INSTALL_FROM_REGISTRY_PATTERN_DEPRECATED,
|
||||
validate: InstallPackageFromRegistryRequestSchemaDeprecated,
|
||||
fleetAuthz: {
|
||||
integrations: { installPackages: true },
|
||||
},
|
||||
fleetAuthz: INSTALL_PACKAGES_AUTHZ,
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const newRequest = {
|
||||
|
@ -356,7 +341,7 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
|
|||
path: EPM_API_ROUTES.REAUTHORIZE_TRANSFORMS,
|
||||
validate: ReauthorizeTransformRequestSchema,
|
||||
fleetAuthz: {
|
||||
integrations: { installPackages: true },
|
||||
...INSTALL_PACKAGES_AUTHZ,
|
||||
packagePrivileges: {
|
||||
transform: {
|
||||
actions: {
|
||||
|
|
|
@ -5,7 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
jest.mock('../security');
|
||||
jest.mock('../security', () => {
|
||||
return {
|
||||
...jest.requireActual('../security'),
|
||||
getAuthzFromRequest: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
import type { MockedLogger } from '@kbn/logging-mocks';
|
||||
|
||||
|
@ -20,6 +25,8 @@ import {
|
|||
import { FleetUnauthorizedError } from '../../errors';
|
||||
import type { InstallablePackage } from '../../types';
|
||||
|
||||
import { getAuthzFromRequest } from '../security';
|
||||
|
||||
import type { PackageClient, PackageService } from './package_service';
|
||||
import { PackageServiceImpl } from './package_service';
|
||||
import * as epmPackagesGet from './packages/get';
|
||||
|
@ -28,6 +35,7 @@ import * as epmRegistry from './registry';
|
|||
import * as epmTransformsInstall from './elasticsearch/transform/install';
|
||||
import * as epmArchiveParse from './archive/parse';
|
||||
|
||||
const mockGetAuthzFromRequest = getAuthzFromRequest as jest.Mock;
|
||||
const testKeys = [
|
||||
'getInstallation',
|
||||
'ensureInstalledPackage',
|
||||
|
@ -206,6 +214,14 @@ describe('PackageService', () => {
|
|||
const unauthError = new FleetUnauthorizedError(
|
||||
`User does not have adequate permissions to access Fleet packages.`
|
||||
);
|
||||
beforeEach(() => {
|
||||
mockGetAuthzFromRequest.mockResolvedValueOnce({
|
||||
integrations: {
|
||||
installPackages: false,
|
||||
readPackageInfo: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it(`rejects on ${testKey}`, async () => {
|
||||
const { method, args } = getTest(
|
||||
|
@ -217,6 +233,14 @@ describe('PackageService', () => {
|
|||
});
|
||||
|
||||
describe.each(testKeys)('with required privileges', (testKey: string) => {
|
||||
beforeEach(() => {
|
||||
mockGetAuthzFromRequest.mockResolvedValueOnce({
|
||||
integrations: {
|
||||
installPackages: true,
|
||||
readPackageInfo: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
it(`calls ${testKey} and returns results`, async () => {
|
||||
const mockClients = {
|
||||
packageClient: mockPackageService.asInternalUser,
|
||||
|
|
|
@ -27,8 +27,10 @@ import type {
|
|||
ArchivePackage,
|
||||
BundledPackage,
|
||||
} from '../../types';
|
||||
import { checkSuperuser } from '../security';
|
||||
import type { FleetAuthzRouteConfig } from '../security/types';
|
||||
import { checkSuperuser, getAuthzFromRequest, doesNotHaveRequiredFleetAuthz } from '../security';
|
||||
import { FleetUnauthorizedError } from '../../errors';
|
||||
import { INSTALL_PACKAGES_AUTHZ, READ_PACKAGE_INFO_AUTHZ } from '../../routes/epm';
|
||||
|
||||
import { installTransforms, isTransform } from './elasticsearch/transform/install';
|
||||
import type { FetchFindLatestPackageOptions } from './registry';
|
||||
|
@ -86,8 +88,17 @@ export class PackageServiceImpl implements PackageService {
|
|||
) {}
|
||||
|
||||
public asScoped(request: KibanaRequest) {
|
||||
const preflightCheck = () => {
|
||||
if (!checkSuperuser(request)) {
|
||||
const preflightCheck = async (requiredAuthz?: FleetAuthzRouteConfig['fleetAuthz']) => {
|
||||
if (requiredAuthz) {
|
||||
const requestedAuthz = await getAuthzFromRequest(request);
|
||||
|
||||
const noRequiredAuthz = doesNotHaveRequiredFleetAuthz(requestedAuthz, requiredAuthz);
|
||||
if (noRequiredAuthz) {
|
||||
throw new FleetUnauthorizedError(
|
||||
`User does not have adequate permissions to access Fleet packages.`
|
||||
);
|
||||
}
|
||||
} else if (!checkSuperuser(request)) {
|
||||
throw new FleetUnauthorizedError(
|
||||
`User does not have adequate permissions to access Fleet packages.`
|
||||
);
|
||||
|
@ -115,7 +126,9 @@ class PackageClientImpl implements PackageClient {
|
|||
private readonly internalEsClient: ElasticsearchClient,
|
||||
private readonly internalSoClient: SavedObjectsClientContract,
|
||||
private readonly logger: Logger,
|
||||
private readonly preflightCheck?: () => void | Promise<void>,
|
||||
private readonly preflightCheck?: (
|
||||
requiredAuthz?: FleetAuthzRouteConfig['fleetAuthz']
|
||||
) => void | Promise<void>,
|
||||
private readonly request?: KibanaRequest
|
||||
) {}
|
||||
|
||||
|
@ -127,7 +140,7 @@ class PackageClientImpl implements PackageClient {
|
|||
}
|
||||
|
||||
public async getInstallation(pkgName: string) {
|
||||
await this.#runPreflight();
|
||||
await this.#runPreflight(READ_PACKAGE_INFO_AUTHZ);
|
||||
return getInstallation({
|
||||
pkgName,
|
||||
savedObjectsClient: this.internalSoClient,
|
||||
|
@ -139,7 +152,7 @@ class PackageClientImpl implements PackageClient {
|
|||
pkgVersion?: string;
|
||||
spaceId?: string;
|
||||
}): Promise<Installation | undefined> {
|
||||
await this.#runPreflight();
|
||||
await this.#runPreflight(INSTALL_PACKAGES_AUTHZ);
|
||||
|
||||
return ensureInstalledPackage({
|
||||
...options,
|
||||
|
@ -152,12 +165,12 @@ class PackageClientImpl implements PackageClient {
|
|||
packageName: string,
|
||||
options?: FetchFindLatestPackageOptions
|
||||
): Promise<RegistryPackage | BundledPackage> {
|
||||
await this.#runPreflight();
|
||||
await this.#runPreflight(READ_PACKAGE_INFO_AUTHZ);
|
||||
return fetchFindLatestPackageOrThrow(packageName, options);
|
||||
}
|
||||
|
||||
public async readBundledPackage(bundledPackage: BundledPackage) {
|
||||
await this.#runPreflight();
|
||||
await this.#runPreflight(READ_PACKAGE_INFO_AUTHZ);
|
||||
return generatePackageInfoFromArchiveBuffer(bundledPackage.buffer, 'application/zip');
|
||||
}
|
||||
|
||||
|
@ -166,7 +179,7 @@ class PackageClientImpl implements PackageClient {
|
|||
packageVersion: string,
|
||||
options?: Parameters<typeof getPackage>['2']
|
||||
) {
|
||||
await this.#runPreflight();
|
||||
await this.#runPreflight(READ_PACKAGE_INFO_AUTHZ);
|
||||
return getPackage(packageName, packageVersion, options);
|
||||
}
|
||||
|
||||
|
@ -176,7 +189,7 @@ class PackageClientImpl implements PackageClient {
|
|||
prerelease?: false;
|
||||
}) {
|
||||
const { excludeInstallStatus, category, prerelease } = params || {};
|
||||
await this.#runPreflight();
|
||||
await this.#runPreflight(READ_PACKAGE_INFO_AUTHZ);
|
||||
return getPackages({
|
||||
savedObjectsClient: this.internalSoClient,
|
||||
excludeInstallStatus,
|
||||
|
@ -189,7 +202,7 @@ class PackageClientImpl implements PackageClient {
|
|||
packageInfo: InstallablePackage,
|
||||
assetPaths: string[]
|
||||
): Promise<InstalledAssetType[]> {
|
||||
await this.#runPreflight();
|
||||
await this.#runPreflight(INSTALL_PACKAGES_AUTHZ);
|
||||
let installedAssets: InstalledAssetType[] = [];
|
||||
|
||||
const transformPaths = assetPaths.filter(isTransform);
|
||||
|
@ -207,7 +220,7 @@ class PackageClientImpl implements PackageClient {
|
|||
}
|
||||
|
||||
async #reinstallTransforms(packageInfo: InstallablePackage, paths: string[]) {
|
||||
const authorizationHeader = await this.getAuthorizationHeader();
|
||||
const authorizationHeader = this.getAuthorizationHeader();
|
||||
|
||||
const { installedTransforms } = await installTransforms({
|
||||
installablePackage: packageInfo,
|
||||
|
@ -222,9 +235,9 @@ class PackageClientImpl implements PackageClient {
|
|||
return installedTransforms;
|
||||
}
|
||||
|
||||
#runPreflight() {
|
||||
async #runPreflight(requiredAuthz?: FleetAuthzRouteConfig['fleetAuthz']) {
|
||||
if (this.preflightCheck) {
|
||||
return this.preflightCheck();
|
||||
return await this.preflightCheck(requiredAuthz);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,8 @@ describe('Endpoint Policy Response', () => {
|
|||
login();
|
||||
});
|
||||
|
||||
describe('from Fleet Agent Details page', () => {
|
||||
// TODO failing test skipped https://github.com/elastic/kibana/issues/162428
|
||||
describe.skip('from Fleet Agent Details page', () => {
|
||||
it('should display policy response with errors', () => {
|
||||
navigateToFleetAgentDetails(endpointMetadata.agent.id);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue