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 }