This is an automated email from the ASF dual-hosted git repository. smihaylov pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/incubator-milagro-dta.git
commit dffff97e82de8d7dcf701a8687d622bc7856d55d Author: Stanislav Mihaylov <[email protected]> AuthorDate: Wed Sep 25 11:06:40 2019 +0300 KeyStore interface to keep secrets File KeyStore implementation to keep secrets in a json file --- libs/keystore/filestore.go | 111 ++++++++++++++++++++++++++++++++++++++ libs/keystore/filestore_test.go | 85 +++++++++++++++++++++++++++++ libs/keystore/keystore.go | 36 +++++++++++++ libs/keystore/memorystore.go | 59 ++++++++++++++++++++ libs/keystore/memorystore_test.go | 46 ++++++++++++++++ 5 files changed, 337 insertions(+) diff --git a/libs/keystore/filestore.go b/libs/keystore/filestore.go new file mode 100644 index 0000000..5491563 --- /dev/null +++ b/libs/keystore/filestore.go @@ -0,0 +1,111 @@ +// 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 keystore + +import ( + "encoding/json" + "io/ioutil" + "os" + "sync" + + "github.com/pkg/errors" +) + +// FileStore is the key Store implementation storing the keys in a file +type FileStore struct { + sync.RWMutex + filePath string + keys map[string][]byte +} + +// NewFileStore creates a new FileStore +func NewFileStore(filePath string) (Store, error) { + fs := &FileStore{ + filePath: filePath, + keys: map[string][]byte{}, + } + + if err := fs.loadKeys(); err != nil { + return nil, err + } + + return fs, nil +} + +// Set stores multiple keys at once +func (f *FileStore) Set(name string, key []byte) error { + f.Lock() + defer f.Unlock() + + f.keys[name] = make([]byte, len(key)) + copy(f.keys[name], key) + + return f.storeKeys() +} + +// Get retrieves multiple keys +func (f *FileStore) Get(name string) ([]byte, error) { + f.RLock() + defer f.RUnlock() + + key, ok := f.keys[name] + if !ok { + return nil, ErrKeyNotFound + } + + return key, nil +} + +// TODO: Lock the file + +func (f *FileStore) loadKeys() error { + rawKeys, err := ioutil.ReadFile(f.filePath) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return errors.Wrap(err, "Load keys") + } + + return json.Unmarshal(rawKeys, &(f).keys) +} + +func (f *FileStore) storeKeys() error { + rawKeys, err := json.Marshal(f.keys) + if err != nil { + return err + } + + // Get the file permissions + var perm os.FileMode + fi, err := os.Stat(f.filePath) + if err != nil { + if !os.IsNotExist(err) { + return errors.Wrap(err, "Get key file permissions") + } + perm = 0600 + } else { + perm = fi.Mode().Perm() + } + + if err := ioutil.WriteFile(f.filePath, rawKeys, perm); err != nil { + return errors.Wrap(err, "Store keys") + } + + return nil +} diff --git a/libs/keystore/filestore_test.go b/libs/keystore/filestore_test.go new file mode 100644 index 0000000..7e946dd --- /dev/null +++ b/libs/keystore/filestore_test.go @@ -0,0 +1,85 @@ +// 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 keystore + +import ( + "bytes" + "crypto/rand" + "fmt" + "os" + "path/filepath" + "testing" + "time" +) + +func TestFileStore(t *testing.T) { + keys := map[string][]byte{"key1": []byte{1}, "key2": []byte{1, 2}, "key3": []byte{1, 2, 3}} + + fn := tmpFileName() + defer func() { + if err := os.Remove(fn); err != nil { + t.Logf("Warning! Temp file could not be deleted (%v): %v", err, fn) + } + }() + + fs, err := NewFileStore(fn) + if err != nil { + t.Fatal(err) + } + + // Set keys + for k, v := range keys { + fs.Set(k, v) + } + + // Get Keys + for name, v := range keys { + key, err := fs.Get(name) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(v, key) { + t.Errorf("Key not match: %v. Expected: %v, Found: %v", name, v, key) + } + + } + + fs1, err := NewFileStore(fn) + if err != nil { + t.Fatal(err) + } + // Get Keys + for name, v := range keys { + key, err := fs1.Get(name) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(v, key) { + t.Errorf("Key not match: %v. Expected: %v, Found: %v", name, v, key) + } + + } +} + +func tmpFileName() string { + rnd := make([]byte, 8) + rand.Read(rnd) + ts := time.Now().UnixNano() + + return filepath.Join(os.TempDir(), fmt.Sprintf("keystore-%v-%x.tmp", ts, rnd)) +} diff --git a/libs/keystore/keystore.go b/libs/keystore/keystore.go new file mode 100644 index 0000000..8b321c7 --- /dev/null +++ b/libs/keystore/keystore.go @@ -0,0 +1,36 @@ +// 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 keystore - keep secrets +*/ +package keystore + +import "github.com/pkg/errors" + +var ( + // ErrKeyNotFound is returned when a key is not found in the store + ErrKeyNotFound = errors.New("Key not found") +) + +// Store is the keystore interface +type Store interface { + // Set stores multiple keys at once + Set(name string, key []byte) error + // Get retrieves multiple keys + Get(name string) ([]byte, error) +} diff --git a/libs/keystore/memorystore.go b/libs/keystore/memorystore.go new file mode 100644 index 0000000..484a45f --- /dev/null +++ b/libs/keystore/memorystore.go @@ -0,0 +1,59 @@ +// 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 keystore + +import ( + "sync" +) + +// MemoryStore is the in-memory implementation of key store +type MemoryStore struct { + sync.RWMutex + keys map[string][]byte +} + +// NewMemoryStore creates a new MemoryStore +func NewMemoryStore() (Store, error) { + return &MemoryStore{ + keys: map[string][]byte{}, + }, nil +} + +// Set stores multiple keys at once +func (f *MemoryStore) Set(name string, key []byte) error { + f.Lock() + defer f.Unlock() + + f.keys[name] = make([]byte, len(key)) + copy(f.keys[name], key) + + return nil +} + +// Get retrieves multiple keys +func (f *MemoryStore) Get(name string) ([]byte, error) { + f.RLock() + defer f.RUnlock() + + key, ok := f.keys[name] + if !ok { + return nil, ErrKeyNotFound + } + + return key, nil +} diff --git a/libs/keystore/memorystore_test.go b/libs/keystore/memorystore_test.go new file mode 100644 index 0000000..5dd92a2 --- /dev/null +++ b/libs/keystore/memorystore_test.go @@ -0,0 +1,46 @@ +// 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 keystore + +import ( + "bytes" + "testing" +) + +func TestMemoryStore(t *testing.T) { + keys := map[string][]byte{"key1": []byte{1}, "key2": []byte{1, 2}, "key3": []byte{1, 2, 3}} + + ms, err := NewMemoryStore() + if err != nil { + t.Fatal(err) + } + for k, v := range keys { + ms.Set(k, v) + } + + for name, v := range keys { + key, err := ms.Get(name) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(v, key) { + t.Errorf("Key not match: %v. Expected: %v, Found: %v", name, v, key) + } + + } +}
