-
Notifications
You must be signed in to change notification settings - Fork 11.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CLI: Add command to migrate all datasources to use encrypted password…
- Loading branch information
Showing
14 changed files
with
266 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
126 changes: 126 additions & 0 deletions
126
pkg/cmd/grafana-cli/commands/datamigrations/encrypt_datasource_passwords.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package datamigrations | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
|
||
"github.com/fatih/color" | ||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger" | ||
|
||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/utils" | ||
"github.com/grafana/grafana/pkg/services/sqlstore" | ||
"github.com/grafana/grafana/pkg/setting" | ||
"github.com/grafana/grafana/pkg/util" | ||
"github.com/grafana/grafana/pkg/util/errutil" | ||
) | ||
|
||
var ( | ||
datasourceTypes = []string{ | ||
"mysql", | ||
"influxdb", | ||
"elasticsearch", | ||
"graphite", | ||
"prometheus", | ||
"opentsdb", | ||
} | ||
) | ||
|
||
// EncryptDatasourcePaswords migrates un-encrypted secrets on datasources | ||
// to the secureJson Column. | ||
func EncryptDatasourcePaswords(c utils.CommandLine, sqlStore *sqlstore.SqlStore) error { | ||
return sqlStore.WithDbSession(context.Background(), func(session *sqlstore.DBSession) error { | ||
passwordsUpdated, err := migrateColumn(session, "password") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
basicAuthUpdated, err := migrateColumn(session, "basic_auth_password") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
logger.Info("\n") | ||
if passwordsUpdated > 0 { | ||
logger.Infof("%s Encrypted password field for %d datasources \n", color.GreenString("✔"), passwordsUpdated) | ||
} | ||
|
||
if basicAuthUpdated > 0 { | ||
logger.Infof("%s Encrypted basic_auth_password field for %d datasources \n", color.GreenString("✔"), basicAuthUpdated) | ||
} | ||
|
||
if passwordsUpdated == 0 && basicAuthUpdated == 0 { | ||
logger.Infof("%s All datasources secrets are allready encrypted\n", color.GreenString("✔")) | ||
} | ||
|
||
logger.Info("\n") | ||
|
||
logger.Warn("Warning: Datasource provisioning files need to be manually changed to prevent overwriting of " + | ||
"the data during provisioning. See https://grafana.com/docs/installation/upgrading/#upgrading-to-v6-2 for " + | ||
"details") | ||
return nil | ||
}) | ||
} | ||
|
||
func migrateColumn(session *sqlstore.DBSession, column string) (int, error) { | ||
var rows []map[string]string | ||
|
||
session.Cols("id", column, "secure_json_data") | ||
session.Table("data_source") | ||
session.In("type", datasourceTypes) | ||
session.Where(column + " IS NOT NULL AND " + column + " != ''") | ||
err := session.Find(&rows) | ||
|
||
if err != nil { | ||
return 0, errutil.Wrapf(err, "failed to select column: %s", column) | ||
} | ||
|
||
rowsUpdated, err := updateRows(session, rows, column) | ||
return rowsUpdated, errutil.Wrapf(err, "failed to update column: %s", column) | ||
} | ||
|
||
func updateRows(session *sqlstore.DBSession, rows []map[string]string, passwordFieldName string) (int, error) { | ||
var rowsUpdated int | ||
|
||
for _, row := range rows { | ||
newSecureJSONData, err := getUpdatedSecureJSONData(row, passwordFieldName) | ||
if err != nil { | ||
return 0, err | ||
} | ||
|
||
data, err := json.Marshal(newSecureJSONData) | ||
if err != nil { | ||
return 0, errutil.Wrap("marshaling newSecureJsonData failed", err) | ||
} | ||
|
||
newRow := map[string]interface{}{"secure_json_data": data, passwordFieldName: ""} | ||
session.Table("data_source") | ||
session.Where("id = ?", row["id"]) | ||
// Setting both columns while having value only for secure_json_data should clear the [passwordFieldName] column | ||
session.Cols("secure_json_data", passwordFieldName) | ||
|
||
_, err = session.Update(newRow) | ||
if err != nil { | ||
return 0, err | ||
} | ||
|
||
rowsUpdated++ | ||
} | ||
return rowsUpdated, nil | ||
} | ||
|
||
func getUpdatedSecureJSONData(row map[string]string, passwordFieldName string) (map[string]interface{}, error) { | ||
encryptedPassword, err := util.Encrypt([]byte(row[passwordFieldName]), setting.SecretKey) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var secureJSONData map[string]interface{} | ||
|
||
if err := json.Unmarshal([]byte(row["secure_json_data"]), &secureJSONData); err != nil { | ||
return nil, err | ||
} | ||
|
||
jsonFieldName := util.ToCamelCase(passwordFieldName) | ||
secureJSONData[jsonFieldName] = encryptedPassword | ||
return secureJSONData, nil | ||
} |
67 changes: 67 additions & 0 deletions
67
pkg/cmd/grafana-cli/commands/datamigrations/encrypt_datasource_passwords_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package datamigrations | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/commands/commandstest" | ||
"github.com/grafana/grafana/pkg/components/securejsondata" | ||
"github.com/grafana/grafana/pkg/models" | ||
"github.com/grafana/grafana/pkg/services/sqlstore" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestPasswordMigrationCommand(t *testing.T) { | ||
//setup datasources with password, basic_auth and none | ||
sqlstore := sqlstore.InitTestDB(t) | ||
session := sqlstore.NewSession() | ||
defer session.Close() | ||
|
||
datasources := []*models.DataSource{ | ||
{Type: "influxdb", Name: "influxdb", Password: "foobar"}, | ||
{Type: "graphite", Name: "graphite", BasicAuthPassword: "foobar"}, | ||
{Type: "prometheus", Name: "prometheus", SecureJsonData: securejsondata.GetEncryptedJsonData(map[string]string{})}, | ||
} | ||
|
||
// set required default values | ||
for _, ds := range datasources { | ||
ds.Created = time.Now() | ||
ds.Updated = time.Now() | ||
ds.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{}) | ||
} | ||
|
||
_, err := session.Insert(&datasources) | ||
assert.Nil(t, err) | ||
|
||
//run migration | ||
err = EncryptDatasourcePaswords(&commandstest.FakeCommandLine{}, sqlstore) | ||
assert.Nil(t, err) | ||
|
||
//verify that no datasources still have password or basic_auth | ||
var dss []*models.DataSource | ||
err = session.SQL("select * from data_source").Find(&dss) | ||
assert.Nil(t, err) | ||
assert.Equal(t, len(dss), 3) | ||
|
||
for _, ds := range dss { | ||
sj := ds.SecureJsonData.Decrypt() | ||
|
||
if ds.Name == "influxdb" { | ||
assert.Equal(t, ds.Password, "") | ||
v, exist := sj["password"] | ||
assert.True(t, exist) | ||
assert.Equal(t, v, "foobar", "expected password to be moved to securejson") | ||
} | ||
|
||
if ds.Name == "graphite" { | ||
assert.Equal(t, ds.BasicAuthPassword, "") | ||
v, exist := sj["basicAuthPassword"] | ||
assert.True(t, exist) | ||
assert.Equal(t, v, "foobar", "expected basic_auth_password to be moved to securejson") | ||
} | ||
|
||
if ds.Name == "prometheus" { | ||
assert.Equal(t, len(sj), 0) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.