mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Plugins: Add monitoring only mode to frontend sandbox (#70688)
This commit is contained in:
parent
549e04a8f1
commit
72f6793344
@ -112,6 +112,7 @@ Experimental features might be changed or removed without prior notice.
|
|||||||
| `extraThemes` | Enables extra themes |
|
| `extraThemes` | Enables extra themes |
|
||||||
| `lokiPredefinedOperations` | Adds predefined query operations to Loki query editor |
|
| `lokiPredefinedOperations` | Adds predefined query operations to Loki query editor |
|
||||||
| `pluginsFrontendSandbox` | Enables the plugins frontend sandbox |
|
| `pluginsFrontendSandbox` | Enables the plugins frontend sandbox |
|
||||||
|
| `frontendSandboxMonitorOnly` | Enables monitor only in the plugin frontend sandbox (if enabled) |
|
||||||
| `cloudWatchLogsMonacoEditor` | Enables the Monaco editor for CloudWatch Logs queries |
|
| `cloudWatchLogsMonacoEditor` | Enables the Monaco editor for CloudWatch Logs queries |
|
||||||
| `exploreScrollableLogsContainer` | Improves the scrolling behavior of logs in Explore |
|
| `exploreScrollableLogsContainer` | Improves the scrolling behavior of logs in Explore |
|
||||||
| `recordedQueriesMulti` | Enables writing multiple items from a single query within Recorded Queries |
|
| `recordedQueriesMulti` | Enables writing multiple items from a single query within Recorded Queries |
|
||||||
|
@ -98,6 +98,7 @@ export interface FeatureToggles {
|
|||||||
extraThemes?: boolean;
|
extraThemes?: boolean;
|
||||||
lokiPredefinedOperations?: boolean;
|
lokiPredefinedOperations?: boolean;
|
||||||
pluginsFrontendSandbox?: boolean;
|
pluginsFrontendSandbox?: boolean;
|
||||||
|
frontendSandboxMonitorOnly?: boolean;
|
||||||
sqlDatasourceDatabaseSelection?: boolean;
|
sqlDatasourceDatabaseSelection?: boolean;
|
||||||
cloudWatchLogsMonacoEditor?: boolean;
|
cloudWatchLogsMonacoEditor?: boolean;
|
||||||
exploreScrollableLogsContainer?: boolean;
|
exploreScrollableLogsContainer?: boolean;
|
||||||
|
@ -545,6 +545,13 @@ var (
|
|||||||
FrontendOnly: true,
|
FrontendOnly: true,
|
||||||
Owner: grafanaPluginsPlatformSquad,
|
Owner: grafanaPluginsPlatformSquad,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "frontendSandboxMonitorOnly",
|
||||||
|
Description: "Enables monitor only in the plugin frontend sandbox (if enabled)",
|
||||||
|
Stage: FeatureStageExperimental,
|
||||||
|
FrontendOnly: true,
|
||||||
|
Owner: grafanaPluginsPlatformSquad,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "sqlDatasourceDatabaseSelection",
|
Name: "sqlDatasourceDatabaseSelection",
|
||||||
Description: "Enables previous SQL data source dataset dropdown behavior",
|
Description: "Enables previous SQL data source dataset dropdown behavior",
|
||||||
|
@ -79,6 +79,7 @@ dataSourcePageHeader,preview,@grafana/enterprise-datasources,false,false,false,t
|
|||||||
extraThemes,experimental,@grafana/grafana-frontend-platform,false,false,false,true
|
extraThemes,experimental,@grafana/grafana-frontend-platform,false,false,false,true
|
||||||
lokiPredefinedOperations,experimental,@grafana/observability-logs,false,false,false,true
|
lokiPredefinedOperations,experimental,@grafana/observability-logs,false,false,false,true
|
||||||
pluginsFrontendSandbox,experimental,@grafana/plugins-platform-backend,false,false,false,true
|
pluginsFrontendSandbox,experimental,@grafana/plugins-platform-backend,false,false,false,true
|
||||||
|
frontendSandboxMonitorOnly,experimental,@grafana/plugins-platform-backend,false,false,false,true
|
||||||
sqlDatasourceDatabaseSelection,preview,@grafana/grafana-bi-squad,false,false,false,true
|
sqlDatasourceDatabaseSelection,preview,@grafana/grafana-bi-squad,false,false,false,true
|
||||||
cloudWatchLogsMonacoEditor,experimental,@grafana/aws-plugins,false,false,false,true
|
cloudWatchLogsMonacoEditor,experimental,@grafana/aws-plugins,false,false,false,true
|
||||||
exploreScrollableLogsContainer,experimental,@grafana/observability-logs,false,false,false,true
|
exploreScrollableLogsContainer,experimental,@grafana/observability-logs,false,false,false,true
|
||||||
|
|
@ -327,6 +327,10 @@ const (
|
|||||||
// Enables the plugins frontend sandbox
|
// Enables the plugins frontend sandbox
|
||||||
FlagPluginsFrontendSandbox = "pluginsFrontendSandbox"
|
FlagPluginsFrontendSandbox = "pluginsFrontendSandbox"
|
||||||
|
|
||||||
|
// FlagFrontendSandboxMonitorOnly
|
||||||
|
// Enables monitor only in the plugin frontend sandbox (if enabled)
|
||||||
|
FlagFrontendSandboxMonitorOnly = "frontendSandboxMonitorOnly"
|
||||||
|
|
||||||
// FlagSqlDatasourceDatabaseSelection
|
// FlagSqlDatasourceDatabaseSelection
|
||||||
// Enables previous SQL data source dataset dropdown behavior
|
// Enables previous SQL data source dataset dropdown behavior
|
||||||
FlagSqlDatasourceDatabaseSelection = "sqlDatasourceDatabaseSelection"
|
FlagSqlDatasourceDatabaseSelection = "sqlDatasourceDatabaseSelection"
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import { cloneDeep, isFunction } from 'lodash';
|
import { cloneDeep, isFunction } from 'lodash';
|
||||||
|
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
|
|
||||||
import { forbiddenElements } from './constants';
|
import { forbiddenElements } from './constants';
|
||||||
|
import { logWarning } from './utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Distortions are near-membrane mechanisms to altert JS instrics and DOM APIs.
|
* Distortions are near-membrane mechanisms to altert JS instrics and DOM APIs.
|
||||||
@ -53,9 +56,11 @@ import { forbiddenElements } from './constants';
|
|||||||
* The code in this file defines that generalDistortionMap.
|
* The code in this file defines that generalDistortionMap.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
type DistortionMap = Map<unknown, (originalAttrOrMethod: unknown) => unknown>;
|
type DistortionMap = Map<unknown, (originalAttrOrMethod: unknown, pluginId: string) => unknown>;
|
||||||
const generalDistortionMap: DistortionMap = new Map();
|
const generalDistortionMap: DistortionMap = new Map();
|
||||||
|
|
||||||
|
const monitorOnly = Boolean(config.featureToggles.frontendSandboxMonitorOnly);
|
||||||
|
|
||||||
export function getGeneralSandboxDistortionMap() {
|
export function getGeneralSandboxDistortionMap() {
|
||||||
if (generalDistortionMap.size === 0) {
|
if (generalDistortionMap.size === 0) {
|
||||||
// initialize the distortion map
|
// initialize the distortion map
|
||||||
@ -72,7 +77,15 @@ export function getGeneralSandboxDistortionMap() {
|
|||||||
return generalDistortionMap;
|
return generalDistortionMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
function failToSet() {
|
function failToSet(originalAttrOrMethod: unknown, pluginId: string) {
|
||||||
|
logWarning(`Plugin ${pluginId} tried to set a sandboxed property`, {
|
||||||
|
pluginId,
|
||||||
|
attrOrMethod: String(originalAttrOrMethod),
|
||||||
|
entity: 'window',
|
||||||
|
});
|
||||||
|
if (monitorOnly) {
|
||||||
|
return originalAttrOrMethod;
|
||||||
|
}
|
||||||
return () => {
|
return () => {
|
||||||
throw new Error('Plugins are not allowed to set sandboxed properties');
|
throw new Error('Plugins are not allowed to set sandboxed properties');
|
||||||
};
|
};
|
||||||
@ -85,11 +98,22 @@ function distortIframeAttributes(distortions: DistortionMap) {
|
|||||||
for (const property of iframeHtmlForbiddenProperties) {
|
for (const property of iframeHtmlForbiddenProperties) {
|
||||||
const descriptor = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, property);
|
const descriptor = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, property);
|
||||||
if (descriptor) {
|
if (descriptor) {
|
||||||
function fail() {
|
function fail(originalAttrOrMethod: unknown, pluginId: string) {
|
||||||
|
logWarning(`Plugin ${pluginId} tried to access iframe.${property}`, {
|
||||||
|
pluginId,
|
||||||
|
attrOrMethod: property,
|
||||||
|
entity: 'iframe',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (monitorOnly) {
|
||||||
|
return originalAttrOrMethod;
|
||||||
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
throw new Error('iframe.' + property + ' is not allowed in sandboxed plugins');
|
throw new Error('iframe.' + property + ' is not allowed in sandboxed plugins');
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (descriptor.value) {
|
if (descriptor.value) {
|
||||||
distortions.set(descriptor.value, fail);
|
distortions.set(descriptor.value, fail);
|
||||||
}
|
}
|
||||||
@ -107,20 +131,23 @@ function distortIframeAttributes(distortions: DistortionMap) {
|
|||||||
function distortConsole(distortions: DistortionMap) {
|
function distortConsole(distortions: DistortionMap) {
|
||||||
const descriptor = Object.getOwnPropertyDescriptor(window, 'console');
|
const descriptor = Object.getOwnPropertyDescriptor(window, 'console');
|
||||||
if (descriptor?.value) {
|
if (descriptor?.value) {
|
||||||
function sandboxLog(...args: unknown[]) {
|
function getSandboxConsole(originalAttrOrMethod: unknown, pluginId: string) {
|
||||||
console.log(`[plugin]`, ...args);
|
// we don't monitor the console because we expect a high volume of calls
|
||||||
}
|
if (monitorOnly) {
|
||||||
const sandboxConsole = {
|
return originalAttrOrMethod;
|
||||||
log: sandboxLog,
|
}
|
||||||
warn: sandboxLog,
|
|
||||||
error: sandboxLog,
|
|
||||||
info: sandboxLog,
|
|
||||||
debug: sandboxLog,
|
|
||||||
table: sandboxLog,
|
|
||||||
};
|
|
||||||
|
|
||||||
function getSandboxConsole() {
|
function sandboxLog(...args: unknown[]) {
|
||||||
return sandboxConsole;
|
console.log(`[plugin ${pluginId}]`, ...args);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
log: sandboxLog,
|
||||||
|
warn: sandboxLog,
|
||||||
|
error: sandboxLog,
|
||||||
|
info: sandboxLog,
|
||||||
|
debug: sandboxLog,
|
||||||
|
table: sandboxLog,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
distortions.set(descriptor.value, getSandboxConsole);
|
distortions.set(descriptor.value, getSandboxConsole);
|
||||||
@ -132,9 +159,19 @@ function distortConsole(distortions: DistortionMap) {
|
|||||||
|
|
||||||
// set distortions to alert to always output to the console
|
// set distortions to alert to always output to the console
|
||||||
function distortAlert(distortions: DistortionMap) {
|
function distortAlert(distortions: DistortionMap) {
|
||||||
function getAlertDistortion() {
|
function getAlertDistortion(originalAttrOrMethod: unknown, pluginId: string) {
|
||||||
|
logWarning(`Plugin ${pluginId} accessed window.alert`, {
|
||||||
|
pluginId,
|
||||||
|
attrOrMethod: 'alert',
|
||||||
|
entity: 'window',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (monitorOnly) {
|
||||||
|
return originalAttrOrMethod;
|
||||||
|
}
|
||||||
|
|
||||||
return function (...args: unknown[]) {
|
return function (...args: unknown[]) {
|
||||||
console.log(`[plugin]`, ...args);
|
console.log(`[plugin ${pluginId}]`, ...args);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const descriptor = Object.getOwnPropertyDescriptor(window, 'alert');
|
const descriptor = Object.getOwnPropertyDescriptor(window, 'alert');
|
||||||
@ -147,12 +184,22 @@ function distortAlert(distortions: DistortionMap) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function distortInnerHTML(distortions: DistortionMap) {
|
function distortInnerHTML(distortions: DistortionMap) {
|
||||||
function getInnerHTMLDistortion(originalMethod: unknown) {
|
function getInnerHTMLDistortion(originalMethod: unknown, pluginId: string) {
|
||||||
return function innerHTMLDistortion(this: HTMLElement, ...args: string[]) {
|
return function innerHTMLDistortion(this: HTMLElement, ...args: string[]) {
|
||||||
for (const arg of args) {
|
for (const arg of args) {
|
||||||
const lowerCase = arg?.toLowerCase() || '';
|
const lowerCase = arg?.toLowerCase() || '';
|
||||||
for (const forbiddenElement of forbiddenElements) {
|
for (const forbiddenElement of forbiddenElements) {
|
||||||
if (lowerCase.includes('<' + forbiddenElement)) {
|
if (lowerCase.includes('<' + forbiddenElement)) {
|
||||||
|
logWarning(`Plugin ${pluginId} tried to set ${forbiddenElement} in innerHTML`, {
|
||||||
|
pluginId,
|
||||||
|
attrOrMethod: 'innerHTML',
|
||||||
|
param: forbiddenElement,
|
||||||
|
entity: 'HTMLElement',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (monitorOnly) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
throw new Error('<' + forbiddenElement + '> is not allowed in sandboxed plugins');
|
throw new Error('<' + forbiddenElement + '> is not allowed in sandboxed plugins');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -181,10 +228,18 @@ function distortInnerHTML(distortions: DistortionMap) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function distortCreateElement(distortions: DistortionMap) {
|
function distortCreateElement(distortions: DistortionMap) {
|
||||||
function getCreateElementDistortion(originalMethod: unknown) {
|
function getCreateElementDistortion(originalMethod: unknown, pluginId: string) {
|
||||||
return function createElementDistortion(this: HTMLElement, arg?: string, options?: unknown) {
|
return function createElementDistortion(this: HTMLElement, arg?: string, options?: unknown) {
|
||||||
if (arg && forbiddenElements.includes(arg)) {
|
if (arg && forbiddenElements.includes(arg)) {
|
||||||
return document.createDocumentFragment();
|
logWarning(`Plugin ${pluginId} tried to create ${arg}`, {
|
||||||
|
pluginId,
|
||||||
|
attrOrMethod: 'createElement',
|
||||||
|
param: arg,
|
||||||
|
entity: 'document',
|
||||||
|
});
|
||||||
|
if (!monitorOnly) {
|
||||||
|
return document.createDocumentFragment();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (isFunction(originalMethod)) {
|
if (isFunction(originalMethod)) {
|
||||||
return originalMethod.apply(this, [arg, options]);
|
return originalMethod.apply(this, [arg, options]);
|
||||||
@ -198,10 +253,20 @@ function distortCreateElement(distortions: DistortionMap) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function distortInsert(distortions: DistortionMap) {
|
function distortInsert(distortions: DistortionMap) {
|
||||||
function getInsertDistortion(originalMethod: unknown) {
|
function getInsertDistortion(originalMethod: unknown, pluginId: string) {
|
||||||
return function insertChildDistortion(this: HTMLElement, node?: Node, ref?: Node) {
|
return function insertChildDistortion(this: HTMLElement, node?: Node, ref?: Node) {
|
||||||
if (node && forbiddenElements.includes(node.nodeName.toLowerCase())) {
|
const nodeType = node?.nodeName?.toLowerCase() || '';
|
||||||
return document.createDocumentFragment();
|
|
||||||
|
if (node && forbiddenElements.includes(nodeType)) {
|
||||||
|
logWarning(`Plugin ${pluginId} tried to insert ${nodeType}`, {
|
||||||
|
pluginId,
|
||||||
|
attrOrMethod: 'insertChild',
|
||||||
|
param: nodeType,
|
||||||
|
entity: 'HTMLElement',
|
||||||
|
});
|
||||||
|
if (!monitorOnly) {
|
||||||
|
return document.createDocumentFragment();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (isFunction(originalMethod)) {
|
if (isFunction(originalMethod)) {
|
||||||
return originalMethod.call(this, node, ref);
|
return originalMethod.call(this, node, ref);
|
||||||
@ -209,10 +274,20 @@ function distortInsert(distortions: DistortionMap) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getinsertAdjacentElementDistortion(originalMethod: unknown) {
|
function getinsertAdjacentElementDistortion(originalMethod: unknown, pluginId: string) {
|
||||||
return function insertAdjacentElementDistortion(this: HTMLElement, position?: string, node?: Node) {
|
return function insertAdjacentElementDistortion(this: HTMLElement, position?: string, node?: Node) {
|
||||||
if (node && forbiddenElements.includes(node.nodeName.toLowerCase())) {
|
const nodeType = node?.nodeName?.toLowerCase() || '';
|
||||||
return document.createDocumentFragment();
|
if (node && forbiddenElements.includes(nodeType)) {
|
||||||
|
logWarning(`Plugin ${pluginId} tried to insert ${nodeType}`, {
|
||||||
|
pluginId,
|
||||||
|
attrOrMethod: 'insertAdjacentElement',
|
||||||
|
param: nodeType,
|
||||||
|
entity: 'HTMLElement',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!monitorOnly) {
|
||||||
|
return document.createDocumentFragment();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (isFunction(originalMethod)) {
|
if (isFunction(originalMethod)) {
|
||||||
return originalMethod.call(this, position, node);
|
return originalMethod.call(this, position, node);
|
||||||
@ -240,9 +315,23 @@ function distortInsert(distortions: DistortionMap) {
|
|||||||
// set distortions to append elements to the document
|
// set distortions to append elements to the document
|
||||||
function distortAppend(distortions: DistortionMap) {
|
function distortAppend(distortions: DistortionMap) {
|
||||||
// append accepts an array of nodes to append https://developer.mozilla.org/en-US/docs/Web/API/Node/append
|
// append accepts an array of nodes to append https://developer.mozilla.org/en-US/docs/Web/API/Node/append
|
||||||
function getAppendDistortion(originalMethod: unknown) {
|
function getAppendDistortion(originalMethod: unknown, pluginId: string) {
|
||||||
return function appendDistortion(this: HTMLElement, ...args: Node[]) {
|
return function appendDistortion(this: HTMLElement, ...args: Node[]) {
|
||||||
const acceptedNodes = args?.filter((node) => !forbiddenElements.includes(node.nodeName.toLowerCase()));
|
let acceptedNodes = args;
|
||||||
|
const filteredAcceptedNodes = args?.filter((node) => !forbiddenElements.includes(node.nodeName.toLowerCase()));
|
||||||
|
if (!monitorOnly) {
|
||||||
|
acceptedNodes = filteredAcceptedNodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (acceptedNodes.length !== filteredAcceptedNodes.length) {
|
||||||
|
logWarning(`Plugin ${pluginId} tried to append fobiddenElements`, {
|
||||||
|
pluginId,
|
||||||
|
attrOrMethod: 'append',
|
||||||
|
param: args?.filter((node) => forbiddenElements.includes(node.nodeName.toLowerCase()))?.join(',') || '',
|
||||||
|
entity: 'HTMLElement',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (isFunction(originalMethod)) {
|
if (isFunction(originalMethod)) {
|
||||||
originalMethod.apply(this, acceptedNodes);
|
originalMethod.apply(this, acceptedNodes);
|
||||||
}
|
}
|
||||||
@ -252,10 +341,20 @@ function distortAppend(distortions: DistortionMap) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// appendChild accepts a single node to add https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild
|
// appendChild accepts a single node to add https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild
|
||||||
function getAppendChildDistortion(originalMethod: unknown) {
|
function getAppendChildDistortion(originalMethod: unknown, pluginId: string) {
|
||||||
return function appendChildDistortion(this: HTMLElement, arg?: Node) {
|
return function appendChildDistortion(this: HTMLElement, arg?: Node) {
|
||||||
if (arg && forbiddenElements.includes(arg.nodeName.toLowerCase())) {
|
const nodeType = arg?.nodeName?.toLowerCase() || '';
|
||||||
return document.createDocumentFragment();
|
if (arg && forbiddenElements.includes(nodeType)) {
|
||||||
|
logWarning(`Plugin ${pluginId} tried to append ${nodeType}`, {
|
||||||
|
pluginId,
|
||||||
|
attrOrMethod: 'appendChild',
|
||||||
|
param: nodeType,
|
||||||
|
entity: 'HTMLElement',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!monitorOnly) {
|
||||||
|
return document.createDocumentFragment();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (isFunction(originalMethod)) {
|
if (isFunction(originalMethod)) {
|
||||||
return originalMethod.call(this, arg);
|
return originalMethod.call(this, arg);
|
||||||
@ -284,6 +383,7 @@ function distortAppend(distortions: DistortionMap) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this is not a distortion for security reasons but to make plugins using web workers work correctly.
|
||||||
function distortWorkers(distortions: DistortionMap) {
|
function distortWorkers(distortions: DistortionMap) {
|
||||||
const descriptor = Object.getOwnPropertyDescriptor(Worker.prototype, 'postMessage');
|
const descriptor = Object.getOwnPropertyDescriptor(Worker.prototype, 'postMessage');
|
||||||
function getPostMessageDistortion(originalMethod: unknown) {
|
function getPostMessageDistortion(originalMethod: unknown) {
|
||||||
@ -306,6 +406,7 @@ function distortWorkers(distortions: DistortionMap) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this is not a distortion for security reasons but to make plugins using document.defaultView work correctly.
|
||||||
function distortDocument(distortions: DistortionMap) {
|
function distortDocument(distortions: DistortionMap) {
|
||||||
const descriptor = Object.getOwnPropertyDescriptor(Document.prototype, 'defaultView');
|
const descriptor = Object.getOwnPropertyDescriptor(Document.prototype, 'defaultView');
|
||||||
if (descriptor?.get) {
|
if (descriptor?.get) {
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
import { isNearMembraneProxy, ProxyTarget } from '@locker/near-membrane-shared';
|
import { isNearMembraneProxy, ProxyTarget } from '@locker/near-membrane-shared';
|
||||||
|
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
|
|
||||||
import { forbiddenElements } from './constants';
|
import { forbiddenElements } from './constants';
|
||||||
import { isReactClassComponent } from './utils';
|
import { isReactClassComponent, logWarning } from './utils';
|
||||||
|
|
||||||
// IMPORTANT: NEVER export this symbol from a public (e.g `@grafana/*`) package
|
// IMPORTANT: NEVER export this symbol from a public (e.g `@grafana/*`) package
|
||||||
const SANDBOX_LIVE_VALUE = Symbol.for('@@SANDBOX_LIVE_VALUE');
|
const SANDBOX_LIVE_VALUE = Symbol.for('@@SANDBOX_LIVE_VALUE');
|
||||||
|
const monitorOnly = Boolean(config.featureToggles.frontendSandboxMonitorOnly);
|
||||||
|
|
||||||
export function getSafeSandboxDomElement(element: Element, pluginId: string): Element {
|
export function getSafeSandboxDomElement(element: Element, pluginId: string): Element {
|
||||||
const nodeName = Reflect.get(element, 'nodeName');
|
const nodeName = Reflect.get(element, 'nodeName');
|
||||||
@ -26,7 +29,14 @@ export function getSafeSandboxDomElement(element: Element, pluginId: string): El
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (forbiddenElements.includes(nodeName)) {
|
if (forbiddenElements.includes(nodeName)) {
|
||||||
throw new Error('<' + nodeName + '> is not allowed in sandboxed plugins');
|
logWarning('<' + nodeName + '> is not allowed in sandboxed plugins', {
|
||||||
|
pluginId,
|
||||||
|
param: nodeName,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!monitorOnly) {
|
||||||
|
throw new Error('<' + nodeName + '> is not allowed in sandboxed plugins');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// allow elements inside the sandbox or the sandbox body
|
// allow elements inside the sandbox or the sandbox body
|
||||||
@ -38,10 +48,15 @@ export function getSafeSandboxDomElement(element: Element, pluginId: string): El
|
|||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
// any other element gets a mock
|
if (!monitorOnly) {
|
||||||
const mockElement = document.createElement(nodeName);
|
// any other element gets a mock
|
||||||
mockElement.dataset.grafanaPluginSandboxElement = 'true';
|
const mockElement = document.createElement(nodeName);
|
||||||
return mockElement;
|
mockElement.dataset.grafanaPluginSandboxElement = 'true';
|
||||||
|
// we are not logging this because a high number of warnings can be generated
|
||||||
|
return mockElement;
|
||||||
|
} else {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isDomElement(obj: unknown): obj is Element {
|
export function isDomElement(obj: unknown): obj is Element {
|
||||||
|
@ -18,6 +18,7 @@ import {
|
|||||||
import { sandboxPluginDependencies } from './plugin_dependencies';
|
import { sandboxPluginDependencies } from './plugin_dependencies';
|
||||||
import { sandboxPluginComponents } from './sandbox_components';
|
import { sandboxPluginComponents } from './sandbox_components';
|
||||||
import { CompartmentDependencyModule, PluginFactoryFunction } from './types';
|
import { CompartmentDependencyModule, PluginFactoryFunction } from './types';
|
||||||
|
import { logError } from './utils';
|
||||||
|
|
||||||
// Loads near membrane custom formatter for near membrane proxy objects.
|
// Loads near membrane custom formatter for near membrane proxy objects.
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
@ -34,7 +35,12 @@ export async function importPluginModuleInSandbox({ pluginId }: { pluginId: stri
|
|||||||
}
|
}
|
||||||
return pluginImportCache.get(pluginId);
|
return pluginImportCache.get(pluginId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`Could not import plugin ${pluginId} inside sandbox: ` + e);
|
const error = new Error(`Could not import plugin ${pluginId} inside sandbox: ` + e);
|
||||||
|
logError(error, {
|
||||||
|
pluginId,
|
||||||
|
error: String(e),
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +62,7 @@ async function doImportPluginModuleInSandbox(meta: PluginMeta): Promise<unknown>
|
|||||||
}
|
}
|
||||||
const distortion = generalDistortionMap.get(originalValue);
|
const distortion = generalDistortionMap.get(originalValue);
|
||||||
if (distortion) {
|
if (distortion) {
|
||||||
return distortion(originalValue) as ProxyTarget;
|
return distortion(originalValue, meta.id) as ProxyTarget;
|
||||||
}
|
}
|
||||||
return originalValue;
|
return originalValue;
|
||||||
}
|
}
|
||||||
@ -100,7 +106,12 @@ async function doImportPluginModuleInSandbox(meta: PluginMeta): Promise<unknown>
|
|||||||
const pluginExports = await sandboxPluginComponents(pluginExportsRaw, meta);
|
const pluginExports = await sandboxPluginComponents(pluginExportsRaw, meta);
|
||||||
resolve(pluginExports);
|
resolve(pluginExports);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(new Error(`Could not execute plugin ${meta.id}: ` + e));
|
const error = new Error(`Could not execute plugin's define ${meta.id}: ` + e);
|
||||||
|
logError(error, {
|
||||||
|
pluginId: meta.id,
|
||||||
|
error: String(e),
|
||||||
|
});
|
||||||
|
reject(error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@ -137,7 +148,12 @@ async function doImportPluginModuleInSandbox(meta: PluginMeta): Promise<unknown>
|
|||||||
// of endowments.
|
// of endowments.
|
||||||
sandboxEnvironment.evaluate(pluginCode);
|
sandboxEnvironment.evaluate(pluginCode);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(new Error(`Could not execute plugin ${meta.id}: ` + e));
|
const error = new Error(`Could not run plugin ${meta.id} inside sandbox: ` + e);
|
||||||
|
logError(error, {
|
||||||
|
pluginId: meta.id,
|
||||||
|
error: String(e),
|
||||||
|
});
|
||||||
|
reject(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import { LogContext } from '@grafana/faro-web-sdk';
|
||||||
|
import { logWarning as logWarningRuntime, logError as logErrorRuntime, config } from '@grafana/runtime';
|
||||||
|
|
||||||
import { SandboxedPluginObject } from './types';
|
import { SandboxedPluginObject } from './types';
|
||||||
|
|
||||||
|
const monitorOnly = Boolean(config.featureToggles.frontendSandboxMonitorOnly);
|
||||||
|
|
||||||
export function isSandboxedPluginObject(value: unknown): value is SandboxedPluginObject {
|
export function isSandboxedPluginObject(value: unknown): value is SandboxedPluginObject {
|
||||||
return !!value && typeof value === 'object' && value?.hasOwnProperty('plugin');
|
return !!value && typeof value === 'object' && value?.hasOwnProperty('plugin');
|
||||||
}
|
}
|
||||||
@ -13,3 +18,21 @@ export function assertNever(x: never): never {
|
|||||||
export function isReactClassComponent(obj: unknown): obj is React.Component {
|
export function isReactClassComponent(obj: unknown): obj is React.Component {
|
||||||
return obj instanceof React.Component;
|
return obj instanceof React.Component;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function logWarning(message: string, context?: LogContext) {
|
||||||
|
context = {
|
||||||
|
...context,
|
||||||
|
source: 'sandbox',
|
||||||
|
monitorOnly: String(monitorOnly),
|
||||||
|
};
|
||||||
|
logWarningRuntime(message, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logError(error: Error, context?: LogContext) {
|
||||||
|
context = {
|
||||||
|
...context,
|
||||||
|
source: 'sandbox',
|
||||||
|
monitorOnly: String(monitorOnly),
|
||||||
|
};
|
||||||
|
logErrorRuntime(error, context);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user