Skip to content

Commit

Permalink
Merge pull request #108 from nitrictech/feature/secret-aws
Browse files Browse the repository at this point in the history
Add AWS secrets Manager plugin.
  • Loading branch information
tjholm authored Aug 4, 2021
2 parents 2da56cc + 762b854 commit 8af4380
Show file tree
Hide file tree
Showing 5 changed files with 525 additions and 1 deletion.
4 changes: 3 additions & 1 deletion makefile
Original file line number Diff line number Diff line change
Expand Up @@ -200,4 +200,6 @@ do-docker: do-docker-static
generate-mocks:
@echo Generating Mock Clients
@mkdir -p mocks/mock_secret_manager
@go run github.com/golang/mock/mockgen github.com/nitric-dev/membrane/pkg/plugins/secret/secret_manager SecretManagerClient > mocks/mock_secret_manager/mock.go
@mkdir -p mocks/secrets_manager
@go run github.com/golang/mock/mockgen github.com/nitric-dev/membrane/pkg/plugins/secret/secret_manager SecretManagerClient > mocks/mock_secret_manager/mock.go
@go run github.com/golang/mock/mockgen github.com/aws/aws-sdk-go/service/secretsmanager/secretsmanageriface SecretsManagerAPI > mocks/secrets_manager/mock.go
168 changes: 168 additions & 0 deletions pkg/plugins/secret/secrets_manager/secrets_manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// 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 secrets_manager_secret_service

import (
"fmt"
"strings"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/session"
secretsmanager "github.com/aws/aws-sdk-go/service/secretsmanager"
"github.com/aws/aws-sdk-go/service/secretsmanager/secretsmanageriface"
"github.com/nitric-dev/membrane/pkg/plugins"
"github.com/nitric-dev/membrane/pkg/plugins/secret"
"github.com/nitric-dev/membrane/pkg/utils"
)

type secretsManagerSecretService struct {
secret.UnimplementedSecretPlugin
client secretsmanageriface.SecretsManagerAPI
}

func (s *secretsManagerSecretService) validateNewSecret(sec *secret.Secret, val []byte) error {
if sec == nil {
return plugins.NewInvalidArgError("provide non-empty secret")
}
if len(sec.Name) == 0 {
return plugins.NewInvalidArgError("provide non-empty secret name")
}
if len(val) == 0 {
return plugins.NewInvalidArgError("provide non-empty secret value")
}

return nil
}

func (s *secretsManagerSecretService) Put(sec *secret.Secret, val []byte) (*secret.SecretPutResponse, error) {
if err := s.validateNewSecret(sec, val); err != nil {
return nil, err
}

_, err := s.client.GetSecretValue(&secretsmanager.GetSecretValueInput{
SecretId: &sec.Name,
})

// There was not existing secret value
// Need to determine if non-existent
if err != nil {
if awsErr, ok := err.(awserr.Error); ok {
// process SDK error

switch awsErr.Code() {
case secretsmanager.ErrCodeResourceNotFoundException:
// Create the secret
result, err := s.client.CreateSecret(&secretsmanager.CreateSecretInput{
Name: aws.String(sec.Name),
SecretBinary: val,
})

if err != nil {
return nil, fmt.Errorf("failed to put secret: %v", err)
}

return &secret.SecretPutResponse{
SecretVersion: &secret.SecretVersion{
Secret: &secret.Secret{
Name: aws.StringValue(result.Name),
},
Version: aws.StringValue(result.VersionId),
},
}, nil
default:
// Return the error
return nil, fmt.Errorf("failed to retrieve secret: %v", awsErr)
}
} else {
// Not an AWS error but still an error...
return nil, fmt.Errorf("failed to retrieve secret: %v", err)
}
} else {
// Create a new version for an existing secret
result, err := s.client.PutSecretValue(&secretsmanager.PutSecretValueInput{
SecretId: aws.String(sec.Name),
SecretBinary: val,
})

if err != nil {
return nil, fmt.Errorf("failed to put secret: %v", err)
}

return &secret.SecretPutResponse{
SecretVersion: &secret.SecretVersion{
Secret: &secret.Secret{
Name: aws.StringValue(result.Name),
},
Version: aws.StringValue(result.VersionId),
},
}, nil
}
}

func (s *secretsManagerSecretService) Access(sv *secret.SecretVersion) (*secret.SecretAccessResponse, error) {
if len(sv.Secret.Name) == 0 {
return nil, plugins.NewInvalidArgError("provide non-empty secret name")
}
if len(sv.Version) == 0 {
return nil, plugins.NewInvalidArgError("provide non-empty version")
}

//Build the request to get the secret
input := &secretsmanager.GetSecretValueInput{
SecretId: aws.String(sv.Secret.Name),
}

// If the requested version is latest then we want
// to exclude the version from input
if strings.ToLower(sv.Version) != "latest" {
input.VersionId = aws.String(sv.Version)
}

result, err := s.client.GetSecretValue(input)

if err != nil {
return nil, fmt.Errorf("failed to access secret version: %v", err)
}

return &secret.SecretAccessResponse{
SecretVersion: &secret.SecretVersion{
Secret: &secret.Secret{
Name: aws.StringValue(result.Name),
},
Version: aws.StringValue(result.VersionId),
},
Value: []byte(result.SecretBinary),
}, nil
}

//Gets a new Secrets Manager Client
func New() (secret.SecretService, error) {
awsRegion := utils.GetEnv("AWS_REGION", "us-east-1")

sess, sessionError := session.NewSession(&aws.Config{
Region: aws.String(awsRegion),
})

if sessionError != nil {
return nil, fmt.Errorf("error creating new AWS session %v", sessionError)
}

client := secretsmanager.New(sess)

return &secretsManagerSecretService{
client: client,
}, nil
}
27 changes: 27 additions & 0 deletions pkg/plugins/secret/secrets_manager/secrets_manager_suite_test.go
Original file line number Diff line number Diff line change
@@ -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 secrets_manager_secret_service

import (
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

func TestPubsub(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Secrets Manager Suite")
}
Loading

0 comments on commit 8af4380

Please sign in to comment.