diff --git a/core/conf/security.go b/core/conf/security.go new file mode 100644 index 000000000000..3490182ce39a --- /dev/null +++ b/core/conf/security.go @@ -0,0 +1,54 @@ +package conf + +import ( + "log" + "os" + "reflect" + + "github.com/WqyJh/confcrypt" +) + +type SecurityConf struct { + Enable bool `json:",default=true"` + Env string `json:",default=CONFIG_KEY"` // environment variable name stores the encryption key +} + +func findSecurityConfInStruct(v interface{}) (SecurityConf, bool) { + if reflect.ValueOf(v).Kind() == reflect.Ptr { + v = reflect.ValueOf(v).Elem().Interface() + } + t := reflect.TypeOf(v) + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + if field.Type == reflect.TypeOf(SecurityConf{}) { + return reflect.ValueOf(v).FieldByIndex(field.Index).Interface().(SecurityConf), true + } + } + return SecurityConf{}, false +} + +func SecurityLoad(path string, v interface{}, opts ...Option) error { + if err := Load(path, v, opts...); err != nil { + return err + } + c, ok := findSecurityConfInStruct(v) + if ok && c.Enable { + key := os.Getenv(c.Env) + decoded, err := confcrypt.Decode(v, key) + if err != nil { + return err + } + if reflect.TypeOf(v).Kind() == reflect.Ptr { + reflect.ValueOf(v).Elem().Set(reflect.ValueOf(decoded).Elem()) + return nil + } + reflect.ValueOf(v).Set(reflect.ValueOf(decoded)) + } + return nil +} + +func SecurityMustLoad(path string, v interface{}, opts ...Option) { + if err := SecurityLoad(path, v, opts...); err != nil { + log.Fatalf("error: config file %s, %s", path, err.Error()) + } +} diff --git a/core/conf/security_test.go b/core/conf/security_test.go new file mode 100644 index 000000000000..72c30c78c2d8 --- /dev/null +++ b/core/conf/security_test.go @@ -0,0 +1,50 @@ +package conf + +import ( + "os" + "testing" + + "github.com/WqyJh/confcrypt" + "github.com/stretchr/testify/assert" +) + +func TestSecurityLoad(t *testing.T) { + key := "testkey" + type testConfig struct { + SecurityConf + + User string `json:"user"` + Pass string `json:"pass"` + Secret string `json:"secret"` + } + expected := testConfig{ + SecurityConf: SecurityConf{ + Enable: true, + Env: "CONFIG_KEY", + }, + User: "testuser", + Pass: "testpass", + Secret: "testsecret", + } + encryptedPass, err := confcrypt.EncryptString(expected.Pass, key) + assert.Nil(t, err) + encryptedSecret, err := confcrypt.EncryptString(expected.Secret, key) + assert.Nil(t, err) + text := `{ + "user": "testuser", + "pass": "` + encryptedPass + `", + "secret": "` + encryptedSecret + `" +}` + tmpfile, err := createTempFile(".json", text) + assert.Nil(t, err) + defer os.Remove(tmpfile) + + os.Setenv("CONFIG_KEY", key) + var config testConfig + err = SecurityLoad(tmpfile, &config) + assert.Nil(t, err) + assert.NotEqual(t, encryptedPass, config.Pass) + assert.NotEqual(t, encryptedPass, config.Secret) + assert.Equal(t, expected.Pass, config.Pass) + assert.Equal(t, expected.Secret, config.Secret) +} diff --git a/go.mod b/go.mod index 60f748b2a4ec..002bf2cb1df6 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.19 require ( github.com/DATA-DOG/go-sqlmock v1.5.0 + github.com/WqyJh/confcrypt v0.1.0 github.com/alicebob/miniredis/v2 v2.31.0 github.com/fatih/color v1.16.0 github.com/fullstorydev/grpcurl v1.8.9 diff --git a/go.sum b/go.sum index b87df7ebcfd0..3cb481c05207 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DmitriyVTitov/size v1.5.0/go.mod h1:le6rNI4CoLQV1b9gzp1+3d7hMAD/uu2QcJ+aYbNgiU0= +github.com/WqyJh/confcrypt v0.1.0 h1:fsVtVXcKIc4oGVwvijIHSFlAdpoAjZaHLx12Hh6//+0= +github.com/WqyJh/confcrypt v0.1.0/go.mod h1:f6DFx0FNr/TDAeTtSrtwQ85d++pLm+9QqBgfRFdLfQo= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE= github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=