mirror of https://github.com/grafana/grafana.git
Advisor: Check plugin signature (#106044)
This commit is contained in:
parent
27ab895eef
commit
e2e8de29ba
|
|
@ -28,6 +28,7 @@ type Service struct {
|
||||||
pluginContextProvider *plugincontext.Provider
|
pluginContextProvider *plugincontext.Provider
|
||||||
pluginClient plugins.Client
|
pluginClient plugins.Client
|
||||||
pluginRepo repo.Service
|
pluginRepo repo.Service
|
||||||
|
pluginErrorResolver plugins.ErrorResolver
|
||||||
updateChecker pluginchecker.PluginUpdateChecker
|
updateChecker pluginchecker.PluginUpdateChecker
|
||||||
pluginPreinstall pluginchecker.Preinstall
|
pluginPreinstall pluginchecker.Preinstall
|
||||||
managedPlugins managedplugins.Manager
|
managedPlugins managedplugins.Manager
|
||||||
|
|
@ -42,6 +43,7 @@ func ProvideService(datasourceSvc datasources.DataSourceService, pluginStore plu
|
||||||
updateChecker pluginchecker.PluginUpdateChecker,
|
updateChecker pluginchecker.PluginUpdateChecker,
|
||||||
pluginRepo repo.Service, pluginPreinstall pluginchecker.Preinstall, managedPlugins managedplugins.Manager,
|
pluginRepo repo.Service, pluginPreinstall pluginchecker.Preinstall, managedPlugins managedplugins.Manager,
|
||||||
provisionedPlugins provisionedplugins.Manager, ssoSettingsSvc ssosettings.Service, cfg *setting.Cfg,
|
provisionedPlugins provisionedplugins.Manager, ssoSettingsSvc ssosettings.Service, cfg *setting.Cfg,
|
||||||
|
pluginErrorResolver plugins.ErrorResolver,
|
||||||
) *Service {
|
) *Service {
|
||||||
return &Service{
|
return &Service{
|
||||||
datasourceSvc: datasourceSvc,
|
datasourceSvc: datasourceSvc,
|
||||||
|
|
@ -49,6 +51,7 @@ func ProvideService(datasourceSvc datasources.DataSourceService, pluginStore plu
|
||||||
pluginContextProvider: pluginContextProvider,
|
pluginContextProvider: pluginContextProvider,
|
||||||
pluginClient: pluginClient,
|
pluginClient: pluginClient,
|
||||||
pluginRepo: pluginRepo,
|
pluginRepo: pluginRepo,
|
||||||
|
pluginErrorResolver: pluginErrorResolver,
|
||||||
updateChecker: updateChecker,
|
updateChecker: updateChecker,
|
||||||
pluginPreinstall: pluginPreinstall,
|
pluginPreinstall: pluginPreinstall,
|
||||||
managedPlugins: managedPlugins,
|
managedPlugins: managedPlugins,
|
||||||
|
|
@ -73,6 +76,7 @@ func (s *Service) Checks() []checks.Check {
|
||||||
s.pluginStore,
|
s.pluginStore,
|
||||||
s.pluginRepo,
|
s.pluginRepo,
|
||||||
s.updateChecker,
|
s.updateChecker,
|
||||||
|
s.pluginErrorResolver,
|
||||||
s.GrafanaVersion,
|
s.GrafanaVersion,
|
||||||
),
|
),
|
||||||
authchecks.New(s.ssoSettingsSvc),
|
authchecks.New(s.ssoSettingsSvc),
|
||||||
|
|
|
||||||
|
|
@ -2,43 +2,42 @@ package plugincheck
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
sysruntime "runtime"
|
sysruntime "runtime"
|
||||||
|
|
||||||
"github.com/grafana/grafana-app-sdk/logging"
|
|
||||||
advisor "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1"
|
|
||||||
"github.com/grafana/grafana/apps/advisor/pkg/app/checks"
|
"github.com/grafana/grafana/apps/advisor/pkg/app/checks"
|
||||||
|
"github.com/grafana/grafana/pkg/plugins"
|
||||||
"github.com/grafana/grafana/pkg/plugins/repo"
|
"github.com/grafana/grafana/pkg/plugins/repo"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginchecker"
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginchecker"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
CheckID = "plugin"
|
CheckID = "plugin"
|
||||||
DeprecationStepID = "deprecation"
|
|
||||||
UpdateStepID = "update"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(
|
func New(
|
||||||
pluginStore pluginstore.Store,
|
pluginStore pluginstore.Store,
|
||||||
pluginRepo repo.Service,
|
pluginRepo repo.Service,
|
||||||
updateChecker pluginchecker.PluginUpdateChecker,
|
updateChecker pluginchecker.PluginUpdateChecker,
|
||||||
|
pluginErrorResolver plugins.ErrorResolver,
|
||||||
grafanaVersion string,
|
grafanaVersion string,
|
||||||
) checks.Check {
|
) checks.Check {
|
||||||
return &check{
|
return &check{
|
||||||
PluginStore: pluginStore,
|
PluginStore: pluginStore,
|
||||||
PluginRepo: pluginRepo,
|
PluginRepo: pluginRepo,
|
||||||
GrafanaVersion: grafanaVersion,
|
GrafanaVersion: grafanaVersion,
|
||||||
updateChecker: updateChecker,
|
updateChecker: updateChecker,
|
||||||
|
pluginErrorResolver: pluginErrorResolver,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type check struct {
|
type check struct {
|
||||||
PluginStore pluginstore.Store
|
PluginStore pluginstore.Store
|
||||||
PluginRepo repo.Service
|
PluginRepo repo.Service
|
||||||
updateChecker pluginchecker.PluginUpdateChecker
|
updateChecker pluginchecker.PluginUpdateChecker
|
||||||
GrafanaVersion string
|
pluginErrorResolver plugins.ErrorResolver
|
||||||
pluginIndex map[string]repo.PluginInfo
|
GrafanaVersion string
|
||||||
|
pluginIndex map[string]repo.PluginInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *check) ID() string {
|
func (c *check) ID() string {
|
||||||
|
|
@ -49,12 +48,40 @@ func (c *check) Name() string {
|
||||||
return "plugin"
|
return "plugin"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type pluginItem struct {
|
||||||
|
Plugin *pluginstore.Plugin
|
||||||
|
Err *plugins.Error
|
||||||
|
}
|
||||||
|
|
||||||
func (c *check) Items(ctx context.Context) ([]any, error) {
|
func (c *check) Items(ctx context.Context) ([]any, error) {
|
||||||
ps := c.PluginStore.Plugins(ctx)
|
ps := c.PluginStore.Plugins(ctx)
|
||||||
res := make([]any, len(ps))
|
resMap := map[string]*pluginItem{}
|
||||||
for i, p := range ps {
|
for _, p := range ps {
|
||||||
res[i] = p
|
resMap[p.ID] = &pluginItem{
|
||||||
|
Plugin: &p,
|
||||||
|
Err: c.pluginErrorResolver.PluginError(ctx, p.ID),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Plugins with errors are not added to the plugin store but
|
||||||
|
// we still want to show them in the check results so we add them to the map
|
||||||
|
pluginErrors := c.pluginErrorResolver.PluginErrors(ctx)
|
||||||
|
for _, e := range pluginErrors {
|
||||||
|
if _, exists := resMap[e.PluginID]; exists {
|
||||||
|
resMap[e.PluginID].Err = e
|
||||||
|
} else {
|
||||||
|
resMap[e.PluginID] = &pluginItem{
|
||||||
|
Plugin: nil,
|
||||||
|
Err: e,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res := make([]any, 0, len(resMap))
|
||||||
|
for _, p := range resMap {
|
||||||
|
res = append(res, p)
|
||||||
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,7 +90,10 @@ func (c *check) Item(ctx context.Context, id string) (any, error) {
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return p, nil
|
return &pluginItem{
|
||||||
|
Plugin: &p,
|
||||||
|
Err: c.pluginErrorResolver.PluginError(ctx, p.ID),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *check) Init(ctx context.Context) error {
|
func (c *check) Init(ctx context.Context) error {
|
||||||
|
|
@ -99,117 +129,8 @@ func (c *check) Steps() []checks.Step {
|
||||||
updateChecker: c.updateChecker,
|
updateChecker: c.updateChecker,
|
||||||
pluginIndex: c.pluginIndex,
|
pluginIndex: c.pluginIndex,
|
||||||
},
|
},
|
||||||
|
&unsignedStep{
|
||||||
|
pluginIndex: c.pluginIndex,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type deprecationStep struct {
|
|
||||||
GrafanaVersion string
|
|
||||||
updateChecker pluginchecker.PluginUpdateChecker
|
|
||||||
pluginIndex map[string]repo.PluginInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *deprecationStep) Title() string {
|
|
||||||
return "Deprecation check"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *deprecationStep) Description() string {
|
|
||||||
return "Check if any installed plugins are deprecated."
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *deprecationStep) Resolution() string {
|
|
||||||
return "Check the <a href='https://grafana.com/legal/plugin-deprecation/#a-plugin-i-use-is-deprecated-what-should-i-do'" +
|
|
||||||
"target=_blank>documentation</a> for recommended steps or delete the plugin."
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *deprecationStep) ID() string {
|
|
||||||
return DeprecationStepID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *deprecationStep) Run(ctx context.Context, log logging.Logger, _ *advisor.CheckSpec, it any) ([]advisor.CheckReportFailure, error) {
|
|
||||||
p, ok := it.(pluginstore.Plugin)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("invalid item type %T", it)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !s.updateChecker.IsUpdatable(ctx, p) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if plugin is deprecated
|
|
||||||
i, ok := s.pluginIndex[p.ID]
|
|
||||||
if !ok {
|
|
||||||
// Unable to check deprecation status
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
if i.Status == "deprecated" {
|
|
||||||
return []advisor.CheckReportFailure{checks.NewCheckReportFailure(
|
|
||||||
advisor.CheckReportFailureSeverityHigh,
|
|
||||||
s.ID(),
|
|
||||||
p.Name,
|
|
||||||
p.ID,
|
|
||||||
[]advisor.CheckErrorLink{
|
|
||||||
{
|
|
||||||
Message: "View plugin",
|
|
||||||
Url: fmt.Sprintf("/plugins/%s", p.ID),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)}, nil
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type updateStep struct {
|
|
||||||
GrafanaVersion string
|
|
||||||
updateChecker pluginchecker.PluginUpdateChecker
|
|
||||||
pluginIndex map[string]repo.PluginInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *updateStep) Title() string {
|
|
||||||
return "Update check"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *updateStep) Description() string {
|
|
||||||
return "Checks if an installed plugins has a newer version available."
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *updateStep) Resolution() string {
|
|
||||||
return "Go to the plugin admin page and upgrade to the latest version."
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *updateStep) ID() string {
|
|
||||||
return UpdateStepID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *updateStep) Run(ctx context.Context, log logging.Logger, _ *advisor.CheckSpec, i any) ([]advisor.CheckReportFailure, error) {
|
|
||||||
p, ok := i.(pluginstore.Plugin)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("invalid item type %T", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !s.updateChecker.IsUpdatable(ctx, p) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if plugin has a newer version available
|
|
||||||
info, ok := s.pluginIndex[p.ID]
|
|
||||||
if !ok {
|
|
||||||
// Unable to check updates
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
if s.updateChecker.CanUpdate(p.ID, p.Info.Version, info.Version, false) {
|
|
||||||
return []advisor.CheckReportFailure{checks.NewCheckReportFailure(
|
|
||||||
advisor.CheckReportFailureSeverityLow,
|
|
||||||
s.ID(),
|
|
||||||
p.Name,
|
|
||||||
p.ID,
|
|
||||||
[]advisor.CheckErrorLink{
|
|
||||||
{
|
|
||||||
Message: "Upgrade",
|
|
||||||
Url: fmt.Sprintf("/plugins/%s?page=version-history", p.ID),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ func TestRun(t *testing.T) {
|
||||||
pluginPreinstalled []string
|
pluginPreinstalled []string
|
||||||
pluginManaged []string
|
pluginManaged []string
|
||||||
pluginProvisioned []string
|
pluginProvisioned []string
|
||||||
|
pluginErrors []*plugins.Error
|
||||||
expectedFailures []advisor.CheckReportFailure
|
expectedFailures []advisor.CheckReportFailure
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
|
@ -119,6 +120,69 @@ func TestRun(t *testing.T) {
|
||||||
pluginProvisioned: []string{"plugin5"},
|
pluginProvisioned: []string{"plugin5"},
|
||||||
expectedFailures: []advisor.CheckReportFailure{},
|
expectedFailures: []advisor.CheckReportFailure{},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid signatures",
|
||||||
|
plugins: []pluginstore.Plugin{
|
||||||
|
{JSONData: plugins.JSONData{ID: "plugin6", Name: "Plugin 6", Info: plugins.Info{Version: "1.0.0"}}, Signature: plugins.SignatureStatusInvalid},
|
||||||
|
{JSONData: plugins.JSONData{ID: "plugin7", Name: "Plugin 7", Info: plugins.Info{Version: "1.0.0"}}, Signature: plugins.SignatureStatusModified},
|
||||||
|
{JSONData: plugins.JSONData{ID: "plugin8", Name: "Plugin 8", Info: plugins.Info{Version: "1.0.0"}}, Signature: plugins.SignatureStatusUnsigned},
|
||||||
|
},
|
||||||
|
pluginInfo: []repo.PluginInfo{
|
||||||
|
{Status: "active", Slug: "plugin6", Version: "1.0.0"},
|
||||||
|
{Status: "active", Slug: "plugin7", Version: "1.0.0"},
|
||||||
|
{Status: "active", Slug: "plugin8", Version: "1.0.0"},
|
||||||
|
},
|
||||||
|
pluginErrors: []*plugins.Error{
|
||||||
|
{PluginID: "plugin9", ErrorCode: plugins.ErrorCodeSignatureInvalid},
|
||||||
|
{PluginID: "plugin10", ErrorCode: plugins.ErrorCodeSignatureModified},
|
||||||
|
{PluginID: "plugin11", ErrorCode: plugins.ErrorCodeSignatureMissing},
|
||||||
|
{PluginID: "plugin12", ErrorCode: plugins.ErrorCodeFailedBackendStart}, // This should be ignored atm
|
||||||
|
},
|
||||||
|
expectedFailures: []advisor.CheckReportFailure{
|
||||||
|
{
|
||||||
|
Severity: advisor.CheckReportFailureSeverityLow,
|
||||||
|
StepID: UnsignedStepID,
|
||||||
|
Item: "Plugin 6",
|
||||||
|
ItemID: "plugin6",
|
||||||
|
Links: []advisor.CheckErrorLink{{Url: "/plugins/plugin6", Message: "View plugin"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Severity: advisor.CheckReportFailureSeverityLow,
|
||||||
|
StepID: UnsignedStepID,
|
||||||
|
Item: "Plugin 7",
|
||||||
|
ItemID: "plugin7",
|
||||||
|
Links: []advisor.CheckErrorLink{{Url: "/plugins/plugin7", Message: "View plugin"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Severity: advisor.CheckReportFailureSeverityLow,
|
||||||
|
StepID: UnsignedStepID,
|
||||||
|
Item: "Plugin 8",
|
||||||
|
ItemID: "plugin8",
|
||||||
|
Links: []advisor.CheckErrorLink{{Url: "/plugins/plugin8", Message: "View plugin"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Severity: advisor.CheckReportFailureSeverityHigh,
|
||||||
|
StepID: UnsignedStepID,
|
||||||
|
Item: "plugin9",
|
||||||
|
ItemID: "plugin9",
|
||||||
|
Links: []advisor.CheckErrorLink{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Severity: advisor.CheckReportFailureSeverityHigh,
|
||||||
|
StepID: UnsignedStepID,
|
||||||
|
Item: "plugin10",
|
||||||
|
ItemID: "plugin10",
|
||||||
|
Links: []advisor.CheckErrorLink{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Severity: advisor.CheckReportFailureSeverityHigh,
|
||||||
|
StepID: UnsignedStepID,
|
||||||
|
Item: "plugin11",
|
||||||
|
ItemID: "plugin11",
|
||||||
|
Links: []advisor.CheckErrorLink{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|
@ -131,7 +195,8 @@ func TestRun(t *testing.T) {
|
||||||
managedPlugins := &mockManagedPlugins{managed: tt.pluginManaged}
|
managedPlugins := &mockManagedPlugins{managed: tt.pluginManaged}
|
||||||
provisionedPlugins := &mockProvisionedPlugins{provisioned: tt.pluginProvisioned}
|
provisionedPlugins := &mockProvisionedPlugins{provisioned: tt.pluginProvisioned}
|
||||||
updateChecker := pluginchecker.ProvideService(managedPlugins, provisionedPlugins, pluginPreinstall)
|
updateChecker := pluginchecker.ProvideService(managedPlugins, provisionedPlugins, pluginPreinstall)
|
||||||
check := New(pluginStore, pluginRepo, updateChecker, "12.0.0")
|
pluginErrorResolver := &mockPluginErrorResolver{pluginErrors: tt.pluginErrors}
|
||||||
|
check := New(pluginStore, pluginRepo, updateChecker, pluginErrorResolver, "12.0.0")
|
||||||
|
|
||||||
items, err := check.Items(context.Background())
|
items, err := check.Items(context.Background())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
@ -148,8 +213,8 @@ func TestRun(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, len(tt.plugins), len(items))
|
assert.Equal(t, len(tt.plugins)+len(tt.pluginErrors), len(items))
|
||||||
assert.Equal(t, tt.expectedFailures, failures)
|
assert.ElementsMatch(t, tt.expectedFailures, failures)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -222,3 +287,21 @@ type mockProvisionedPlugins struct {
|
||||||
func (m *mockProvisionedPlugins) ProvisionedPlugins(ctx context.Context) ([]string, error) {
|
func (m *mockProvisionedPlugins) ProvisionedPlugins(ctx context.Context) ([]string, error) {
|
||||||
return m.provisioned, nil
|
return m.provisioned, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockPluginErrorResolver struct {
|
||||||
|
plugins.ErrorResolver
|
||||||
|
pluginErrors []*plugins.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockPluginErrorResolver) PluginErrors(ctx context.Context) []*plugins.Error {
|
||||||
|
return m.pluginErrors
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockPluginErrorResolver) PluginError(ctx context.Context, id string) *plugins.Error {
|
||||||
|
for _, err := range m.pluginErrors {
|
||||||
|
if err.PluginID == id {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
package plugincheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana-app-sdk/logging"
|
||||||
|
advisor "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1"
|
||||||
|
"github.com/grafana/grafana/apps/advisor/pkg/app/checks"
|
||||||
|
"github.com/grafana/grafana/pkg/plugins/repo"
|
||||||
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginchecker"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DeprecationStepID = "deprecation"
|
||||||
|
)
|
||||||
|
|
||||||
|
type deprecationStep struct {
|
||||||
|
GrafanaVersion string
|
||||||
|
updateChecker pluginchecker.PluginUpdateChecker
|
||||||
|
pluginIndex map[string]repo.PluginInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *deprecationStep) Title() string {
|
||||||
|
return "Deprecation check"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *deprecationStep) Description() string {
|
||||||
|
return "Check if any installed plugins are deprecated."
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *deprecationStep) Resolution() string {
|
||||||
|
return "Check the <a href='https://grafana.com/legal/plugin-deprecation/#a-plugin-i-use-is-deprecated-what-should-i-do'" +
|
||||||
|
"target=_blank>documentation</a> for recommended steps or delete the plugin."
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *deprecationStep) ID() string {
|
||||||
|
return DeprecationStepID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *deprecationStep) Run(ctx context.Context, log logging.Logger, _ *advisor.CheckSpec, it any) ([]advisor.CheckReportFailure, error) {
|
||||||
|
pi, ok := it.(*pluginItem)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid item type %T", it)
|
||||||
|
}
|
||||||
|
p := pi.Plugin
|
||||||
|
if p == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !s.updateChecker.IsUpdatable(ctx, *p) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if plugin is deprecated
|
||||||
|
i, ok := s.pluginIndex[p.ID]
|
||||||
|
if !ok {
|
||||||
|
// Unable to check deprecation status
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if i.Status == "deprecated" {
|
||||||
|
return []advisor.CheckReportFailure{checks.NewCheckReportFailure(
|
||||||
|
advisor.CheckReportFailureSeverityHigh,
|
||||||
|
s.ID(),
|
||||||
|
p.Name,
|
||||||
|
p.ID,
|
||||||
|
[]advisor.CheckErrorLink{
|
||||||
|
{
|
||||||
|
Message: "View plugin",
|
||||||
|
Url: fmt.Sprintf("/plugins/%s", p.ID),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)}, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
package plugincheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana-app-sdk/logging"
|
||||||
|
advisor "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1"
|
||||||
|
"github.com/grafana/grafana/apps/advisor/pkg/app/checks"
|
||||||
|
"github.com/grafana/grafana/pkg/plugins"
|
||||||
|
"github.com/grafana/grafana/pkg/plugins/repo"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
UnsignedStepID = "unsigned"
|
||||||
|
)
|
||||||
|
|
||||||
|
type unsignedStep struct {
|
||||||
|
pluginIndex map[string]repo.PluginInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *unsignedStep) Title() string {
|
||||||
|
return "Plugin signature check"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *unsignedStep) Description() string {
|
||||||
|
return "Checks has a missing or invalid signature."
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *unsignedStep) Resolution() string {
|
||||||
|
return "For security, we recommend only installing plugins from the catalog. " +
|
||||||
|
"Review the plugin's status and verify your allowlist if appropriate."
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *unsignedStep) ID() string {
|
||||||
|
return UnsignedStepID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *unsignedStep) Run(ctx context.Context, log logging.Logger, _ *advisor.CheckSpec, it any) ([]advisor.CheckReportFailure, error) {
|
||||||
|
pi, ok := it.(*pluginItem)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid item type %T", it)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := pi.Plugin
|
||||||
|
invalidSignatureTypes := []plugins.SignatureStatus{
|
||||||
|
plugins.SignatureStatusUnsigned,
|
||||||
|
plugins.SignatureStatusModified,
|
||||||
|
plugins.SignatureStatusInvalid,
|
||||||
|
}
|
||||||
|
if p != nil && slices.Contains(invalidSignatureTypes, p.Signature) {
|
||||||
|
// This will only happen in dev mode or if the plugin is in the unsigned allow list
|
||||||
|
links := []advisor.CheckErrorLink{}
|
||||||
|
if _, ok := s.pluginIndex[p.ID]; ok {
|
||||||
|
links = append(links, advisor.CheckErrorLink{
|
||||||
|
Message: "View plugin",
|
||||||
|
Url: fmt.Sprintf("/plugins/%s", p.ID),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return []advisor.CheckReportFailure{checks.NewCheckReportFailure(
|
||||||
|
advisor.CheckReportFailureSeverityLow,
|
||||||
|
s.ID(),
|
||||||
|
p.Name,
|
||||||
|
p.ID,
|
||||||
|
links,
|
||||||
|
)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginErr := pi.Err
|
||||||
|
invalidErrorCodeTypes := []plugins.ErrorCode{
|
||||||
|
plugins.ErrorCodeSignatureMissing,
|
||||||
|
plugins.ErrorCodeSignatureInvalid,
|
||||||
|
plugins.ErrorCodeSignatureModified,
|
||||||
|
}
|
||||||
|
if pluginErr != nil && slices.Contains(invalidErrorCodeTypes, pluginErr.ErrorCode) {
|
||||||
|
links := []advisor.CheckErrorLink{}
|
||||||
|
if _, ok := s.pluginIndex[pluginErr.PluginID]; ok {
|
||||||
|
links = append(links, advisor.CheckErrorLink{
|
||||||
|
Message: "View plugin",
|
||||||
|
Url: fmt.Sprintf("/plugins/%s", pluginErr.PluginID),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return []advisor.CheckReportFailure{checks.NewCheckReportFailure(
|
||||||
|
advisor.CheckReportFailureSeverityHigh,
|
||||||
|
s.ID(),
|
||||||
|
pluginErr.PluginID,
|
||||||
|
pluginErr.PluginID,
|
||||||
|
links,
|
||||||
|
)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
package plugincheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana-app-sdk/logging"
|
||||||
|
advisor "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1"
|
||||||
|
"github.com/grafana/grafana/apps/advisor/pkg/app/checks"
|
||||||
|
"github.com/grafana/grafana/pkg/plugins/repo"
|
||||||
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginchecker"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
UpdateStepID = "update"
|
||||||
|
)
|
||||||
|
|
||||||
|
type updateStep struct {
|
||||||
|
GrafanaVersion string
|
||||||
|
updateChecker pluginchecker.PluginUpdateChecker
|
||||||
|
pluginIndex map[string]repo.PluginInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *updateStep) Title() string {
|
||||||
|
return "Update check"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *updateStep) Description() string {
|
||||||
|
return "Checks if an installed plugins has a newer version available."
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *updateStep) Resolution() string {
|
||||||
|
return "Go to the plugin admin page and upgrade to the latest version."
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *updateStep) ID() string {
|
||||||
|
return UpdateStepID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *updateStep) Run(ctx context.Context, log logging.Logger, _ *advisor.CheckSpec, it any) ([]advisor.CheckReportFailure, error) {
|
||||||
|
pi, ok := it.(*pluginItem)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid item type %T", it)
|
||||||
|
}
|
||||||
|
p := pi.Plugin
|
||||||
|
if p == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !s.updateChecker.IsUpdatable(ctx, *p) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if plugin has a newer version available
|
||||||
|
info, ok := s.pluginIndex[p.ID]
|
||||||
|
if !ok {
|
||||||
|
// Unable to check updates
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if s.updateChecker.CanUpdate(p.ID, p.Info.Version, info.Version, false) {
|
||||||
|
return []advisor.CheckReportFailure{checks.NewCheckReportFailure(
|
||||||
|
advisor.CheckReportFailureSeverityLow,
|
||||||
|
s.ID(),
|
||||||
|
p.Name,
|
||||||
|
p.ID,
|
||||||
|
[]advisor.CheckErrorLink{
|
||||||
|
{
|
||||||
|
Message: "Upgrade",
|
||||||
|
Url: fmt.Sprintf("/plugins/%s?page=version-history", p.ID),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
@ -341,9 +341,9 @@ type AppDTO struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
errorCodeSignatureMissing ErrorCode = "signatureMissing"
|
ErrorCodeSignatureMissing ErrorCode = "signatureMissing"
|
||||||
errorCodeSignatureModified ErrorCode = "signatureModified"
|
ErrorCodeSignatureModified ErrorCode = "signatureModified"
|
||||||
errorCodeSignatureInvalid ErrorCode = "signatureInvalid"
|
ErrorCodeSignatureInvalid ErrorCode = "signatureInvalid"
|
||||||
ErrorCodeFailedBackendStart ErrorCode = "failedBackendStart"
|
ErrorCodeFailedBackendStart ErrorCode = "failedBackendStart"
|
||||||
ErrorAngular ErrorCode = "angular"
|
ErrorAngular ErrorCode = "angular"
|
||||||
)
|
)
|
||||||
|
|
@ -392,11 +392,11 @@ func (e Error) AsErrorCode() ErrorCode {
|
||||||
|
|
||||||
switch e.SignatureStatus {
|
switch e.SignatureStatus {
|
||||||
case SignatureStatusInvalid:
|
case SignatureStatusInvalid:
|
||||||
return errorCodeSignatureInvalid
|
return ErrorCodeSignatureInvalid
|
||||||
case SignatureStatusModified:
|
case SignatureStatusModified:
|
||||||
return errorCodeSignatureModified
|
return ErrorCodeSignatureModified
|
||||||
case SignatureStatusUnsigned:
|
case SignatureStatusUnsigned:
|
||||||
return errorCodeSignatureMissing
|
return ErrorCodeSignatureMissing
|
||||||
case SignatureStatusInternal, SignatureStatusValid:
|
case SignatureStatusInternal, SignatureStatusValid:
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
@ -411,11 +411,11 @@ func (e *Error) WithMessage(m string) *Error {
|
||||||
|
|
||||||
func (e Error) PublicMessage() string {
|
func (e Error) PublicMessage() string {
|
||||||
switch e.ErrorCode {
|
switch e.ErrorCode {
|
||||||
case errorCodeSignatureInvalid:
|
case ErrorCodeSignatureInvalid:
|
||||||
return "Invalid plugin signature"
|
return "Invalid plugin signature"
|
||||||
case errorCodeSignatureModified:
|
case ErrorCodeSignatureModified:
|
||||||
return "Plugin signature does not match"
|
return "Plugin signature does not match"
|
||||||
case errorCodeSignatureMissing:
|
case ErrorCodeSignatureMissing:
|
||||||
return "Plugin signature is missing"
|
return "Plugin signature is missing"
|
||||||
case ErrorCodeFailedBackendStart:
|
case ErrorCodeFailedBackendStart:
|
||||||
return "Plugin failed to start"
|
return "Plugin failed to start"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue