diff --git a/cmd/kieserver/main.go b/cmd/kieserver/main.go index e0b65fbd..b906bbee 100644 --- a/cmd/kieserver/main.go +++ b/cmd/kieserver/main.go @@ -18,6 +18,7 @@ package main import ( + "github.com/apache/servicecomb-kie/server/cipher" "github.com/apache/servicecomb-kie/server/service" "os" @@ -101,6 +102,9 @@ func main() { if err := service.DBInit(); err != nil { openlogging.Fatal(err.Error()) } + if err := cipher.Init(); err != nil { + openlogging.Fatal(err.Error()) + } if err := chassis.Run(); err != nil { openlogging.Fatal("service exit: " + err.Error()) } diff --git a/examples/dev/kie-conf.yaml b/examples/dev/kie-conf.yaml index c9633fa8..959096a0 100644 --- a/examples/dev/kie-conf.yaml +++ b/examples/dev/kie-conf.yaml @@ -5,4 +5,7 @@ db: poolSize: 10 timeout: 5m sslEnabled: false - rootCAFile: /opt/kie/ca.crt \ No newline at end of file + rootCAFile: /opt/kie/ca.crt + +cipher: + name: noop \ No newline at end of file diff --git a/server/cipher/cipher.go b/server/cipher/cipher.go new file mode 100644 index 00000000..22efe0f4 --- /dev/null +++ b/server/cipher/cipher.go @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cipher + +import ( + "github.com/apache/servicecomb-kie/server/config" + "github.com/apache/servicecomb-kie/server/service" + "github.com/go-chassis/foundation/security" +) + +//See https://github.com/go-chassis/foundation/blob/master/security/cipher.go +var ciphers map[string]security.Cipher + +// Register is register crypto +func Register(name string, c security.Cipher) { + if ciphers == nil { + ciphers = make(map[string]security.Cipher) + } + ciphers[name] = c +} + +// Lookup is lookup crypto +func Lookup(name string) security.Cipher { + cipher, ok := ciphers[name] + if !ok { + cipher = &namedNoop{Name: name} + ciphers[name] = cipher + } + + return cipher +} + +// Init init crypto config +func Init() error { + if config.GetCrypto().Name == "" { + return nil + } + if service.KVService != nil { + service.KVService = newCipherKV(service.KVService) + } + if service.HistoryService != nil { + service.HistoryService = newCipherHistory(service.HistoryService) + } + + return nil +} diff --git a/server/cipher/cipher_test.go b/server/cipher/cipher_test.go new file mode 100644 index 00000000..f75ce808 --- /dev/null +++ b/server/cipher/cipher_test.go @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cipher + +import ( + "reflect" + "testing" +) + +func TestLookup(t *testing.T) { + type args struct { + name string + value string + } + tests := []struct { + name string + args args + want string + }{ + {"noop", args{"noop", "123"}, "123"}, + {"namedNoop", args{"not_implemented", "123"}, "123"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotCipher := Lookup(tt.args.name) + expect, _ := gotCipher.Encrypt(tt.args.value) + if !reflect.DeepEqual(expect, tt.want) { + t.Errorf("Lookup() = %v, want %v", expect, tt.want) + } + + expect, _ = gotCipher.Decrypt(tt.args.value) + if !reflect.DeepEqual(expect, tt.want) { + t.Errorf("Lookup() = %v, want %v", expect, tt.want) + } + }) + } +} + +type testCipher struct{} + +func (*testCipher) Encrypt(src string) (string, error) { + return src, nil +} + +func (*testCipher) Decrypt(src string) (string, error) { + return src, nil +} + +func TestRegister(t *testing.T) { + test := &testCipher{} + noop2 := &namedNoop{} + Register("test", test) + Register("noop2", noop2) + + act := Lookup("test") + if !reflect.DeepEqual(test, act) { + t.Errorf("Register() = %v, want %v", test, act) + } + act = Lookup("noop2") + if !reflect.DeepEqual(noop2, act) { + t.Errorf("Register() = %v, want %v", noop2, act) + } +} diff --git a/server/cipher/init.go b/server/cipher/init.go new file mode 100644 index 00000000..74da9a2e --- /dev/null +++ b/server/cipher/init.go @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cipher + +func init() { + Register("noop", &Noop{}) +} diff --git a/server/cipher/noop.go b/server/cipher/noop.go new file mode 100644 index 00000000..451d9de4 --- /dev/null +++ b/server/cipher/noop.go @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cipher + +import ( + "fmt" + "github.com/go-mesh/openlogging" +) + +// Noop is none implement +type Noop struct { +} + +// Encrypt implement +func (*Noop) Encrypt(src string) (string, error) { + return src, nil +} + +// Decrypt implement +func (*Noop) Decrypt(src string) (string, error) { + return src, nil +} + +type namedNoop struct { + Name string +} + +func (nn *namedNoop) Encrypt(src string) (string, error) { + openlogging.Warn(fmt.Sprintf("security name [%s] not implemented.", nn.Name)) + return src, nil +} + +func (nn *namedNoop) Decrypt(src string) (string, error) { + openlogging.Warn(fmt.Sprintf("security name [%s] not implemented.", nn.Name)) + return src, nil +} diff --git a/server/cipher/noop_test.go b/server/cipher/noop_test.go new file mode 100644 index 00000000..97f168b7 --- /dev/null +++ b/server/cipher/noop_test.go @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cipher + +import "testing" + +func TestNoop_Encrypt(t *testing.T) { + type args struct { + src string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + {"empty", args{""}, "", false}, + {"123", args{"123"}, "123", false}, + {"nil", args{}, "", false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := &Noop{} + got, err := n.Encrypt(tt.args.src) + if (err != nil) != tt.wantErr { + t.Errorf("Noop.Encrypt() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("Noop.Encrypt() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNoop_Decrypt(t *testing.T) { + type args struct { + src string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + {"empty", args{""}, "", false}, + {"123", args{"123"}, "123", false}, + {"nil", args{}, "", false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := &Noop{} + got, err := n.Decrypt(tt.args.src) + if (err != nil) != tt.wantErr { + t.Errorf("Noop.Decrypt() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("Noop.Decrypt() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_namedNoop_Encrypt(t *testing.T) { + type args struct { + src string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + {"empty", args{""}, "", false}, + {"123", args{"123"}, "123", false}, + {"nil", args{}, "", false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + nn := &namedNoop{ + Name: "any", + } + got, err := nn.Encrypt(tt.args.src) + if (err != nil) != tt.wantErr { + t.Errorf("namedNoop.Encrypt() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("namedNoop.Encrypt() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_namedNoop_Decrypt(t *testing.T) { + + type args struct { + src string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + {"empty", args{""}, "", false}, + {"123", args{"123"}, "123", false}, + {"nil", args{}, "", false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + nn := &namedNoop{ + Name: "any", + } + got, err := nn.Decrypt(tt.args.src) + if (err != nil) != tt.wantErr { + t.Errorf("namedNoop.Decrypt() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("namedNoop.Decrypt() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/server/cipher/service.go b/server/cipher/service.go new file mode 100644 index 00000000..e014a201 --- /dev/null +++ b/server/cipher/service.go @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cipher + +import ( + "context" + "github.com/apache/servicecomb-kie/pkg/model" + "github.com/apache/servicecomb-kie/server/config" + "github.com/apache/servicecomb-kie/server/service" + "github.com/go-chassis/foundation/security" +) + +func newCipherHistory(service service.History) service.History { + return &cryptoHistory{service: service} +} + +func newCipherKV(service service.KV) service.KV { + return &cryptoKV{service: service} +} + +// lookup Cipher +func lookupCrypto(unused *model.KVDoc) security.Cipher { + return Lookup(config.GetCrypto().Name) +} + +// History service security proxy +type cryptoHistory struct { + service service.History +} + +func (history *cryptoHistory) GetHistory(ctx context.Context, labelID string, options ...service.FindOption) ([]*model.LabelRevisionDoc, error) { + res, err := history.service.GetHistory(ctx, labelID, options...) + + cipher := lookupCrypto(nil) + + for i := 0; i < len(res); i++ { + doc := res[i] + for j := 0; j < len(doc.KVs); j++ { + kv := doc.KVs[j] + val, err := cipher.Decrypt(kv.Value) + if err != nil { + return nil, err + } + kv.Value = val + } + } + + return res, err +} + +// KV service security proxy +type cryptoKV struct { + service service.KV +} + +func (ckv *cryptoKV) CreateOrUpdate(ctx context.Context, kv *model.KVDoc) (*model.KVDoc, error) { + cipher := lookupCrypto(nil) + val, err := cipher.Encrypt(kv.Value) + if err != nil { + return nil, err + } + kv.Value = val + + res, err := ckv.service.CreateOrUpdate(ctx, kv) + if res == nil { + return res, err + } + + val, err = cipher.Decrypt(kv.Value) + if err != nil { + return nil, err + } + kv.Value = val + + return res, err +} + +func (ckv *cryptoKV) List(ctx context.Context, domain, project, key string, labels map[string]string, limit, offset int) (*model.KVResponse, error) { + res, err := ckv.service.List(ctx, domain, project, key, labels, limit, offset) + cipher := lookupCrypto(nil) + if res == nil { + return res, err + } + + for j := 0; j < len(res.Data); j++ { + kv := res.Data[j] + val, err := cipher.Decrypt(kv.Value) + if err != nil { + return nil, err + } + kv.Value = val + } + + return res, err +} + +func (ckv *cryptoKV) Delete(kvID string, labelID string, domain, project string) error { + return ckv.Delete(kvID, labelID, domain, project) +} + +func (ckv *cryptoKV) FindKV(ctx context.Context, domain, project string, options ...service.FindOption) ([]*model.KVResponse, error) { + res, err := ckv.service.FindKV(ctx, domain, project, options...) + cipher := lookupCrypto(nil) + if res == nil { + return res, err + } + + for i := 0; i < len(res); i++ { + doc := res[i] + for j := 0; j < len(doc.Data); j++ { + kv := doc.Data[j] + val, err := cipher.Decrypt(kv.Value) + if err != nil { + return nil, err + } + kv.Value = val + } + } + + return res, err +} diff --git a/server/config/config.go b/server/config/config.go index de573d04..7be31975 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -42,3 +42,8 @@ func Init(file string) error { func GetDB() DB { return Configurations.DB } + +//GetCrypto return crypto configs +func GetCrypto() Cipher { + return Configurations.Cipher +} diff --git a/server/config/config_test.go b/server/config/config_test.go index 75ca3aea..6e0a4d45 100644 --- a/server/config/config_test.go +++ b/server/config/config_test.go @@ -37,7 +37,8 @@ db: ssl: false sslCA: sslCert: - +cipher: + name: noop `) defer os.Remove("test.yaml") f1, err := os.Create("test.yaml") @@ -48,4 +49,5 @@ db: assert.NoError(t, err) assert.Equal(t, 10, config.GetDB().PoolSize) assert.Equal(t, "mongodb://admin:123@127.0.0.1:27017/kie", config.GetDB().URI) + assert.Equal(t, "noop", config.GetCrypto().Name) } diff --git a/server/config/struct.go b/server/config/struct.go index d3ad4dad..a4a0beb1 100644 --- a/server/config/struct.go +++ b/server/config/struct.go @@ -19,7 +19,8 @@ package config //Config is yaml file struct type Config struct { - DB DB `yaml:"db"` + DB DB `yaml:"db"` + Cipher Cipher `yaml:"cipher"` } //DB is yaml file struct to set mongodb config @@ -30,3 +31,8 @@ type DB struct { RootCA string `yaml:"rootCAFile"` Timeout string `yaml:"timeout"` } + +// Cipher is yaml file struct to set crypto config +type Cipher struct { + Name string `yaml:"name"` +} diff --git a/server/resource/v1/history_resource_test.go b/server/resource/v1/history_resource_test.go index c4316148..bf2b6af8 100644 --- a/server/resource/v1/history_resource_test.go +++ b/server/resource/v1/history_resource_test.go @@ -19,6 +19,7 @@ package v1_test import ( "context" "encoding/json" + "github.com/apache/servicecomb-kie/server/cipher" "github.com/apache/servicecomb-kie/server/service" "io/ioutil" @@ -42,7 +43,8 @@ import ( var _ = Describe("v1 history resource", func() { config.Configurations = &config.Config{ - DB: config.DB{}, + DB: config.DB{}, + Cipher: config.Cipher{}, } Describe("get history revisions", func() { @@ -51,6 +53,10 @@ var _ = Describe("v1 history resource", func() { It("should not return err", func() { Expect(err).Should(BeNil()) }) + err = cipher.Init() + It("should not return err", func() { + Expect(err).Should(BeNil()) + }) Context("valid param", func() { kv := &model.KVDoc{ Key: "test", @@ -61,7 +67,11 @@ var _ = Describe("v1 history resource", func() { Domain: "default", Project: "test", } - kv, _ = service.KVService.CreateOrUpdate(context.Background(), kv) + kv, err = service.KVService.CreateOrUpdate(context.Background(), kv) + It("should not return err or nil", func() { + Expect(err).Should(BeNil()) + }) + path := fmt.Sprintf("/v1/%s/kie/revision/%s", "test", kv.LabelID) r, _ := http.NewRequest("GET", path, nil) revision := &v1.HistoryResource{} diff --git a/server/resource/v1/kv_resource.go b/server/resource/v1/kv_resource.go index b8e20104..dba600ea 100644 --- a/server/resource/v1/kv_resource.go +++ b/server/resource/v1/kv_resource.go @@ -120,7 +120,7 @@ func (r *KVResource) List(rctx *restful.Context) { return } var limit int64 = 20 - var offset int64 = 0 + var offset int64 labels := make(map[string]string, 0) var err error for k, v := range rctx.ReadRequest().URL.Query() { diff --git a/server/resource/v1/kv_resource_test.go b/server/resource/v1/kv_resource_test.go index 5fe878b1..75eb1229 100644 --- a/server/resource/v1/kv_resource_test.go +++ b/server/resource/v1/kv_resource_test.go @@ -20,6 +20,7 @@ package v1_test import ( "bytes" "encoding/json" + "github.com/apache/servicecomb-kie/server/cipher" "github.com/apache/servicecomb-kie/server/service" "io/ioutil" "log" @@ -42,13 +43,18 @@ import ( var _ = Describe("v1 kv resource", func() { //for UT config.Configurations = &config.Config{ - DB: config.DB{}, + DB: config.DB{}, + Cipher: config.Cipher{Name: "not_implemented"}, } config.Configurations.DB.URI = "mongodb://kie:123@127.0.0.1:27017" err := service.DBInit() if err != nil { panic(err) } + err = cipher.Init() + if err != nil { + panic(err) + } Describe("put kv", func() { Context("valid param", func() { kv := &model.KVDoc{ diff --git a/server/service/mongo/session/session.go b/server/service/mongo/session/session.go index 1eb43e23..17f15cff 100644 --- a/server/service/mongo/session/session.go +++ b/server/service/mongo/session/session.go @@ -88,7 +88,8 @@ func Init() error { RegisterEncoder(reflect.TypeOf(model.KVDoc{}), sc). RegisterEncoder(reflect.TypeOf(model.LabelRevisionDoc{}), sc). Build() - clientOps := []*options.ClientOptions{options.Client().ApplyURI(config.GetDB().URI)} + opt := options.Client().ApplyURI(config.GetDB().URI).SetRegistry(reg) + clientOps := []*options.ClientOptions{opt} if config.GetDB().SSLEnabled { if config.GetDB().RootCA == "" { err = ErrRootCAMissing