From 7cb1b762d1cca37408525a46f7db40ac7e777a2a Mon Sep 17 00:00:00 2001 From: Tim Holm Date: Tue, 3 Aug 2021 11:54:11 +1000 Subject: [PATCH] feat(plugins/secret): Add local dev secret plugin. Co-authored-by: Ryan Cartwright --- go.mod | 2 +- go.sum | 2 + pkg/plugins/secret/dev/dev.go | 123 +++++++++++++++++++ pkg/plugins/secret/dev/dev_suite_test.go | 27 +++++ pkg/plugins/secret/dev/dev_test.go | 148 +++++++++++++++++++++++ pkg/providers/dev/membrane.go | 3 + 6 files changed, 304 insertions(+), 1 deletion(-) create mode 100644 pkg/plugins/secret/dev/dev.go create mode 100644 pkg/plugins/secret/dev/dev_suite_test.go create mode 100644 pkg/plugins/secret/dev/dev_test.go diff --git a/go.mod b/go.mod index f19e83727..8ddc6e25e 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/aws/aws-sdk-go v1.36.12 github.com/golang/protobuf v1.5.2 github.com/golang/snappy v0.0.3 // indirect - github.com/google/addlicense v0.0.0-20210727174409-874627749a46 + github.com/google/addlicense v0.0.0-20210729153508-ef04bb38a16b github.com/google/uuid v1.1.2 github.com/googleapis/gax-go/v2 v2.0.5 github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25 // indirect diff --git a/go.sum b/go.sum index 39ee0c250..7b2c737a6 100644 --- a/go.sum +++ b/go.sum @@ -155,6 +155,8 @@ github.com/google/addlicense v0.0.0-20210428195630-6d92264d7170 h1:jLUa4MO3autxl github.com/google/addlicense v0.0.0-20210428195630-6d92264d7170/go.mod h1:EMjYTRimagHs1FwlIqKyX3wAM0u3rA+McvlIIWmSamA= github.com/google/addlicense v0.0.0-20210727174409-874627749a46 h1:1locMH9PVZH3LXvogcvdTxf2/9J4YT/9W3BSXrTN4/U= github.com/google/addlicense v0.0.0-20210727174409-874627749a46/go.mod h1:EMjYTRimagHs1FwlIqKyX3wAM0u3rA+McvlIIWmSamA= +github.com/google/addlicense v0.0.0-20210729153508-ef04bb38a16b h1:KwI0NOpYd3rzKojfjeRerF7rzjeTwvJARVsgGf5TWmY= +github.com/google/addlicense v0.0.0-20210729153508-ef04bb38a16b/go.mod h1:EMjYTRimagHs1FwlIqKyX3wAM0u3rA+McvlIIWmSamA= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= diff --git a/pkg/plugins/secret/dev/dev.go b/pkg/plugins/secret/dev/dev.go new file mode 100644 index 000000000..222b99363 --- /dev/null +++ b/pkg/plugins/secret/dev/dev.go @@ -0,0 +1,123 @@ +// Copyright 2021 Nitric Pty Ltd. +// +// Licensed 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 secret_service + +import ( + "bufio" + "fmt" + "io/ioutil" + "os" + "strings" + + "github.com/google/uuid" + "github.com/nitric-dev/membrane/pkg/plugins/secret" + "github.com/nitric-dev/membrane/pkg/utils" +) + +const DEFAULT_DIR = ".nitric/secrets/" + +type DevSecretService struct { + secret.UnimplementedSecretPlugin + secDir string +} + +func (s *DevSecretService) secretFileName(sec *secret.Secret, v string) string { + return fmt.Sprintf("%s/%s_%s.txt", s.secDir, sec.Name, v) +} + +func (s *DevSecretService) Put(sec *secret.Secret, val []byte) (*secret.SecretPutResponse, error) { + if sec == nil { + return nil, fmt.Errorf("provide non-empty secret") + } + if len(sec.Name) == 0 { + return nil, fmt.Errorf("provide non-blank secret name") + } + if len(val) == 0 { + return nil, fmt.Errorf("provide non-blank secret value") + } + + var versionId = uuid.New().String() + //Creates a new file in the form: + // DIR/Name_Version.txt + file, err := os.Create(s.secretFileName(sec, versionId)) + if err != nil { + return nil, fmt.Errorf("error creating secret store: %v", err) + } + writer := bufio.NewWriter(file) + writer.WriteString(string(val)) + writer.Flush() + + //Creates a new file as latest + latestFile, err := os.Create(s.secretFileName(sec, "latest")) + if err != nil { + return nil, fmt.Errorf("error creating latest secret: %v", err) + } + latestWriter := bufio.NewWriter(latestFile) + latestWriter.WriteString(string(val)) + latestWriter.WriteString("," + versionId) + latestWriter.Flush() + + return &secret.SecretPutResponse{ + SecretVersion: &secret.SecretVersion{ + Secret: &secret.Secret{ + Name: sec.Name, + }, + Version: versionId, + }, + }, nil +} + +func (s *DevSecretService) Access(sv *secret.SecretVersion) (*secret.SecretAccessResponse, error) { + if sv.Secret.Name == "" { + return nil, fmt.Errorf("provide non-blank name") + } + if sv.Version == "" { + return nil, fmt.Errorf("provide non-blank version") + } + + content, err := ioutil.ReadFile(s.secretFileName(sv.Secret, sv.Version)) + if err != nil { + return nil, fmt.Errorf("error reading secret store: %v", err) + } + + splitContent := strings.Split(string(content), ",") + return &secret.SecretAccessResponse{ + SecretVersion: &secret.SecretVersion{ + Secret: &secret.Secret{ + Name: sv.Secret.Name, + }, + Version: splitContent[len(splitContent)-1], + }, + Value: []byte(splitContent[0]), + }, nil +} + +//Create new secret store +func New() (secret.SecretService, error) { + secDir := utils.GetEnv("LOCAL_SEC_DIR", DEFAULT_DIR) + + //Check whether file exists + _, err := os.Stat(secDir) + if os.IsNotExist(err) { + //Make directory if not present + err := os.MkdirAll(secDir, 0777) + if err != nil { + return nil, err + } + } + return &DevSecretService{ + secDir: secDir, + }, nil +} diff --git a/pkg/plugins/secret/dev/dev_suite_test.go b/pkg/plugins/secret/dev/dev_suite_test.go new file mode 100644 index 000000000..d7420c789 --- /dev/null +++ b/pkg/plugins/secret/dev/dev_suite_test.go @@ -0,0 +1,27 @@ +// Copyright 2021 Nitric Pty Ltd. +// +// Licensed 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 secret_service_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestPubsub(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Dev Secret Plugin Suite") +} diff --git a/pkg/plugins/secret/dev/dev_test.go b/pkg/plugins/secret/dev/dev_test.go new file mode 100644 index 000000000..df1c1d6f7 --- /dev/null +++ b/pkg/plugins/secret/dev/dev_test.go @@ -0,0 +1,148 @@ +// Copyright 2021 Nitric Pty Ltd. +// +// Licensed 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 secret_service_test + +import ( + "os" + + "github.com/nitric-dev/membrane/pkg/plugins/secret" + secretPlugin "github.com/nitric-dev/membrane/pkg/plugins/secret/dev" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Dev Secret Manager", func() { + os.Setenv("LOCAL_SEC_DIR", "./.nitric/") + + AfterSuite(func() { + // Cleanup default secrect directory + os.RemoveAll("./.nitric/") + }) + + testSecret := secret.Secret{ + Name: "Test", + } + testSecretVal := []byte("Super Secret Message") + When("Put", func() { + When("Putting a secret to a non-existent secret", func() { + secretPlugin, _ := secretPlugin.New() + It("Should successfully store a secret", func() { + response, err := secretPlugin.Put(&testSecret, testSecretVal) + By("Not returning an error") + Expect(err).ShouldNot(HaveOccurred()) + + By("Returning a non nil response") + Expect(response).ShouldNot(BeNil()) + }) + }) + When("Putting a secret to an existing secret", func() { + secretPlugin, _ := secretPlugin.New() + It("Should succesfully store a secret", func() { + response, err := secretPlugin.Put(&testSecret, testSecretVal) + By("Not returning an error") + Expect(err).ShouldNot(HaveOccurred()) + + By("Returning a non nil response") + Expect(response).ShouldNot(BeNil()) + }) + }) + When("Putting a secret with an empty name", func() { + secretPlugin, _ := secretPlugin.New() + It("Should throw an error", func() { + emptySecretName := &secret.Secret{} + response, err := secretPlugin.Put(emptySecretName, testSecretVal) + By("Returning an error") + Expect(err).Should(HaveOccurred()) + + By("Returning a nil response") + Expect(response).Should(BeNil()) + }) + }) + }) + When("Get", func() { + When("Getting a secret that exists", func() { + secretPlugin, _ := secretPlugin.New() + It("Should return the secret", func() { + putResponse, _ := secretPlugin.Put(&testSecret, testSecretVal) + response, err := secretPlugin.Access(putResponse.SecretVersion) + By("Not returning an error") + Expect(err).ShouldNot(HaveOccurred()) + By("Returning a response") + Expect(response.SecretVersion.Secret.Name).Should(Equal(testSecret.Name)) + Expect(response.Value).Should(Equal(testSecretVal)) + }) + }) + When("Getting the latest secret", func() { + secretPlugin, _ := secretPlugin.New() + It("Should return the latest secret", func() { + putResponse, _ := secretPlugin.Put(&testSecret, testSecretVal) + response, err := secretPlugin.Access(&secret.SecretVersion{ + Secret: &secret.Secret{ + Name: testSecret.Name, + }, + Version: "latest", + }) + By("Not returning an error") + Expect(err).ShouldNot(HaveOccurred()) + By("Returning a response") + Expect(response.SecretVersion.Secret.Name).Should(Equal(testSecret.Name)) + Expect(response.SecretVersion.Version).Should(Equal(putResponse.SecretVersion.Version)) + Expect(response.Value).Should(Equal(testSecretVal)) + }) + }) + When("Getting a secret that doesn't exist", func() { + secretPlugin, _ := secretPlugin.New() + It("Should return an error", func() { + response, err := secretPlugin.Access(&secret.SecretVersion{ + Secret: &secret.Secret{ + Name: "test-id", + }, + Version: "test-version-id", + }) + By("Returning an error") + Expect(err).Should(HaveOccurred()) + By("Returning a nil response") + Expect(response).Should(BeNil()) + }) + }) + When("Getting a secret with an empty id", func() { + secretPlugin, _ := secretPlugin.New() + It("Should return an error", func() { + response, err := secretPlugin.Access(&secret.SecretVersion{ + Secret: &secret.Secret{}, + Version: "test-version-id", + }) + By("Returning an error") + Expect(err).Should(HaveOccurred()) + By("Returning a nil response") + Expect(response).Should(BeNil()) + }) + }) + When("Getting a secret with an empty version id", func() { + secretPlugin, _ := secretPlugin.New() + It("Should return an error", func() { + response, err := secretPlugin.Access(&secret.SecretVersion{ + Secret: &secret.Secret{ + Name: "test-id", + }, + }) + By("Returning an error") + Expect(err).Should(HaveOccurred()) + By("Returning a nil response") + Expect(response).Should(BeNil()) + }) + }) + }) +}) diff --git a/pkg/providers/dev/membrane.go b/pkg/providers/dev/membrane.go index a31ee1da4..1bfc3c631 100644 --- a/pkg/providers/dev/membrane.go +++ b/pkg/providers/dev/membrane.go @@ -27,6 +27,7 @@ import ( gateway_plugin "github.com/nitric-dev/membrane/pkg/plugins/gateway/dev" queue_service "github.com/nitric-dev/membrane/pkg/plugins/queue/dev" boltdb_storage_service "github.com/nitric-dev/membrane/pkg/plugins/storage/boltdb" + secret_service "github.com/nitric-dev/membrane/pkg/plugins/secret/dev" ) func main() { @@ -35,6 +36,7 @@ func main() { signal.Notify(term, os.Interrupt, syscall.SIGTERM) signal.Notify(term, os.Interrupt, syscall.SIGINT) + secretPlugin, _ := secret_service.New() documentPlugin, _ := boltdb_service.New() eventsPlugin, _ := events_service.New() gatewayPlugin, _ := gateway_plugin.New() @@ -47,6 +49,7 @@ func main() { GatewayPlugin: gatewayPlugin, QueuePlugin: queuePlugin, StoragePlugin: storagePlugin, + SecretPlugin: secretPlugin, }) if err != nil {