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

Use kibana_system user for Fleet setup and package operations #112808

Merged
merged 12 commits into from
Oct 15, 2021
Merged
18 changes: 16 additions & 2 deletions x-pack/plugins/fleet/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ import {
registerPreconfigurationRoutes,
} from './routes';

import type { ExternalCallback } from './types';
import type { ExternalCallback, FleetRequestHandlerContext } from './types';
import type {
ESIndexPatternService,
AgentService,
Expand Down Expand Up @@ -231,7 +231,21 @@ export class FleetPlugin
});
}

const router = core.http.createRouter();
core.http.registerRouteHandlerContext<FleetRequestHandlerContext, 'fleet'>(
'fleet',
(coreContext, request) => ({
epm: {
// Use a lazy getter to avoid constructing this client when not used by a request handler
get internalSoClient() {
return appContextService
.getSavedObjects()
.getScopedClient(request, { excludedWrappers: ['security'] });
},
},
})
);

const router = core.http.createRouter<FleetRequestHandlerContext>();

// Register usage collection
registerFleetUsageCollector(core, config, deps.usageCollection);
Expand Down
233 changes: 113 additions & 120 deletions x-pack/plugins/fleet/server/routes/epm/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import path from 'path';

import type { TypeOf } from '@kbn/config-schema';
import mime from 'mime-types';
import type { RequestHandler, ResponseHeaders, KnownHeaders } from 'src/core/server';
import type { ResponseHeaders, KnownHeaders } from 'src/core/server';

