Plugins: Expose core APIs only for certain plugins (#107967)

* feat(plugins): add a way to expose core apis only to certain plugins

* review: update naming

* review: update the owners of the feature toggle

* feat: share the restricted apis with extensions

* fix: linters

* feat: remove the `addPanel` api

* chore: fix linting and betterer issue

* tests: use `@ts-expect-error` for more clarity
This commit is contained in:
Levente Balogh
2025-09-01 11:57:00 +02:00
committed by GitHub
parent da43e2ae07
commit d31e682345
20 changed files with 504 additions and 43 deletions
+21 -19
View File
@@ -207,25 +207,27 @@ type FrontendSettingsDTO struct {
DashboardPerformanceMetrics []string `json:"dashboardPerformanceMetrics"`
PanelSeriesLimit int `json:"panelSeriesLimit"`
FeedbackLinksEnabled bool `json:"feedbackLinksEnabled"`
ApplicationInsightsConnectionString string `json:"applicationInsightsConnectionString"`
ApplicationInsightsEndpointUrl string `json:"applicationInsightsEndpointUrl"`
DisableLoginForm bool `json:"disableLoginForm"`
DisableUserSignUp bool `json:"disableUserSignUp"`
LoginHint string `json:"loginHint"`
PasswordHint string `json:"passwordHint"`
ExternalUserMngInfo string `json:"externalUserMngInfo"`
ExternalUserMngLinkUrl string `json:"externalUserMngLinkUrl"`
ExternalUserMngLinkName string `json:"externalUserMngLinkName"`
ExternalUserMngAnalytics bool `json:"externalUserMngAnalytics"`
ExternalUserMngAnalyticsParams string `json:"externalUserMngAnalyticsParams"`
ViewersCanEdit bool `json:"viewersCanEdit"`
DisableSanitizeHtml bool `json:"disableSanitizeHtml"`
TrustedTypesDefaultPolicyEnabled bool `json:"trustedTypesDefaultPolicyEnabled"`
CSPReportOnlyEnabled bool `json:"cspReportOnlyEnabled"`
EnableFrontendSandboxForPlugins []string `json:"enableFrontendSandboxForPlugins"`
ExploreDefaultTimeOffset string `json:"exploreDefaultTimeOffset"`
ExploreHideLogsDownload bool `json:"exploreHideLogsDownload"`
FeedbackLinksEnabled bool `json:"feedbackLinksEnabled"`
ApplicationInsightsConnectionString string `json:"applicationInsightsConnectionString"`
ApplicationInsightsEndpointUrl string `json:"applicationInsightsEndpointUrl"`
DisableLoginForm bool `json:"disableLoginForm"`
DisableUserSignUp bool `json:"disableUserSignUp"`
LoginHint string `json:"loginHint"`
PasswordHint string `json:"passwordHint"`
ExternalUserMngInfo string `json:"externalUserMngInfo"`
ExternalUserMngLinkUrl string `json:"externalUserMngLinkUrl"`
ExternalUserMngLinkName string `json:"externalUserMngLinkName"`
ExternalUserMngAnalytics bool `json:"externalUserMngAnalytics"`
ExternalUserMngAnalyticsParams string `json:"externalUserMngAnalyticsParams"`
ViewersCanEdit bool `json:"viewersCanEdit"`
DisableSanitizeHtml bool `json:"disableSanitizeHtml"`
TrustedTypesDefaultPolicyEnabled bool `json:"trustedTypesDefaultPolicyEnabled"`
CSPReportOnlyEnabled bool `json:"cspReportOnlyEnabled"`
EnableFrontendSandboxForPlugins []string `json:"enableFrontendSandboxForPlugins"`
PluginRestrictedAPIsAllowList map[string][]string `json:"pluginRestrictedAPIsAllowList"`
PluginRestrictedAPIsBlockList map[string][]string `json:"pluginRestrictedAPIsBlockList"`
ExploreDefaultTimeOffset string `json:"exploreDefaultTimeOffset"`
ExploreHideLogsDownload bool `json:"exploreHideLogsDownload"`
Auth FrontendSettingsAuthDTO `json:"auth"`
+2
View File
@@ -250,6 +250,8 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
QuickRanges: hs.Cfg.QuickRanges,
SecureSocksDSProxyEnabled: hs.Cfg.SecureSocksDSProxy.Enabled && hs.Cfg.SecureSocksDSProxy.ShowUI,
EnableFrontendSandboxForPlugins: hs.Cfg.EnableFrontendSandboxForPlugins,
PluginRestrictedAPIsAllowList: hs.Cfg.PluginRestrictedAPIsAllowList,
PluginRestrictedAPIsBlockList: hs.Cfg.PluginRestrictedAPIsBlockList,
PublicDashboardAccessToken: c.PublicDashboardAccessToken,
PublicDashboardsEnabled: hs.Cfg.PublicDashboardsEnabled,
CloudMigrationIsTarget: isCloudMigrationTarget,
+9
View File
@@ -1869,6 +1869,15 @@ var (
Owner: grafanaAlertingSquad,
HideFromAdminPage: true,
HideFromDocs: true,
},
{
Name: "restrictedPluginApis",
Description: "Enables sharing a list of APIs with a list of plugins",
Stage: FeatureStageExperimental,
Owner: grafanaPluginsPlatformSquad,
HideFromAdminPage: true,
HideFromDocs: true,
FrontendOnly: true,
Expression: "false",
},
{
+1
View File
@@ -241,6 +241,7 @@ unifiedStorageSearchDualReaderEnabled,experimental,@grafana/search-and-storage,f
dashboardDsAdHocFiltering,experimental,@grafana/datapro,false,false,true
dashboardLevelTimeMacros,experimental,@grafana/dashboards-squad,false,false,true
alertmanagerRemoteSecondaryWithRemoteState,experimental,@grafana/alerting-squad,false,false,false
restrictedPluginApis,experimental,@grafana/plugins-platform-backend,false,false,true
adhocFiltersInTooltips,experimental,@grafana/datapro,false,false,true
favoriteDatasources,experimental,@grafana/plugins-platform-backend,false,false,true
newLogContext,experimental,@grafana/observability-logs,false,false,true
1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
241 dashboardDsAdHocFiltering experimental @grafana/datapro false false true
242 dashboardLevelTimeMacros experimental @grafana/dashboards-squad false false true
243 alertmanagerRemoteSecondaryWithRemoteState experimental @grafana/alerting-squad false false false
244 restrictedPluginApis experimental @grafana/plugins-platform-backend false false true
245 adhocFiltersInTooltips experimental @grafana/datapro false false true
246 favoriteDatasources experimental @grafana/plugins-platform-backend false false true
247 newLogContext experimental @grafana/observability-logs false false true
+4
View File
@@ -975,6 +975,10 @@ const (
// Starts Grafana in remote secondary mode pulling the latest state from the remote Alertmanager to avoid duplicate notifications.
FlagAlertmanagerRemoteSecondaryWithRemoteState = "alertmanagerRemoteSecondaryWithRemoteState"
// FlagRestrictedPluginApis
// Enables sharing a list of APIs with a list of plugins
FlagRestrictedPluginApis = "restrictedPluginApis"
// FlagAdhocFiltersInTooltips
// Enable adhoc filter buttons in visualization tooltips
FlagAdhocFiltersInTooltips = "adhocFiltersInTooltips"
+25 -4
View File
@@ -572,16 +572,18 @@
{
"metadata": {
"name": "alertmanagerRemoteSecondaryWithRemoteState",
"resourceVersion": "1753448760331",
"creationTimestamp": "2025-07-25T13:06:00Z"
"resourceVersion": "1753776005753",
"creationTimestamp": "2025-07-25T13:06:00Z",
"annotations": {
"grafana.app/updatedTimestamp": "2025-07-29 08:00:05.753498 +0000 UTC"
}
},
"spec": {
"description": "Starts Grafana in remote secondary mode pulling the latest state from the remote Alertmanager to avoid duplicate notifications.",
"stage": "experimental",
"codeowner": "@grafana/alerting-squad",
"hideFromAdminPage": true,
"hideFromDocs": true,
"expression": "false"
"hideFromDocs": true
}
},
{
@@ -2889,6 +2891,25 @@
"expression": "false"
}
},
{
"metadata": {
"name": "restrictedPluginApis",
"resourceVersion": "1753776783657",
"creationTimestamp": "2025-07-25T07:46:26Z",
"annotations": {
"grafana.app/updatedTimestamp": "2025-07-29 08:13:03.657209 +0000 UTC"
}
},
"spec": {
"description": "Enables sharing a list of APIs with a list of plugins",
"stage": "experimental",
"codeowner": "@grafana/plugins-platform-backend",
"frontend": true,
"hideFromAdminPage": true,
"hideFromDocs": true,
"expression": "false"
}
},
{
"metadata": {
"name": "rolePickerDrawer",
+8
View File
@@ -214,6 +214,10 @@ type Cfg struct {
PluginUpdateStrategy string
// Plugin API restrictions - maps API name to list of plugin IDs/patterns
PluginRestrictedAPIsAllowList map[string][]string
PluginRestrictedAPIsBlockList map[string][]string
// Panels
DisableSanitizeHtml bool
@@ -1057,6 +1061,10 @@ func NewCfg() *Cfg {
Raw: ini.Empty(),
Azure: &azsettings.AzureSettings{},
// Initialize plugin API restriction maps
PluginRestrictedAPIsAllowList: make(map[string][]string),
PluginRestrictedAPIsBlockList: make(map[string][]string),
// Avoid nil pointer
IsFeatureToggleEnabled: func(_ string) bool {
return false
+24
View File
@@ -110,6 +110,26 @@ func (cfg *Cfg) processPreinstallPlugins(rawInstallPlugins []string, preinstallP
}
}
// readPluginAPIRestrictionsSection reads a plugin API restrictions section and returns a map of API names to plugin lists
func readPluginAPIRestrictionsSection(iniFile *ini.File, sectionName string) map[string][]string {
result := make(map[string][]string)
if !iniFile.HasSection(sectionName) {
return result
}
section := iniFile.Section(sectionName)
for _, key := range section.Keys() {
apiName := key.Name()
pluginList := util.SplitString(key.MustString(""))
if len(pluginList) > 0 {
result[apiName] = pluginList
}
}
return result
}
func (cfg *Cfg) readPluginSettings(iniFile *ini.File) error {
pluginsSection := iniFile.Section("plugins")
@@ -179,5 +199,9 @@ func (cfg *Cfg) readPluginSettings(iniFile *ini.File) error {
cfg.PluginUpdateStrategy = pluginsSection.Key("update_strategy").In(PluginUpdateStrategyLatest, []string{PluginUpdateStrategyLatest, PluginUpdateStrategyMinor})
// Plugin API restrictions - read from sections
cfg.PluginRestrictedAPIsAllowList = readPluginAPIRestrictionsSection(iniFile, "plugins.restricted_apis_allowlist")
cfg.PluginRestrictedAPIsBlockList = readPluginAPIRestrictionsSection(iniFile, "plugins.restricted_apis_blocklist")
return nil
}