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:
@@ -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"`
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user