Skip to content

Commit

Permalink
fix(gcp secrets): use tags to find the correct secret
Browse files Browse the repository at this point in the history
  • Loading branch information
asalkeld committed Jun 8, 2022
1 parent 8a277d1 commit cd2e061
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 170 deletions.
2 changes: 1 addition & 1 deletion makefile
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ generate-mocks:
@go run github.com/golang/mock/mockgen github.com/nitrictech/nitric/pkg/providers/azure/core AzProvider > mocks/provider/azure.go
@go run github.com/golang/mock/mockgen github.com/nitrictech/nitric/pkg/api/nitric/v1 FaasService_TriggerStreamServer > mocks/nitric/mock.go
@go run github.com/golang/mock/mockgen sync Locker > mocks/sync/mock.go
@go run github.com/golang/mock/mockgen github.com/nitrictech/nitric/pkg/plugins/secret/secret_manager SecretManagerClient > mocks/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
@go run github.com/golang/mock/mockgen github.com/nitrictech/nitric/pkg/plugins/storage/azblob/iface AzblobServiceUrlIface,AzblobContainerUrlIface,AzblobBlockBlobUrlIface,AzblobDownloadResponse > mocks/azblob/mock.go
@go run github.com/golang/mock/mockgen github.com/nitrictech/nitric/pkg/plugins/secret/key_vault KeyVaultClient > mocks/key_vault/mock.go
Expand All @@ -173,5 +172,6 @@ generate-mocks:
@go run github.com/golang/mock/mockgen github.com/Azure/azure-sdk-for-go/services/eventgrid/mgmt/2020-06-01/eventgrid/eventgridapi TopicsClientAPI > mocks/mock_event_grid/topic.go
@go run github.com/golang/mock/mockgen github.com/nitrictech/nitric/pkg/plugins/queue/azqueue/iface AzqueueServiceUrlIface,AzqueueQueueUrlIface,AzqueueMessageUrlIface,AzqueueMessageIdUrlIface,DequeueMessagesResponseIface > mocks/azqueue/mock.go
@go run github.com/golang/mock/mockgen github.com/nitrictech/nitric/pkg/ifaces/gcloud_storage Reader,Writer,ObjectHandle,BucketHandle,BucketIterator,StorageClient,ObjectIterator > mocks/gcp_storage/mock.go
@go run github.com/golang/mock/mockgen github.com/nitrictech/nitric/pkg/ifaces/gcloud_secret SecretManagerClient,SecretIterator > mocks/gcp_secret/mock.go

generate-sources: generate-proto generate-mocks
76 changes: 47 additions & 29 deletions mocks/secret_manager/mock.go → mocks/gcp_secret/mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 51 additions & 0 deletions pkg/ifaces/gcloud_secret/adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 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 ifaces_gcloud_secret

import (
"context"

secretmanager "cloud.google.com/go/secretmanager/apiv1"
gax "github.com/googleapis/gax-go/v2"
secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
)

type realClient struct {
*secretmanager.Client
}

func NewClient(ctx context.Context) (SecretManagerClient, error) {
c, err := secretmanager.NewClient(ctx)
if err != nil {
return nil, err
}
return &realClient{Client: c}, nil
}

func (r *realClient) AccessSecretVersion(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, co ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error) {
return r.Client.AccessSecretVersion(ctx, req, co...)
}

func (r *realClient) AddSecretVersion(ctx context.Context, req *secretmanagerpb.AddSecretVersionRequest, co ...gax.CallOption) (*secretmanagerpb.SecretVersion, error) {
return r.Client.AddSecretVersion(ctx, req, co...)
}

func (r *realClient) UpdateSecret(ctx context.Context, req *secretmanagerpb.UpdateSecretRequest, co ...gax.CallOption) (*secretmanagerpb.Secret, error) {
return r.Client.UpdateSecret(ctx, req, co...)
}

func (r *realClient) ListSecrets(ctx context.Context, req *secretmanagerpb.ListSecretsRequest, co ...gax.CallOption) SecretIterator {
return r.Client.ListSecrets(ctx, req, co...)
}
33 changes: 33 additions & 0 deletions pkg/ifaces/gcloud_secret/iface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// 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 ifaces_gcloud_secret

import (
"context"

gax "github.com/googleapis/gax-go/v2"
secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
)

type SecretIterator interface {
Next() (*secretmanagerpb.Secret, error)
}

type SecretManagerClient interface {
AccessSecretVersion(context.Context, *secretmanagerpb.AccessSecretVersionRequest, ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error)
AddSecretVersion(context.Context, *secretmanagerpb.AddSecretVersionRequest, ...gax.CallOption) (*secretmanagerpb.SecretVersion, error)
UpdateSecret(context.Context, *secretmanagerpb.UpdateSecretRequest, ...gax.CallOption) (*secretmanagerpb.Secret, error)
ListSecrets(ctx context.Context, req *secretmanagerpb.ListSecretsRequest, opts ...gax.CallOption) SecretIterator
}
96 changes: 33 additions & 63 deletions pkg/plugins/secret/secret_manager/secret_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,31 +20,25 @@ import (
"strings"

secretmanager "cloud.google.com/go/secretmanager/apiv1"
gax "github.com/googleapis/gax-go/v2"
"golang.org/x/oauth2/google"
"google.golang.org/api/iterator"
secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
pbcodes "google.golang.org/grpc/codes"
grpcCodes "google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

ifaces_gcloud_secret "github.com/nitrictech/nitric/pkg/ifaces/gcloud_secret"
"github.com/nitrictech/nitric/pkg/plugins/errors"
"github.com/nitrictech/nitric/pkg/plugins/errors/codes"
"github.com/nitrictech/nitric/pkg/plugins/secret"
"github.com/nitrictech/nitric/pkg/utils"
)

