[MM-57194] Allow plugins to mark setting fields as secret (#27986)
Co-authored-by: Claudio Costa <cstcld91@gmail.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
84a0c09d56
Коммит
70fe2abea6
@@ -3197,6 +3197,38 @@ func (s *PluginSettings) SetDefaults(ls LogSettings) {
|
||||
}
|
||||
}
|
||||
|
||||
// Sanitize cleans up the plugin settings by removing any sensitive information.
|
||||
// It does so by checking if the setting is marked as secret in the plugin manifest.
|
||||
// If it is, the setting is replaced with a fake value.
|
||||
// If a plugin is no longer installed, all settings of it's are sanitized.
|
||||
// If the list of manifests in nil, i.e. plugins are disabled, all settings are sanitized.
|
||||
func (s *PluginSettings) Sanitize(pluginManifests []*Manifest) {
|
||||
manifestMap := make(map[string]*Manifest, len(pluginManifests))
|
||||
|
||||
for _, manifest := range pluginManifests {
|
||||
manifestMap[manifest.Id] = manifest
|
||||
}
|
||||
|
||||
for id, settings := range s.Plugins {
|
||||
manifest := manifestMap[id]
|
||||
|
||||
for key := range settings {
|
||||
if manifest == nil {
|
||||
// Sanitize plugin settings for plugins that are not installed
|
||||
settings[key] = FakeSetting
|
||||
continue
|
||||
}
|
||||
|
||||
for _, definedSetting := range manifest.SettingsSchema.Settings {
|
||||
if definedSetting.Secret && strings.EqualFold(definedSetting.Key, key) {
|
||||
settings[key] = FakeSetting
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type WranglerSettings struct {
|
||||
PermittedWranglerRoles []string
|
||||
AllowedEmailDomain []string
|
||||
@@ -4396,7 +4428,7 @@ func (o *Config) GetSanitizeOptions() map[string]bool {
|
||||
return options
|
||||
}
|
||||
|
||||
func (o *Config) Sanitize() {
|
||||
func (o *Config) Sanitize(pluginManifests []*Manifest) {
|
||||
if o.LdapSettings.BindPassword != nil && *o.LdapSettings.BindPassword != "" {
|
||||
*o.LdapSettings.BindPassword = FakeSetting
|
||||
}
|
||||
@@ -4462,6 +4494,8 @@ func (o *Config) Sanitize() {
|
||||
if o.ServiceSettings.SplitKey != nil {
|
||||
*o.ServiceSettings.SplitKey = FakeSetting
|
||||
}
|
||||
|
||||
o.PluginSettings.Sanitize(pluginManifests)
|
||||
}
|
||||
|
||||
// structToMapFilteredByTag converts a struct into a map removing those fields that has the tag passed
|
||||
|
||||
@@ -1387,7 +1387,7 @@ func TestConfigSanitize(t *testing.T) {
|
||||
QueryTimeLag: NewPointer("QueryTimeLag"),
|
||||
}}
|
||||
|
||||
c.Sanitize()
|
||||
c.Sanitize(nil)
|
||||
|
||||
assert.Equal(t, FakeSetting, *c.LdapSettings.BindPassword)
|
||||
assert.Equal(t, FakeSetting, *c.FileSettings.PublicLinkSalt)
|
||||
@@ -1409,12 +1409,201 @@ func TestConfigSanitize(t *testing.T) {
|
||||
t.Run("with default config", func(t *testing.T) {
|
||||
c := Config{}
|
||||
c.SetDefaults()
|
||||
c.Sanitize()
|
||||
c.Sanitize(nil)
|
||||
|
||||
assert.Len(t, c.SqlSettings.ReplicaLagSettings, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPluginSettingsSanitize(t *testing.T) {
|
||||
plugins := map[string]map[string]any{
|
||||
"plugin.id": {
|
||||
"somesetting": "some value",
|
||||
"secrettext": "a secret",
|
||||
"secretnumber": 123,
|
||||
},
|
||||
"another.plugin": {
|
||||
"somesetting": 456,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range map[string]struct {
|
||||
manifests []*Manifest
|
||||
expected map[string]map[string]any
|
||||
}{
|
||||
"nil list of manifests": {
|
||||
manifests: nil,
|
||||
expected: map[string]map[string]any{
|
||||
"plugin.id": {
|
||||
"somesetting": FakeSetting,
|
||||
"secrettext": FakeSetting,
|
||||
"secretnumber": FakeSetting,
|
||||
},
|
||||
"another.plugin": {
|
||||
"somesetting": FakeSetting,
|
||||
},
|
||||
},
|
||||
},
|
||||
"empty list of manifests": {
|
||||
manifests: []*Manifest{},
|
||||
expected: map[string]map[string]any{
|
||||
"plugin.id": {
|
||||
"somesetting": FakeSetting,
|
||||
"secrettext": FakeSetting,
|
||||
"secretnumber": FakeSetting,
|
||||
},
|
||||
"another.plugin": {
|
||||
"somesetting": FakeSetting,
|
||||
},
|
||||
},
|
||||
},
|
||||
"one plugin installed": {
|
||||
manifests: []*Manifest{
|
||||
{
|
||||
Id: "plugin.id",
|
||||
SettingsSchema: &PluginSettingsSchema{
|
||||
Settings: []*PluginSetting{
|
||||
{
|
||||
Key: "somesetting",
|
||||
Type: "text",
|
||||
Secret: false,
|
||||
},
|
||||
{
|
||||
Key: "secrettext",
|
||||
Type: "text",
|
||||
Secret: true,
|
||||
},
|
||||
{
|
||||
Key: "secretnumber",
|
||||
Type: "number",
|
||||
Secret: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]map[string]any{
|
||||
"plugin.id": {
|
||||
"somesetting": "some value",
|
||||
"secrettext": FakeSetting,
|
||||
"secretnumber": FakeSetting,
|
||||
},
|
||||
"another.plugin": {
|
||||
"somesetting": FakeSetting,
|
||||
},
|
||||
},
|
||||
},
|
||||
"two plugins installed": {
|
||||
manifests: []*Manifest{
|
||||
{
|
||||
Id: "plugin.id",
|
||||
SettingsSchema: &PluginSettingsSchema{
|
||||
Settings: []*PluginSetting{
|
||||
{
|
||||
Key: "somesetting",
|
||||
Type: "text",
|
||||
Secret: false,
|
||||
},
|
||||
{
|
||||
Key: "secrettext",
|
||||
Type: "text",
|
||||
Secret: true,
|
||||
},
|
||||
{
|
||||
Key: "secretnumber",
|
||||
Type: "number",
|
||||
Secret: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: "another.plugin",
|
||||
SettingsSchema: &PluginSettingsSchema{
|
||||
Settings: []*PluginSetting{
|
||||
{
|
||||
Key: "somesetting",
|
||||
Type: "number",
|
||||
Secret: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]map[string]any{
|
||||
"plugin.id": {
|
||||
"somesetting": "some value",
|
||||
"secrettext": FakeSetting,
|
||||
"secretnumber": FakeSetting,
|
||||
},
|
||||
"another.plugin": {
|
||||
"somesetting": 456,
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
name := name // TODO: Remove once go1.22 is used
|
||||
tc := tc // TODO: Remove once go1.22 is used
|
||||
|
||||
if name != "one plugin installed" {
|
||||
return
|
||||
}
|
||||
|
||||
c := PluginSettings{}
|
||||
c.SetDefaults(*NewLogSettings())
|
||||
c.Plugins = plugins
|
||||
|
||||
c.Sanitize(tc.manifests)
|
||||
|
||||
assert.Equal(t, tc.expected, c.Plugins, name)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("one plugin installed, two in the config", func(t *testing.T) {
|
||||
c := PluginSettings{}
|
||||
c.SetDefaults(*NewLogSettings())
|
||||
c.Plugins = plugins
|
||||
|
||||
c.Sanitize([]*Manifest{
|
||||
{
|
||||
Id: "plugin.id",
|
||||
SettingsSchema: &PluginSettingsSchema{
|
||||
Settings: []*PluginSetting{
|
||||
{
|
||||
Key: "somesetting",
|
||||
Type: "text",
|
||||
Secret: false,
|
||||
},
|
||||
{
|
||||
Key: "secrettext",
|
||||
Type: "text",
|
||||
Secret: true,
|
||||
},
|
||||
{
|
||||
Key: "secretnumber",
|
||||
Type: "number",
|
||||
Secret: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expected := map[string]map[string]any{
|
||||
"plugin.id": {
|
||||
"somesetting": "some value",
|
||||
"secrettext": FakeSetting,
|
||||
"secretnumber": FakeSetting,
|
||||
},
|
||||
"another.plugin": {
|
||||
"somesetting": FakeSetting,
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expected, c.Plugins)
|
||||
})
|
||||
}
|
||||
|
||||
func TestConfigFilteredByTag(t *testing.T) {
|
||||
c := Config{}
|
||||
c.SetDefaults()
|
||||
|
||||
@@ -89,6 +89,10 @@ type PluginSetting struct {
|
||||
// and the opposite environment is running the plugin, the setting will be hidden in the admin console UI.
|
||||
// Note that this functionality is entirely client-side, so the plugin needs to handle the case of invalid submissions.
|
||||
Hosting string `json:"hosting"`
|
||||
|
||||
// If true, the setting is sanitized before showing it in the System Console or returning it via the API.
|
||||
// This is useful for settings that contain sensitive information.
|
||||
Secret bool `json:"secret"`
|
||||
}
|
||||
|
||||
type PluginSettingsSection struct {
|
||||
|
||||
Ссылка в новой задаче
Block a user