github.com/cornelk/go-cloud@v0.17.1/secrets/gcpkms/kms.go (about)

     1  // Copyright 2018 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 gcpkms provides a secrets implementation backed by Google Cloud KMS.
    16  // Use OpenKeeper to construct a *secrets.Keeper.
    17  //
    18  // URLs
    19  //
    20  // For secrets.OpenKeeper, gcpkms registers for the scheme "gcpkms".
    21  // The default URL opener will create a connection using use default
    22  // credentials from the environment, as described in
    23  // https://cloud.google.com/docs/authentication/production.
    24  // To customize the URL opener, or for more details on the URL format,
    25  // see URLOpener.
    26  // See https://github.com/cornelk/go-cloud/concepts/urls/ for background information.
    27  //
    28  // As
    29  //
    30  // gcpkms exposes the following type for As:
    31  //  - Error: *google.golang.org/grpc/status.Status
    32  package gcpkms // import "github.com/cornelk/go-cloud/secrets/gcpkms"
    33  
    34  import (
    35  	"context"
    36  	"fmt"
    37  	"net/url"
    38  	"path"
    39  	"sync"
    40  
    41  	cloudkms "cloud.google.com/go/kms/apiv1"
    42  	"github.com/cornelk/go-cloud/gcerrors"
    43  	"github.com/cornelk/go-cloud/gcp"
    44  	"github.com/cornelk/go-cloud/internal/gcerr"
    45  	"github.com/cornelk/go-cloud/internal/useragent"
    46  	"github.com/cornelk/go-cloud/secrets"
    47  	"github.com/google/wire"
    48  	"google.golang.org/api/option"
    49  	kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
    50  	"google.golang.org/grpc/status"
    51  )
    52  
    53  // endPoint is the address to access Google Cloud KMS API.
    54  const endPoint = "cloudkms.googleapis.com:443"
    55  
    56  // Dial returns a client to use with Cloud KMS and a clean-up function to close
    57  // the client after used.
    58  func Dial(ctx context.Context, ts gcp.TokenSource) (*cloudkms.KeyManagementClient, func(), error) {
    59  	c, err := cloudkms.NewKeyManagementClient(ctx, option.WithTokenSource(ts), useragent.ClientOption("secrets"))
    60  	return c, func() { c.Close() }, err
    61  }
    62  
    63  func init() {
    64  	secrets.DefaultURLMux().RegisterKeeper(Scheme, new(lazyCredsOpener))
    65  }
    66  
    67  // Set holds Wire providers for this package.
    68  var Set = wire.NewSet(
    69  	Dial,
    70  	wire.Struct(new(URLOpener), "Client"),
    71  )
    72  
    73  // lazyCredsOpener obtains Application Default Credentials on the first call
    74  // lazyCredsOpener obtains Application Default Credentials on the first call
    75  // to OpenKeeperURL.
    76  type lazyCredsOpener struct {
    77  	init   sync.Once
    78  	opener *URLOpener
    79  	err    error
    80  }
    81  
    82  func (o *lazyCredsOpener) OpenKeeperURL(ctx context.Context, u *url.URL) (*secrets.Keeper, error) {
    83  	o.init.Do(func() {
    84  		creds, err := gcp.DefaultCredentials(ctx)
    85  		if err != nil {
    86  			o.err = err
    87  			return
    88  		}
    89  		client, _, err := Dial(ctx, creds.TokenSource)
    90  		if err != nil {
    91  			o.err = err
    92  			return
    93  		}
    94  		o.opener = &URLOpener{Client: client}
    95  	})
    96  	if o.err != nil {
    97  		return nil, fmt.Errorf("open keeper %v: %v", u, o.err)
    98  	}
    99  	return o.opener.OpenKeeperURL(ctx, u)
   100  }
   101  
   102  // Scheme is the URL scheme gcpkms registers its URLOpener under on secrets.DefaultMux.
   103  const Scheme = "gcpkms"
   104  
   105  // URLOpener opens GCP KMS URLs like
   106  // "gcpkms://projects/[PROJECT_ID]/locations/[LOCATION]/keyRings/[KEY_RING]/cryptoKeys/[KEY]".
   107  //
   108  // The URL host+path are used as the key resource ID; see
   109  // https://cloud.google.com/kms/docs/object-hierarchy#key for more details.
   110  //
   111  // No query parameters are supported.
   112  type URLOpener struct {
   113  	// Client must be non-nil and be authenticated with "cloudkms" scope or equivalent.
   114  	Client *cloudkms.KeyManagementClient
   115  
   116  	// Options specifies the default options to pass to OpenKeeper.
   117  	Options KeeperOptions
   118  }
   119  
   120  // OpenKeeperURL opens the GCP KMS URLs.
   121  func (o *URLOpener) OpenKeeperURL(ctx context.Context, u *url.URL) (*secrets.Keeper, error) {
   122  	for param := range u.Query() {
   123  		return nil, fmt.Errorf("open keeper %v: invalid query parameter %q", u, param)
   124  	}
   125  	return OpenKeeper(o.Client, path.Join(u.Host, u.Path), &o.Options), nil
   126  }
   127  
   128  // OpenKeeper returns a *secrets.Keeper that uses Google Cloud KMS.
   129  // You can use KeyResourceID to construct keyResourceID from its parts,
   130  // or provide the whole string if you have it (e.g., from the GCP console).
   131  // See https://cloud.google.com/kms/docs/object-hierarchy#key for more details.
   132  // See the package documentation for an example.
   133  func OpenKeeper(client *cloudkms.KeyManagementClient, keyResourceID string, opts *KeeperOptions) *secrets.Keeper {
   134  	return secrets.NewKeeper(&keeper{
   135  		keyResourceID: keyResourceID,
   136  		client:        client,
   137  	})
   138  }
   139  
   140  // KeyResourceID constructs a key resourceID for GCP KMS.
   141  // See https://cloud.google.com/kms/docs/object-hierarchy#key for more details.
   142  func KeyResourceID(projectID, location, keyRing, key string) string {
   143  	return fmt.Sprintf("projects/%s/locations/%s/keyRings/%s/cryptoKeys/%s",
   144  		projectID, location, keyRing, key)
   145  }
   146  
   147  // keeper implements driver.Keeper.
   148  type keeper struct {
   149  	keyResourceID string
   150  	client        *cloudkms.KeyManagementClient
   151  }
   152  
   153  // Decrypt decrypts the ciphertext using the key constructed from ki.
   154  func (k *keeper) Decrypt(ctx context.Context, ciphertext []byte) ([]byte, error) {
   155  	req := &kmspb.DecryptRequest{
   156  		Name:       k.keyResourceID,
   157  		Ciphertext: ciphertext,
   158  	}
   159  	resp, err := k.client.Decrypt(ctx, req)
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  	return resp.GetPlaintext(), nil
   164  }
   165  
   166  // Encrypt encrypts the plaintext into a ciphertext.
   167  func (k *keeper) Encrypt(ctx context.Context, plaintext []byte) ([]byte, error) {
   168  	req := &kmspb.EncryptRequest{
   169  		Name:      k.keyResourceID,
   170  		Plaintext: plaintext,
   171  	}
   172  	resp, err := k.client.Encrypt(ctx, req)
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  	return resp.GetCiphertext(), nil
   177  }
   178  
   179  // Close implements driver.Keeper.Close.
   180  func (k *keeper) Close() error { return nil }
   181  
   182  // ErrorAs implements driver.Keeper.ErrorAs.
   183  func (k *keeper) ErrorAs(err error, i interface{}) bool {
   184  	s, ok := status.FromError(err)
   185  	if !ok {
   186  		return false
   187  	}
   188  	p, ok := i.(**status.Status)
   189  	if !ok {
   190  		return false
   191  	}
   192  	*p = s
   193  	return true
   194  }
   195  
   196  // ErrorCode implements driver.ErrorCode.
   197  func (k *keeper) ErrorCode(err error) gcerrors.ErrorCode {
   198  	return gcerr.GRPCCode(err)
   199  }
   200  
   201  // KeeperOptions controls Keeper behaviors.
   202  // It is provided for future extensibility.
   203  type KeeperOptions struct{}