import type {
GetInfoResponse,
Expand All @@ -34,6 +34,7 @@ import type {
DeletePackageRequestSchema,
BulkUpgradePackagesFromRegistryRequestSchema,
GetStatsRequestSchema,
FleetRequestHandler,
UpdatePackageRequestSchema,
} from '../../types';
import {
Expand All @@ -57,7 +58,7 @@ import { getAsset } from '../../services/epm/archive/storage';
import { getPackageUsageStats } from '../../services/epm/packages/get';
import { updatePackage } from '../../services/epm/packages/update';

export const getCategoriesHandler: RequestHandler<
export const getCategoriesHandler: FleetRequestHandler<
undefined,
TypeOf<typeof GetCategoriesRequestSchema.query>
> = async (context, request, response) => {
Expand All @@ -72,12 +73,12 @@ export const getCategoriesHandler: RequestHandler<
}
};

export const getListHandler: RequestHandler<
export const getListHandler: FleetRequestHandler<
undefined,
TypeOf<typeof GetPackagesRequestSchema.query>
> = async (context, request, response) => {
try {
const savedObjectsClient = context.core.savedObjects.client;
const savedObjectsClient = context.fleet.epm.internalSoClient;
const res = await getPackages({
savedObjectsClient,
...request.query,
Expand All @@ -93,9 +94,9 @@ export const getListHandler: RequestHandler<
}
};

export const getLimitedListHandler: RequestHandler = async (context, request, response) => {
export const getLimitedListHandler: FleetRequestHandler = async (context, request, response) => {
try {
const savedObjectsClient = context.core.savedObjects.client;
const savedObjectsClient = context.fleet.epm.internalSoClient;
const res = await getLimitedPackages({ savedObjectsClient });
const body: GetLimitedPackagesResponse = {
response: res,
Expand All @@ -108,110 +109,105 @@ export const getLimitedListHandler: RequestHandler = async (context, request, re
}
};

export const getFileHandler: RequestHandler<TypeOf<typeof GetFileRequestSchema.params>> = async (
context,
request,
response
) => {
try {
const { pkgName, pkgVersion, filePath } = request.params;
const savedObjectsClient = context.core.savedObjects.client;
const installation = await getInstallation({ savedObjectsClient, pkgName });
const useLocalFile = pkgVersion === installation?.version;
export const getFileHandler: FleetRequestHandler<TypeOf<typeof GetFileRequestSchema.params>> =
async (context, request, response) => {
try {
const { pkgName, pkgVersion, filePath } = request.params;
const savedObjectsClient = context.fleet.epm.internalSoClient;
Comment on lines +112 to +116
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Only changes on this function are the change to FleetRequestHandler type and using internalSoClient instead of the current user one. Everything else is the same, but got re-formatted by the linter.

const installation = await getInstallation({ savedObjectsClient, pkgName });
const useLocalFile = pkgVersion === installation?.version;

if (useLocalFile) {
const assetPath = `${pkgName}-${pkgVersion}/${filePath}`;
const fileBuffer = getArchiveEntry(assetPath);
// only pull local installation if we don't have it cached
const storedAsset =
!fileBuffer && (await getAsset({ savedObjectsClient, path: assetPath }));

if (useLocalFile) {
const assetPath = `${pkgName}-${pkgVersion}/${filePath}`;
const fileBuffer = getArchiveEntry(assetPath);
// only pull local installation if we don't have it cached
const storedAsset = !fileBuffer && (await getAsset({ savedObjectsClient, path: assetPath }));
// error, if neither is available
if (!fileBuffer && !storedAsset) {
return response.custom({
body: `installed package file not found: ${filePath}`,
statusCode: 404,
});
}

// if storedAsset is not available, fileBuffer *must* be
// b/c we error if we don't have at least one, and storedAsset is the least likely
const { buffer, contentType } = storedAsset
? {
contentType: storedAsset.media_type,
buffer: storedAsset.data_utf8
? Buffer.from(storedAsset.data_utf8, 'utf8')
: Buffer.from(storedAsset.data_base64, 'base64'),
}
: {
contentType: mime.contentType(path.extname(assetPath)),
buffer: fileBuffer,
};

if (!contentType) {
return response.custom({
body: `unknown content type for file: ${filePath}`,
statusCode: 400,
});
}

// error, if neither is available
if (!fileBuffer && !storedAsset) {
return response.custom({
body: `installed package file not found: ${filePath}`,
statusCode: 404,
body: buffer,
statusCode: 200,
headers: {
'cache-control': 'max-age=10, public',
'content-type': contentType,
},
});
}

// if storedAsset is not available, fileBuffer *must* be
// b/c we error if we don't have at least one, and storedAsset is the least likely
const { buffer, contentType } = storedAsset
? {
contentType: storedAsset.media_type,
buffer: storedAsset.data_utf8
? Buffer.from(storedAsset.data_utf8, 'utf8')
: Buffer.from(storedAsset.data_base64, 'base64'),
} else {
const registryResponse = await getFile(pkgName, pkgVersion, filePath);
const headersToProxy: KnownHeaders[] = ['content-type', 'cache-control'];
const proxiedHeaders = headersToProxy.reduce((headers, knownHeader) => {
const value = registryResponse.headers.get(knownHeader);
if (value !== null) {
headers[knownHeader] = value;
}
: {
contentType: mime.contentType(path.extname(assetPath)),
buffer: fileBuffer,
};
return headers;
}, {} as ResponseHeaders);

if (!contentType) {
return response.custom({
body: `unknown content type for file: ${filePath}`,
statusCode: 400,
body: registryResponse.body,
statusCode: registryResponse.status,
headers: proxiedHeaders,
});
}

return response.custom({
body: buffer,
statusCode: 200,
headers: {
'cache-control': 'max-age=10, public',
'content-type': contentType,
},
});
} else {
const registryResponse = await getFile(pkgName, pkgVersion, filePath);
const headersToProxy: KnownHeaders[] = ['content-type', 'cache-control'];
const proxiedHeaders = headersToProxy.reduce((headers, knownHeader) => {
const value = registryResponse.headers.get(knownHeader);
if (value !== null) {
headers[knownHeader] = value;
}
return headers;
}, {} as ResponseHeaders);

return response.custom({
body: registryResponse.body,
statusCode: registryResponse.status,
headers: proxiedHeaders,
});
} catch (error) {
return defaultIngestErrorHandler({ error, response });
}
} catch (error) {
return defaultIngestErrorHandler({ error, response });
}
};
};

export const getInfoHandler: RequestHandler<TypeOf<typeof GetInfoRequestSchema.params>> = async (
context,
request,
response
) => {
try {
const { pkgkey } = request.params;
const savedObjectsClient = context.core.savedObjects.client;
// TODO: change epm API to /packageName/version so we don't need to do this
const { pkgName, pkgVersion } = splitPkgKey(pkgkey);
const res = await getPackageInfo({ savedObjectsClient, pkgName, pkgVersion });
const body: GetInfoResponse = {
response: res,
};
return response.ok({ body });
} catch (error) {
return defaultIngestErrorHandler({ error, response });
}
};
export const getInfoHandler: FleetRequestHandler<TypeOf<typeof GetInfoRequestSchema.params>> =
async (context, request, response) => {
try {
const { pkgkey } = request.params;
const savedObjectsClient = context.fleet.epm.internalSoClient;
Comment on lines +186 to +190
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same here as well

// TODO: change epm API to /packageName/version so we don't need to do this
const { pkgName, pkgVersion } = splitPkgKey(pkgkey);
const res = await getPackageInfo({ savedObjectsClient, pkgName, pkgVersion });
const body: GetInfoResponse = {
response: res,
};
return response.ok({ body });
} catch (error) {
return defaultIngestErrorHandler({ error, response });
}
};

export const updatePackageHandler: RequestHandler<
export const updatePackageHandler: FleetRequestHandler<
TypeOf<typeof UpdatePackageRequestSchema.params>,
unknown,
TypeOf<typeof UpdatePackageRequestSchema.body>
> = async (context, request, response) => {
try {
const { pkgkey } = request.params;
const savedObjectsClient = context.core.savedObjects.client;
const savedObjectsClient = context.fleet.epm.internalSoClient;

const { pkgName } = splitPkgKey(pkgkey);

Expand All @@ -226,30 +222,27 @@ export const updatePackageHandler: RequestHandler<
}
};

export const getStatsHandler: RequestHandler<TypeOf<typeof GetStatsRequestSchema.params>> = async (
context,
request,
response
) => {
try {
const { pkgName } = request.params;
const savedObjectsClient = context.core.savedObjects.client;
const body: GetStatsResponse = {
response: await getPackageUsageStats({ savedObjectsClient, pkgName }),
};
return response.ok({ body });
} catch (error) {
return defaultIngestErrorHandler({ error, response });
}
};
export const getStatsHandler: FleetRequestHandler<TypeOf<typeof GetStatsRequestSchema.params>> =
async (context, request, response) => {
try {
const { pkgName } = request.params;
const savedObjectsClient = context.fleet.epm.internalSoClient;
const body: GetStatsResponse = {
response: await getPackageUsageStats({ savedObjectsClient, pkgName }),
};
return response.ok({ body });
} catch (error) {
return defaultIngestErrorHandler({ error, response });
}
};

export const installPackageFromRegistryHandler: RequestHandler<
export const installPackageFromRegistryHandler: FleetRequestHandler<
TypeOf<typeof InstallPackageFromRegistryRequestSchema.params>,
undefined,
TypeOf<typeof InstallPackageFromRegistryRequestSchema.body>
> = async (context, request, response) => {
const savedObjectsClient = context.core.savedObjects.client;
const esClient = context.core.elasticsearch.client.asCurrentUser;
const savedObjectsClient = context.fleet.epm.internalSoClient;
const esClient = context.core.elasticsearch.client.asInternalUser;
const { pkgkey } = request.params;

const res = await installPackage({
Expand Down Expand Up @@ -284,13 +277,13 @@ const bulkInstallServiceResponseToHttpEntry = (
}
};

export const bulkInstallPackagesFromRegistryHandler: RequestHandler<
export const bulkInstallPackagesFromRegistryHandler: FleetRequestHandler<
undefined,
undefined,
TypeOf<typeof BulkUpgradePackagesFromRegistryRequestSchema.body>
> = async (context, request, response) => {
const savedObjectsClient = context.core.savedObjects.client;
const esClient = context.core.elasticsearch.client.asCurrentUser;
const savedObjectsClient = context.fleet.epm.internalSoClient;
const esClient = context.core.elasticsearch.client.asInternalUser;
const bulkInstalledResponses = await bulkInstallPackages({
savedObjectsClient,
esClient,
Expand All @@ -303,7 +296,7 @@ export const bulkInstallPackagesFromRegistryHandler: RequestHandler<
return response.ok({ body });
};

export const installPackageByUploadHandler: RequestHandler<
export const installPackageByUploadHandler: FleetRequestHandler<
undefined,
undefined,
TypeOf<typeof InstallPackageByUploadRequestSchema.body>
Expand All @@ -314,8 +307,8 @@ export const installPackageByUploadHandler: RequestHandler<
body: { message: 'Requires Enterprise license' },
});
}
const savedObjectsClient = context.core.savedObjects.client;
const esClient = context.core.elasticsearch.client.asCurrentUser;
const savedObjectsClient = context.fleet.epm.internalSoClient;
const esClient = context.core.elasticsearch.client.asInternalUser;
const contentType = request.headers['content-type'] as string; // from types it could also be string[] or undefined but this is checked later
const archiveBuffer = Buffer.from(request.body);

Expand All @@ -336,15 +329,15 @@ export const installPackageByUploadHandler: RequestHandler<
}
};

export const deletePackageHandler: RequestHandler<
export const deletePackageHandler: FleetRequestHandler<
TypeOf<typeof DeletePackageRequestSchema.params>,
undefined,
TypeOf<typeof DeletePackageRequestSchema.body>
> = async (context, request, response) => {
try {
const { pkgkey } = request.params;
const savedObjectsClient = context.core.savedObjects.client;
const esClient = context.core.elasticsearch.client.asCurrentUser;
const savedObjectsClient = context.fleet.epm.internalSoClient;
const esClient = context.core.elasticsearch.client.asInternalUser;
const res = await removeInstallation({
savedObjectsClient,
pkgkey,
Expand Down
3 changes: 2 additions & 1 deletion x-pack/plugins/fleet/server/routes/epm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import type { IRouter } from 'src/core/server';

import { PLUGIN_ID, EPM_API_ROUTES } from '../../constants';
import type { FleetRequestHandlerContext } from '../../types';
import {
GetCategoriesRequestSchema,
GetPackagesRequestSchema,
Expand Down Expand Up @@ -38,7 +39,7 @@ import {

const MAX_FILE_SIZE_BYTES = 104857600; // 100MB

export const registerRoutes = (router: IRouter) => {
export const registerRoutes = (router: IRouter<FleetRequestHandlerContext>) => {
router.get(
{
path: EPM_API_ROUTES.CATEGORIES_PATTERN,
Expand Down
Loading