diff --git a/storage/backend.go b/storage/backend.go index e8a324ae2..d1af7be74 100644 --- a/storage/backend.go +++ b/storage/backend.go @@ -169,6 +169,10 @@ func NewFailedStorageBackend(driver Driver) *Backend { return &backend } +func (b Backend) String() string { + return fmt.Sprintf("%#v", b) +} + func (b *Backend) AddStoragePool(pool *Pool) { b.Storage[pool.Name] = pool } diff --git a/storage_drivers/eseries/eseries_iscsi.go b/storage_drivers/eseries/eseries_iscsi.go index 95ac2b9b9..35c7a0edc 100644 --- a/storage_drivers/eseries/eseries_iscsi.go +++ b/storage_drivers/eseries/eseries_iscsi.go @@ -1198,9 +1198,9 @@ func (d *SANStorageDriver) GetExternalConfig() interface{} { // Clone the config so we don't risk altering the original var cloneConfig drivers.ESeriesStorageDriverConfig drivers.Clone(d.Config, &cloneConfig) - cloneConfig.Username = "" // redact the username - cloneConfig.Password = "" // redact the password - cloneConfig.PasswordArray = "" // redact the password + cloneConfig.Username = "" // redact the username + cloneConfig.Password = "" // redact the password + cloneConfig.PasswordArray = "" // redact the password return cloneConfig } @@ -1255,6 +1255,12 @@ func (d *SANStorageDriver) GetVolumeExternal(name string) (*storage.VolumeExtern return d.getVolumeExternal(&volumeAttrs), nil } +// Implement stringer interface for the E-Series driver +func (d SANStorageDriver) String() string { + sensitive := d.Config.DebugTraceFlags["sensitive"] + return drivers.ToString(sensitive, &d, []string{"API"}, d.GetExternalConfig()) +} + // GetVolumeExternalWrappers queries the storage backend for all relevant info about // container volumes managed by this driver. It then writes a VolumeExternal // representation of each volume to the supplied channel, closing the channel diff --git a/storage_drivers/eseries/eseries_iscsi_test.go b/storage_drivers/eseries/eseries_iscsi_test.go new file mode 100644 index 000000000..a66cd2950 --- /dev/null +++ b/storage_drivers/eseries/eseries_iscsi_test.go @@ -0,0 +1,120 @@ +// Copyright 2020 NetApp, Inc. All Rights Reserved. + +package eseries + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + drivers "github.com/netapp/trident/storage_drivers" + "github.com/netapp/trident/storage_drivers/eseries/api" +) + +const ( + Username = "tester" + Password = "password" + PasswordArray = "Passwords" +) + +func newTestEseriesSANDriver(showSensitive *bool) *SANStorageDriver { + config := &drivers.ESeriesStorageDriverConfig{} + sp := func(s string) *string { return &s } + + config.CommonStorageDriverConfig = &drivers.CommonStorageDriverConfig{} + config.CommonStorageDriverConfig.DebugTraceFlags = make(map[string]bool) + config.CommonStorageDriverConfig.DebugTraceFlags["method"] = true + + if showSensitive != nil { + config.CommonStorageDriverConfig.DebugTraceFlags["sensitive"] = *showSensitive + } + + config.Username = Username + config.Password = Password + config.PasswordArray = PasswordArray + config.WebProxyHostname = "10.0.0.1" + config.WebProxyPort = "2222" + config.WebProxyUseHTTP = false + config.WebProxyVerifyTLS = false + config.ControllerA = "10.0.0.2" + config.ControllerB = "10.0.0.3" + config.HostDataIP = "10.0.0.4" + config.StorageDriverName = "eseries-san" + config.StoragePrefix = sp("test_") + + telemetry := make(map[string]string) + telemetry["version"] = "20.07.0" + telemetry["plugin"] = "eseries" + telemetry["storagePrefix"] = *config.StoragePrefix + + API := api.NewAPIClient(api.ClientConfig{ + WebProxyHostname: config.WebProxyHostname, + WebProxyPort: config.WebProxyPort, + WebProxyUseHTTP: config.WebProxyUseHTTP, + WebProxyVerifyTLS: config.WebProxyVerifyTLS, + Username: config.Username, + Password: config.Password, + ControllerA: config.ControllerA, + ControllerB: config.ControllerB, + PasswordArray: config.PasswordArray, + PoolNameSearchPattern: config.PoolNameSearchPattern, + HostDataIP: config.HostDataIP, + Protocol: "iscsi", + AccessGroup: config.AccessGroup, + HostType: config.HostType, + DriverName: "eseries-iscsi", + Telemetry: telemetry, + }) + + sanDriver := &SANStorageDriver{} + sanDriver.Config = *config + sanDriver.API = API + + return sanDriver +} + +func TestEseriesSANStorageDriverConfigString(t *testing.T) { + + var EseriesSANDrivers = []SANStorageDriver{ + *newTestEseriesSANDriver(&[]bool{true}[0]), + *newTestEseriesSANDriver(&[]bool{false}[0]), + *newTestEseriesSANDriver(nil), + } + + for _, EseriesSANDriver := range EseriesSANDrivers { + sensitive, ok := EseriesSANDriver.Config.DebugTraceFlags["sensitive"] + + switch { + + case !ok: + assert.Contains(t, EseriesSANDriver.String(), "", + "Eseries driver does not contain ") + assert.Contains(t, EseriesSANDriver.String(), "API:", + "Eseries driver does not redact API information") + assert.NotContains(t, EseriesSANDriver.String(), Username, + "Eseries driver contains username") + assert.NotContains(t, EseriesSANDriver.String(), Password, + "Eseries driver contains password") + assert.NotContains(t, EseriesSANDriver.String(), PasswordArray, + "Eseries driver contains password array") + case ok && sensitive: + assert.Contains(t, EseriesSANDriver.String(), Username, + "Eseries driver does not contain username") + assert.Contains(t, EseriesSANDriver.String(), Password, + "Eseries driver does not contain password") + assert.Contains(t, EseriesSANDriver.String(), PasswordArray, + "Eseries driver does not contain password array") + case ok && !sensitive: + assert.Contains(t, EseriesSANDriver.String(), "", + "Eseries driver does not contain ") + assert.Contains(t, EseriesSANDriver.String(), "API:", + "Eseries driver does not redact API information") + assert.NotContains(t, EseriesSANDriver.String(), Username, + "Eseries driver contains username") + assert.NotContains(t, EseriesSANDriver.String(), Password, + "Eseries driver contains password") + assert.NotContains(t, EseriesSANDriver.String(), PasswordArray, + "Eseries driver contains password array") + } + } +} diff --git a/storage_drivers/solidfire/solidfire_san.go b/storage_drivers/solidfire/solidfire_san.go index 83bb4e4ae..307abfe98 100644 --- a/storage_drivers/solidfire/solidfire_san.go +++ b/storage_drivers/solidfire/solidfire_san.go @@ -1716,6 +1716,12 @@ func (d *SANStorageDriver) GetVolumeExternal(name string) (*storage.VolumeExtern return d.getVolumeExternal(name, &volume), nil } +// Implement stringer interface for the SANStorageDriver driver +func (d SANStorageDriver) String() string { + sensitive := d.Config.DebugTraceFlags["sensitive"] + return drivers.ToString(sensitive, &d, []string{"Client", "AccountID"}, d.GetExternalConfig()) +} + // GetVolumeExternalWrappers queries the storage backend for all relevant info about // container volumes managed by this driver. It then writes a VolumeExternal // representation of each volume to the supplied channel, closing the channel diff --git a/storage_drivers/solidfire/solidfire_san_test.go b/storage_drivers/solidfire/solidfire_san_test.go new file mode 100644 index 000000000..c8d68be91 --- /dev/null +++ b/storage_drivers/solidfire/solidfire_san_test.go @@ -0,0 +1,135 @@ +// Copyright 2020 NetApp, Inc. All Rights Reserved. + +package solidfire + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + drivers "github.com/netapp/trident/storage_drivers" + "github.com/netapp/trident/storage_drivers/solidfire/api" +) + +const ( + TenantName = "tester" + AdminPass = "admin:password" + Endpoint = "https://" + AdminPass + "@10.0.0.1/json-rpc/7.0" +) + +func newTestSolidfireSANDriver(showSensitive *bool) *SANStorageDriver { + config := &drivers.SolidfireStorageDriverConfig{} + sp := func(s string) *string { return &s } + + config.CommonStorageDriverConfig = &drivers.CommonStorageDriverConfig{} + config.CommonStorageDriverConfig.DebugTraceFlags = make(map[string]bool) + config.CommonStorageDriverConfig.DebugTraceFlags["method"] = true + + if showSensitive != nil { + config.CommonStorageDriverConfig.DebugTraceFlags["sensitive"] = *showSensitive + } + + config.TenantName = TenantName + config.EndPoint = Endpoint + config.SVIP = "10.0.0.1:1000" + config.InitiatorIFace = "default" + config.Types = &[]api.VolType{ + { + Type: "Gold", + QOS: api.QoS{ + BurstIOPS: 10000, + MaxIOPS: 8000, + MinIOPS: 6000, + }, + }, + { + Type: "Bronze", + QOS: api.QoS{ + BurstIOPS: 4000, + MaxIOPS: 2000, + MinIOPS: 1000, + }, + }, + } + config.AccessGroups = []int64{} + config.UseCHAP = true + config.DefaultBlockSize = 4096 + config.StorageDriverName = "solidfire-san" + config.StoragePrefix = sp("test_") + + cfg := api.Config{ + TenantName: config.TenantName, + EndPoint: Endpoint, + SVIP: config.SVIP, + InitiatorIFace: config.InitiatorIFace, + Types: config.Types, + LegacyNamePrefix: config.LegacyNamePrefix, + AccessGroups: config.AccessGroups, + DefaultBlockSize: 4096, + DebugTraceFlags: config.DebugTraceFlags, + } + + client, _ := api.NewFromParameters(Endpoint, config.SVIP, cfg) + + sanDriver := &SANStorageDriver{} + sanDriver.Config = *config + sanDriver.Client = client + sanDriver.AccountID = 2222 + sanDriver.AccessGroups = []int64{} + sanDriver.LegacyNamePrefix = "oldtest_" + sanDriver.InitiatorIFace = "default" + sanDriver.DefaultMaxIOPS = 20000 + sanDriver.DefaultMinIOPS = 1000 + + return sanDriver +} + +func TestSolidfireSANStorageDriverConfigString(t *testing.T) { + + var solidfireSANDrivers = []SANStorageDriver{ + *newTestSolidfireSANDriver(&[]bool{true}[0]), + *newTestSolidfireSANDriver(&[]bool{false}[0]), + *newTestSolidfireSANDriver(nil), + } + + for _, solidfireSANDriver := range solidfireSANDrivers { + sensitive, ok := solidfireSANDriver.Config.DebugTraceFlags["sensitive"] + + switch { + + case !ok: + assert.Contains(t, solidfireSANDriver.String(), "", + "Solidfire driver does not contain ") + assert.Contains(t, solidfireSANDriver.String(), "Client:", + "Solidfire driver does not redact client API information") + assert.Contains(t, solidfireSANDriver.String(), "AccountID:", + "Solidfire driver does not redact Account ID information") + assert.NotContains(t, solidfireSANDriver.String(), TenantName, + "Solidfire driver contains tenant name") + assert.NotContains(t, solidfireSANDriver.String(), AdminPass, + "Solidfire driver contains endpoint's admin and password") + assert.NotContains(t, solidfireSANDriver.String(), "2222", + "Solidfire driver contains Account ID") + case ok && sensitive: + assert.Contains(t, solidfireSANDriver.String(), TenantName, + "Solidfire driver does not contain tenant name") + assert.Contains(t, solidfireSANDriver.String(), AdminPass, + "Solidfire driver does not contain endpoint's admin and password") + assert.Contains(t, solidfireSANDriver.String(), "2222", + "Solidfire driver does not contain Account ID") + case ok && !sensitive: + assert.Contains(t, solidfireSANDriver.String(), "", + "Solidfire driver does not contain ") + assert.Contains(t, solidfireSANDriver.String(), "Client:", + "Solidfire driver does not redact client API information") + assert.Contains(t, solidfireSANDriver.String(), "AccountID:", + "Solidfire driver does not redact Account ID information") + assert.NotContains(t, solidfireSANDriver.String(), TenantName, + "Solidfire driver contains tenant name") + assert.NotContains(t, solidfireSANDriver.String(), AdminPass, + "Solidfire driver contains endpoint's admin and password") + assert.NotContains(t, solidfireSANDriver.String(), "2222", + "Solidfire driver contains Account ID") + } + } +} diff --git a/storage_drivers/types.go b/storage_drivers/types.go index a0fea4def..b65d40a2b 100644 --- a/storage_drivers/types.go +++ b/storage_drivers/types.go @@ -4,11 +4,15 @@ package storagedrivers import ( "encoding/json" "fmt" + "reflect" "strings" + log "github.com/sirupsen/logrus" + trident "github.com/netapp/trident/config" "github.com/netapp/trident/storage/fake" sfapi "github.com/netapp/trident/storage_drivers/solidfire/api" + "github.com/netapp/trident/utils" ) // CommonStorageDriverConfig holds settings in common across all StorageDrivers @@ -30,6 +34,12 @@ type CommonStorageDriverConfigDefaults struct { Size string `json:"size"` } +// Implement stringer interface for the CommonStorageDriverConfig driver +func (d CommonStorageDriverConfig) String() string { + sensitive := d.DebugTraceFlags["sensitive"] + return ToString(sensitive, &d, []string{}, nil) +} + // ESeriesStorageDriverConfig holds settings for ESeriesStorageDriver type ESeriesStorageDriverConfig struct { *CommonStorageDriverConfig @@ -71,6 +81,12 @@ type EseriesStorageDriverConfigDefaults struct { CommonStorageDriverConfigDefaults } +// Implement stringer interface for the E-Series driver +func (d ESeriesStorageDriverConfig) String() string { + sensitive := d.CommonStorageDriverConfig.DebugTraceFlags["sensitive"] + return ToString(sensitive, &d, []string{"Password", "PasswordArray", "Username"}, nil) +} + // OntapStorageDriverConfig holds settings for OntapStorageDrivers type OntapStorageDriverConfig struct { *CommonStorageDriverConfig // embedded types replicate all fields @@ -150,6 +166,12 @@ type SolidfireStorageDriverConfigDefaults struct { CommonStorageDriverConfigDefaults } +// Implement stringer interface for the Solidfire driver +func (d SolidfireStorageDriverConfig) String() string { + sensitive := d.CommonStorageDriverConfig.DebugTraceFlags["sensitive"] + return ToString(sensitive, &d, []string{"TenantName", "EndPoint"}, nil) +} + type AWSNFSStorageDriverConfig struct { *CommonStorageDriverConfig APIURL string `json:"apiURL"` @@ -341,3 +363,41 @@ func NewSnapshotsNotSupportedError(backendType string) error { message: fmt.Sprintf("snapshots are not supported by backend type %s", backendType), } } + +// ToString identifies attributes of a struct, stringifies them such that they can be consumed by the +// struct's stringer interface, redacts elements specified in the redactList, and replaces +// config with external config format depending upon the value of sensitive flag. +func ToString(sensitive bool, structPointer interface{}, redactList []string, configVal interface{}) (out string) { + + defer func() { + if r := recover(); r != nil { + log.Errorf("Panic in types#ToString; err: %v", r) + out = "" + } + }() + + elements := reflect.ValueOf(structPointer).Elem() + var output strings.Builder + + for i := 0; i < elements.NumField(); i++ { + + fieldName := elements.Type().Field(i).Name + + if sensitive { + output.WriteString(fmt.Sprintf("%v:%v ", fieldName, elements.Field(i))) + } else { + switch { + case fieldName == "Config" && configVal != nil: + output.WriteString(fmt.Sprintf("%v:%v ", fieldName, configVal)) + case utils.SliceContainsString(redactList, fieldName): + output.WriteString(fmt.Sprintf("%v:%v ", fieldName, "")) + default: + output.WriteString(fmt.Sprintf("%v:%v ", fieldName, elements.Field(i))) + } + } + } + + out = output.String() + + return +}