// SecretManagerClient - iface that exposes utilized subset of generated SecretManagerServiceClient
// Used with gomock to assert create client -> service interaction in unit tests
type SecretManagerClient interface {
AccessSecretVersion(context.Context, *secretmanagerpb.AccessSecretVersionRequest, ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error)
AddSecretVersion(context.Context, *secretmanagerpb.AddSecretVersionRequest, ...gax.CallOption) (*secretmanagerpb.SecretVersion, error)
CreateSecret(context.Context, *secretmanagerpb.CreateSecretRequest, ...gax.CallOption) (*secretmanagerpb.Secret, error)
GetSecret(context.Context, *secretmanagerpb.GetSecretRequest, ...gax.CallOption) (*secretmanagerpb.Secret, error)
UpdateSecret(context.Context, *secretmanagerpb.UpdateSecretRequest, ...gax.CallOption) (*secretmanagerpb.Secret, error)
}

type secretManagerSecretService struct {
secret.UnimplementedSecretPlugin
client SecretManagerClient
client ifaces_gcloud_secret.SecretManagerClient
projectId string
stackName string
cache map[string]string
}

func validateNewSecret(sec *secret.Secret, val []byte) error {
Expand All @@ -65,71 +59,45 @@ func (s *secretManagerSecretService) getParentName() string {
return fmt.Sprintf("projects/%s", s.projectId)
}

func (s *secretManagerSecretService) buildSecretName(sec *secret.Secret) (string, error) {
if len(sec.Name) == 0 {
return "", fmt.Errorf("provide non-blank name")
}

return fmt.Sprintf("%s/secrets/%s", s.getParentName(), sec.Name), nil
}

func (s *secretManagerSecretService) buildSecretVersionName(sv *secret.SecretVersion) (string, error) {
parent, err := s.buildSecretName(sv.Secret)

if err != nil {
return "", err
if len(sv.Secret.Name) == 0 {
return "", fmt.Errorf("provide non-blank name")
}

if len(sv.Version) == 0 {
return "", fmt.Errorf("provide non-blank version")
}

parent, inCache := s.cache[sv.Secret.Name]
if !inCache {
realSec, err := s.getSecret(sv.Secret)
if err != nil {
return "", err
}

parent = realSec.Name
}

return fmt.Sprintf("%s/versions/%s", parent, sv.Version), nil
}

// ensure a secret container exists for storing secret versions
func (s *secretManagerSecretService) ensureSecret(sec *secret.Secret) (*secretmanagerpb.Secret, error) {
secName, err := s.buildSecretName(sec)

if err != nil {
return nil, err
}
func (s *secretManagerSecretService) getSecret(sec *secret.Secret) (*secretmanagerpb.Secret, error) {
iter := s.client.ListSecrets(context.TODO(), &secretmanagerpb.ListSecretsRequest{
Parent: s.getParentName(),
Filter: "labels.x-nitric-name=" + sec.Name + " AND labels.x-nitric-stack=" + s.stackName,
})

getReq := &secretmanagerpb.GetSecretRequest{
Name: secName,
result, err := iter.Next()
if err == iterator.Done {
return nil, status.Error(grpcCodes.NotFound, "secret not found")
}

result, err := s.client.GetSecret(context.TODO(), getReq)

if err != nil {
// check error status, if it was an RPC NOT_FOUND error then continue
if s, ok := status.FromError(err); ok && s.Code() != pbcodes.NotFound {
return nil, err
} else if !ok {
// It wasn't an RPC error so return
return nil, err
}
return nil, err
}

if result == nil {
// Creates the secret container
secReq := &secretmanagerpb.CreateSecretRequest{
Parent: s.getParentName(),
SecretId: sec.Name,
Secret: &secretmanagerpb.Secret{
Replication: &secretmanagerpb.Replication{
Replication: &secretmanagerpb.Replication_Automatic_{
Automatic: &secretmanagerpb.Replication_Automatic{},
},
},
},
}

result, err = s.client.CreateSecret(context.TODO(), secReq)
if err != nil {
return nil, fmt.Errorf("failed to create new secret: %v", err)
}
}
s.cache[sec.Name] = result.Name

return result, nil
}
Expand All @@ -152,7 +120,7 @@ func (s *secretManagerSecretService) Put(sec *secret.Secret, val []byte) (*secre
}

// ensure the secret container exists...
parentSec, err := s.ensureSecret(sec)
parentSec, err := s.getSecret(sec)

if err != nil {
return nil, newErr(
Expand Down Expand Up @@ -238,13 +206,15 @@ func New() (secret.SecretService, error) {
return nil, fmt.Errorf("GCP credentials error: %v", credentialsError)
}

client, clientError := secretmanager.NewClient(ctx)
client, clientError := ifaces_gcloud_secret.NewClient(ctx)
if clientError != nil {
return nil, fmt.Errorf("secret manager client error: %v", clientError)
}

return &secretManagerSecretService{
client: client,
projectId: credentials.ProjectID,
stackName: utils.GetEnv("NITRIC_STACK", ""),
cache: make(map[string]string),
}, nil
}
Loading

0 comments on commit cd2e061

Please sign in to comment.