Files
grafana/public/app/features/plugins/pluginPreloader.ts
Giuseppe Guerra 0db65d229e Plugins: Add Subresource Integrity checks (#93024)
* Plugins: Pass hashes for SRI to frontend

* Add SRI hashes to frontendsettings DTOs

* Add docstring

* TestSriHashes

* Fix typo

* Changed SriHashes to ModuleHash

* update loader_test compareOpts

* update ModuleHash error message

* Add TestModuleHash/no_module.js

* Add omitEmpty to moduleHash

* Add ModuleHash to api/plugins/${pluginId}/settings

* moved ModuleHash field

* feat(plugins): add moduleHash to bootData and plugin types

* feat(plugins): if moduleHash is available apply it to systemjs importmap

* Calculate ModuleHash for CDN provisioned plugins

* Add ModuleHash tests for TestCalculate

* adjust test case name

* removed .envrc

* Fix signature verification failing for internal plugins

* fix tests

* Add pluginsFilesystemSriChecks feature togglemk

* renamed FilesystemSriChecksEnabled

* refactor(plugin_loader): prefer extending type declaration over ts-error

* added a couple more tests

* Removed unused features

* Removed unused argument from signature.DefaultCalculator call

* Removed unused argument from bootstrap.DefaultConstructFunc

* Moved ModuleHash to pluginassets service

* update docstring

* lint

* Removed cdn dependency from manifest.Signature

* add tests

* fix extra parameters in tests

* "fix" tests

* removed outdated test

* removed unused cdn dependency in signature.DefaultCalculator

* reduce diff

* Cache returned values

* Add support for deeply nested plugins (more than 1 hierarchy level)

* simplify cache usage

* refactor TestService_ModuleHash_Cache

* removed unused testdata

* re-generate feature toggles

* use version for module hash cache

* Renamed feature toggle to pluginsSriChecks and use it for both cdn and filesystem

* Removed app/types/system-integrity.d.ts

* re-generate feature toggles

* re-generate feature toggles

* feat(plugins): put systemjs integrity hash behind feature flag

---------

Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com>
2024-10-04 14:55:09 +02:00

82 lines
3.0 KiB
TypeScript

import type { PluginExtensionAddedLinkConfig, PluginExtensionExposedComponentConfig } from '@grafana/data';
import { PluginExtensionAddedComponentConfig } from '@grafana/data/src/types/pluginExtensions';
import type { AppPluginConfig } from '@grafana/runtime';
import { startMeasure, stopMeasure } from 'app/core/utils/metrics';
import { getPluginSettings } from 'app/features/plugins/pluginSettings';
import { PluginExtensionRegistries } from './extensions/registry/types';
import { importPluginModule } from './plugin_loader';
export type PluginPreloadResult = {
pluginId: string;
error?: unknown;
exposedComponentConfigs: PluginExtensionExposedComponentConfig[];
addedComponentConfigs?: PluginExtensionAddedComponentConfig[];
addedLinkConfigs?: PluginExtensionAddedLinkConfig[];
};
export async function preloadPlugins(
apps: AppPluginConfig[] = [],
registries: PluginExtensionRegistries,
eventName = 'frontend_plugins_preload'
) {
startMeasure(eventName);
const promises = apps.filter((config) => config.preload).map((config) => preload(config));
const preloadedPlugins = await Promise.all(promises);
for (const preloadedPlugin of preloadedPlugins) {
if (preloadedPlugin.error) {
console.error(`[Plugins] Skip loading extensions for "${preloadedPlugin.pluginId}" due to an error.`);
continue;
}
registries.exposedComponentsRegistry.register({
pluginId: preloadedPlugin.pluginId,
configs: preloadedPlugin.exposedComponentConfigs,
});
registries.addedComponentsRegistry.register({
pluginId: preloadedPlugin.pluginId,
configs: preloadedPlugin.addedComponentConfigs || [],
});
registries.addedLinksRegistry.register({
pluginId: preloadedPlugin.pluginId,
configs: preloadedPlugin.addedLinkConfigs || [],
});
}
stopMeasure(eventName);
}
async function preload(config: AppPluginConfig): Promise<PluginPreloadResult> {
const { path, version, id: pluginId, loadingStrategy } = config;
try {
startMeasure(`frontend_plugin_preload_${pluginId}`);
const { plugin } = await importPluginModule({
path,
version,
isAngular: config.angular.detected,
pluginId,
loadingStrategy,
moduleHash: config.moduleHash,
});
const { exposedComponentConfigs = [], addedComponentConfigs = [], addedLinkConfigs = [] } = plugin;
// Fetching meta-information for the preloaded app plugin and caching it for later.
// (The function below returns a promise, but it's not awaited for a reason: we don't want to block the preload process, we would only like to cache the result for later.)
getPluginSettings(pluginId);
return { pluginId, exposedComponentConfigs, addedComponentConfigs, addedLinkConfigs };
} catch (error) {
console.error(`[Plugins] Failed to preload plugin: ${path} (version: ${version})`, error);
return {
pluginId,
error,
exposedComponentConfigs: [],
addedComponentConfigs: [],
addedLinkConfigs: [],
};
} finally {
stopMeasure(`frontend_plugin_preload_${pluginId}`);
}
}