github.com/SaurabhDubey-Groww/go-cloud@v0.0.0-20221124105541-b26c29285fd8/runtimevar/gcpsecretmanager/gcpsecretmanager.go (about)

     1  // Copyright 2020 The Go Cloud Development Kit Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package gcpsecretmanager provides a runtimevar implementation with
    16  // secrets read from GCP Secret Manager
    17  // (https://cloud.google.com/secret-manager).
    18  // Use OpenVariable to construct a *runtimevar.Variable.
    19  //
    20  // # URLs
    21  //
    22  // For runtimevar.OpenVariable, gcpsecretmanager registers for the scheme
    23  // "gcpsecretmanager".
    24  // The default URL opener will creating a connection using use default
    25  // credentials from the environment, as described in
    26  // https://cloud.google.com/docs/authentication/production.
    27  // To customize the URL opener, or for more details on the URL format,
    28  // see URLOpener.
    29  // See https://gocloud.dev/concepts/urls/ for background information.
    30  //
    31  // # As
    32  //
    33  // gcpsecretmanager exposes the following types for As:
    34  //   - Snapshot: *secretmanagerpb.AccessSecretVersionResponse
    35  //   - Error: *status.Status
    36  package gcpsecretmanager // import "gocloud.dev/runtimevar/gcpsecretmanager"
    37  
    38  import (
    39  	"bytes"
    40  	"context"
    41  	"errors"
    42  	"fmt"
    43  	"net/url"
    44  	"path"
    45  	"regexp"
    46  	"sync"
    47  	"time"
    48  
    49  	secretmanager "cloud.google.com/go/secretmanager/apiv1"
    50  	"github.com/google/wire"
    51  	"gocloud.dev/gcerrors"
    52  	"gocloud.dev/gcp"
    53  	"gocloud.dev/internal/gcerr"
    54  	"gocloud.dev/runtimevar"
    55  	"gocloud.dev/runtimevar/driver"
    56  	"google.golang.org/api/option"
    57  	secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
    58  	"google.golang.org/grpc"
    59  	"google.golang.org/grpc/codes"
    60  	"google.golang.org/grpc/credentials"
    61  	"google.golang.org/grpc/credentials/oauth"
    62  	"google.golang.org/grpc/status"
    63  )
    64  
    65  // Dial opens a gRPC connection to the Secret Manager API using
    66  // credentials from ts. It is provided as an optional helper with useful
    67  // defaults.
    68  //
    69  // The second return value is a function that should be called to clean up
    70  // the connection opened by Dial.
    71  func Dial(ctx context.Context, ts gcp.TokenSource) (*secretmanager.Client, func(), error) {
    72  	client, err := secretmanager.NewClient(ctx,
    73  		option.WithGRPCDialOption(
    74  			grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")),
    75  		),
    76  		option.WithTokenSource(oauth.TokenSource{TokenSource: ts}),
    77  		option.WithUserAgent("runtimevar"),
    78  	)
    79  	if err != nil {
    80  		return nil, nil, err
    81  	}
    82  
    83  	return client, func() { _ = client.Close() }, nil
    84  }
    85  
    86  func init() {
    87  	runtimevar.DefaultURLMux().RegisterVariable(Scheme, new(lazyCredsOpener))
    88  }
    89  
    90  // Set holds Wire providers for this package.
    91  var Set = wire.NewSet(
    92  	Dial,
    93  	wire.Struct(new(URLOpener), "Client"),
    94  )
    95  
    96  // lazyCredsOpener obtains Application Default Credentials on the first call
    97  // to OpenVariableURL.
    98  type lazyCredsOpener struct {
    99  	init   sync.Once
   100  	opener *URLOpener
   101  	err    error
   102  }
   103  
   104  func (o *lazyCredsOpener) OpenVariableURL(ctx context.Context, u *url.URL) (*runtimevar.Variable, error) {
   105  	o.init.Do(func() {
   106  		creds, err := gcp.DefaultCredentials(ctx)
   107  		if err != nil {
   108  			o.err = err
   109  			return
   110  		}
   111  		client, _, err := Dial(ctx, creds.TokenSource)
   112  		if err != nil {
   113  			o.err = err
   114  			return
   115  		}
   116  		o.opener = &URLOpener{Client: client}
   117  	})
   118  	if o.err != nil {
   119  		return nil, fmt.Errorf("open variable %v: %v", u, o.err)
   120  	}
   121  	return o.opener.OpenVariableURL(ctx, u)
   122  }
   123  
   124  // Scheme is the URL scheme gcpsecretmanager registers its URLOpener under on runtimevar.DefaultMux.
   125  const Scheme = "gcpsecretmanager"
   126  
   127  // URLOpener opens gcpsecretmanager URLs like "gcpsecretmanager://projects/[project_id]/secrets/[secret_id]".
   128  //
   129  // The URL Host+Path are used as the GCP Secret Manager secret key;
   130  // see https://cloud.google.com/secret-manager
   131  // for more details.
   132  //
   133  // The following query parameters are supported:
   134  //
   135  //   - decoder: The decoder to use. Defaults to URLOpener.Decoder, or
   136  //     runtimevar.BytesDecoder if URLOpener.Decoder is nil.
   137  //     See runtimevar.DecoderByName for supported values.
   138  //   - wait: The poll interval, in time.ParseDuration formats.
   139  //     Defaults to 30s.
   140  type URLOpener struct {
   141  	// Client must be set to a non-nil client authenticated with
   142  	// Secret Manager scope or equivalent.
   143  	Client *secretmanager.Client
   144  
   145  	// Decoder specifies the decoder to use if one is not specified in the URL.
   146  	// Defaults to runtimevar.BytesDecoder.
   147  	Decoder *runtimevar.Decoder
   148  
   149  	// Options specifies the options to pass to New.
   150  	Options Options
   151  }
   152  
   153  // OpenVariableURL opens a gcpsecretmanager Secret.
   154  func (o *URLOpener) OpenVariableURL(ctx context.Context, u *url.URL) (*runtimevar.Variable, error) {
   155  	q := u.Query()
   156  
   157  	decoderName := q.Get("decoder")
   158  	q.Del("decoder")
   159  	decoder, err := runtimevar.DecoderByName(ctx, decoderName, o.Decoder)
   160  	if err != nil {
   161  		return nil, fmt.Errorf("open variable %v: invalid decoder: %v", u, err)
   162  	}
   163  	opts := o.Options
   164  	if s := q.Get("wait"); s != "" {
   165  		q.Del("wait")
   166  		d, err := time.ParseDuration(s)
   167  		if err != nil {
   168  			return nil, fmt.Errorf("open variable %v: invalid wait %q: %v", u, s, err)
   169  		}
   170  		opts.WaitDuration = d
   171  	}
   172  
   173  	for param := range q {
   174  		return nil, fmt.Errorf("open variable %v: invalid query parameter %q", u, param)
   175  	}
   176  	return OpenVariable(o.Client, path.Join(u.Host, u.Path), decoder, &opts)
   177  }
   178  
   179  // Options sets options.
   180  type Options struct {
   181  	// WaitDuration controls the rate at which Secret Manager is polled.
   182  	// Defaults to 30 seconds.
   183  	WaitDuration time.Duration
   184  }
   185  
   186  // OpenVariable constructs a *runtimevar.Variable backed by secretKey in GCP Secret Manager.
   187  //
   188  // A secretKey will look like:
   189  //
   190  //	projects/[project_id]/secrets/[secret_id]
   191  //
   192  // A project ID is a unique, user-assigned ID of the Project.
   193  // It must be 6 to 30 lowercase letters, digits, or hyphens.
   194  // It must start with a letter. Trailing hyphens are prohibited.
   195  //
   196  // A secret ID is a string with a maximum length of 255 characters and can
   197  // contain uppercase and lowercase letters, numerals, and the hyphen (`-`) and
   198  // underscore (`_`) characters.
   199  //
   200  // gcpsecretmanager package will always use the latest secret value,
   201  // so `/version/latest` postfix must NOT be added to the secret key.
   202  //
   203  // You can use the full string (e.g., copied from the GCP Console), or
   204  // construct one from its parts using SecretKey.
   205  //
   206  // See https://cloud.google.com/secret-manager for more details.
   207  //
   208  // Secret Manager returns raw bytes; provide a decoder to decode the raw bytes
   209  // into the appropriate type for runtimevar.Snapshot.Value.
   210  // See the runtimevar package documentation for examples of decoders.
   211  func OpenVariable(client *secretmanager.Client, secretKey string, decoder *runtimevar.Decoder, opts *Options) (*runtimevar.Variable, error) {
   212  	w, err := newWatcher(client, secretKey, decoder, opts)
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  	return runtimevar.New(w), nil
   217  }
   218  
   219  var secretKeyRE = regexp.MustCompile("^projects/[a-z][a-z0-9_\\-]{4,28}[a-z0-9_]/secrets/[a-zA-Z0-9_\\-]{1,255}$")
   220  
   221  const latestVersion = "/versions/latest"
   222  
   223  func newWatcher(client *secretmanager.Client, secretKey string, decoder *runtimevar.Decoder, opts *Options) (driver.Watcher, error) {
   224  	if opts == nil {
   225  		opts = &Options{}
   226  	}
   227  
   228  	if !secretKeyRE.MatchString(secretKey) {
   229  		return nil, fmt.Errorf("invalid secretKey %q; must match %v", secretKey, secretKeyRE)
   230  	}
   231  
   232  	return &watcher{
   233  		client:  client,
   234  		wait:    driver.WaitDuration(opts.WaitDuration),
   235  		name:    secretKey,
   236  		decoder: decoder,
   237  	}, nil
   238  }
   239  
   240  // SecretKey constructs a GCP Secret Manager secret key from component parts.
   241  // See https://cloud.google.com/secret-manager for more details.
   242  func SecretKey(projectID gcp.ProjectID, secretID string) string {
   243  	return "projects/" + string(projectID) + "/secrets/" + secretID
   244  }
   245  
   246  // state implements driver.State.
   247  type state struct {
   248  	val        interface{}
   249  	raw        *secretmanagerpb.AccessSecretVersionResponse
   250  	updateTime time.Time
   251  	rawBytes   []byte
   252  	err        error
   253  }
   254  
   255  // Value implements driver.State.Value.
   256  func (s *state) Value() (interface{}, error) {
   257  	return s.val, s.err
   258  }
   259  
   260  // UpdateTime implements driver.State.UpdateTime.
   261  func (s *state) UpdateTime() time.Time {
   262  	return s.updateTime
   263  }
   264  
   265  // As implements driver.State.As.
   266  func (s *state) As(i interface{}) bool {
   267  	if s.raw == nil {
   268  		return false
   269  	}
   270  	p, ok := i.(**secretmanagerpb.AccessSecretVersionResponse)
   271  	if !ok {
   272  		return false
   273  	}
   274  	*p = s.raw
   275  	return true
   276  }
   277  
   278  // errorState returns a new State with err, unless prevS also represents
   279  // the same error, in which case it returns nil.
   280  func errorState(err error, prevS driver.State) driver.State {
   281  	s := &state{err: err}
   282  	if prevS == nil {
   283  		return s
   284  	}
   285  	prev := prevS.(*state)
   286  	if prev.err == nil {
   287  		// New error.
   288  		return s
   289  	}
   290  	if equivalentError(err, prev.err) {
   291  		// Same error, return nil to indicate no change.
   292  		return nil
   293  	}
   294  	return s
   295  }
   296  
   297  // equivalentError returns true iff err1 and err2 represent an equivalent error;
   298  // i.e., we don't want to return it to the user as a different error.
   299  func equivalentError(err1, err2 error) bool {
   300  	if err1 == err2 || err1.Error() == err2.Error() {
   301  		return true
   302  	}
   303  	code1, code2 := status.Code(err1), status.Code(err2)
   304  	return code1 != codes.OK && code1 != codes.Unknown && code1 == code2
   305  }
   306  
   307  // watcher implements driver.Watcher for secrets provided by the Secret Manager service.
   308  type watcher struct {
   309  	client  *secretmanager.Client
   310  	wait    time.Duration
   311  	name    string
   312  	decoder *runtimevar.Decoder
   313  }
   314  
   315  // WatchVariable implements driver.WatchVariable.
   316  func (w *watcher) WatchVariable(ctx context.Context, prev driver.State) (driver.State, time.Duration) {
   317  	latest := w.name + latestVersion
   318  
   319  	secret, err := w.client.AccessSecretVersion(ctx, &secretmanagerpb.AccessSecretVersionRequest{Name: latest})
   320  	if err != nil {
   321  		return errorState(err, prev), w.wait
   322  	}
   323  
   324  	if secret == nil || secret.Payload == nil || secret.Payload.Data == nil {
   325  		return errorState(errors.New("invalid secret payload"), prev), w.wait
   326  	}
   327  
   328  	meta, err := w.client.GetSecretVersion(ctx, &secretmanagerpb.GetSecretVersionRequest{Name: latest})
   329  	if err != nil {
   330  		return errorState(err, prev), w.wait
   331  	}
   332  
   333  	createTime := meta.CreateTime.AsTime()
   334  
   335  	// See if it's the same raw bytes as before.
   336  	if prev != nil {
   337  		prevState, ok := prev.(*state)
   338  		if ok && prevState != nil && bytes.Equal(secret.Payload.Data, prevState.rawBytes) {
   339  			// No change!
   340  			return nil, w.wait
   341  		}
   342  	}
   343  
   344  	// Decode the value.
   345  	val, err := w.decoder.Decode(ctx, secret.Payload.Data)
   346  	if err != nil {
   347  		return errorState(err, prev), w.wait
   348  	}
   349  
   350  	// A secret version is immutable.
   351  	// The latest secret value creation time is the last time the secret value has been changed.
   352  	// Hence set updateTime as createTime.
   353  	return &state{val: val, raw: secret, updateTime: createTime, rawBytes: secret.Payload.Data}, w.wait
   354  }
   355  
   356  // Close implements driver.Close.
   357  func (w *watcher) Close() error {
   358  	return nil
   359  }
   360  
   361  // ErrorAs implements driver.ErrorAs.
   362  func (w *watcher) ErrorAs(err error, i interface{}) bool {
   363  	// FromError converts err to a *status.Status.
   364  	s, _ := status.FromError(err)
   365  	if p, ok := i.(**status.Status); ok {
   366  		*p = s
   367  		return true
   368  	}
   369  	return false
   370  }
   371  
   372  // ErrorCode implements driver.ErrorCode.
   373  func (*watcher) ErrorCode(err error) gcerrors.ErrorCode {
   374  	return gcerr.GRPCCode(err)
   375  }