github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/secrets/cloud/manager.go (about)

     1  // Copyright 2016-2018, Pulumi Corporation.
     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  //     http://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 cloud implements support for a generic cloud secret manager.
    16  package cloud
    17  
    18  import (
    19  	"context"
    20  	"crypto/rand"
    21  	"encoding/json"
    22  	"fmt"
    23  	netUrl "net/url"
    24  
    25  	gosecrets "gocloud.dev/secrets"
    26  	_ "gocloud.dev/secrets/awskms"        // support for awskms://
    27  	_ "gocloud.dev/secrets/azurekeyvault" // support for azurekeyvault://
    28  	"gocloud.dev/secrets/gcpkms"          // support for gcpkms://
    29  	_ "gocloud.dev/secrets/hashivault"    // support for hashivault://
    30  	"google.golang.org/api/cloudkms/v1"
    31  
    32  	"github.com/pulumi/pulumi/pkg/v3/authhelpers"
    33  	"github.com/pulumi/pulumi/pkg/v3/secrets"
    34  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource/config"
    35  )
    36  
    37  // Type is the type of secrets managed by this secrets provider
    38  const Type = "cloud"
    39  
    40  type cloudSecretsManagerState struct {
    41  	URL          string `json:"url"`
    42  	EncryptedKey []byte `json:"encryptedkey"`
    43  }
    44  
    45  // NewCloudSecretsManagerFromState deserialize configuration from state and returns a secrets
    46  // manager that uses the target cloud key management service to encrypt/decrypt a data key used for
    47  // envelope encryption of secrets values.
    48  func NewCloudSecretsManagerFromState(state json.RawMessage) (secrets.Manager, error) {
    49  	var s cloudSecretsManagerState
    50  	if err := json.Unmarshal(state, &s); err != nil {
    51  		return nil, fmt.Errorf("unmarshalling state: %w", err)
    52  	}
    53  
    54  	return NewCloudSecretsManager(s.URL, s.EncryptedKey)
    55  }
    56  
    57  // openKeeper opens the keeper, handling pulumi-specifc cases in the URL.
    58  func openKeeper(ctx context.Context, url string) (*gosecrets.Keeper, error) {
    59  	u, err := netUrl.Parse(url)
    60  	if err != nil {
    61  		return nil, fmt.Errorf("unable to parse the secrets provider URL: %w", err)
    62  	}
    63  
    64  	switch u.Scheme {
    65  	case gcpkms.Scheme:
    66  		credentials, err := authhelpers.ResolveGoogleCredentials(ctx, cloudkms.CloudkmsScope)
    67  		if err != nil {
    68  			return nil, fmt.Errorf("missing google credentials: %w", err)
    69  		}
    70  
    71  		kmsClient, _, err := gcpkms.Dial(ctx, credentials.TokenSource)
    72  		if err != nil {
    73  			return nil, fmt.Errorf("failed to connect to gcpkms: %w", err)
    74  		}
    75  		opener := gcpkms.URLOpener{
    76  			Client: kmsClient,
    77  		}
    78  
    79  		return opener.OpenKeeperURL(ctx, u)
    80  	default:
    81  		return gosecrets.OpenKeeper(ctx, url)
    82  	}
    83  }
    84  
    85  // GenerateNewDataKey generates a new DataKey seeded by a fresh random 32-byte key and encrypted
    86  // using the target cloud key management service.
    87  func GenerateNewDataKey(url string) ([]byte, error) {
    88  	plaintextDataKey := make([]byte, 32)
    89  	_, err := rand.Read(plaintextDataKey)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  	keeper, err := openKeeper(context.Background(), url)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  	return keeper.Encrypt(context.Background(), plaintextDataKey)
    98  }
    99  
   100  // NewCloudSecretsManager returns a secrets manager that uses the target cloud key management
   101  // service to encrypt/decrypt a data key used for envelope encryption of secrets values.
   102  func NewCloudSecretsManager(url string, encryptedDataKey []byte) (*Manager, error) {
   103  	keeper, err := openKeeper(context.Background(), url)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  	plaintextDataKey, err := keeper.Decrypt(context.Background(), encryptedDataKey)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  	crypter := config.NewSymmetricCrypter(plaintextDataKey)
   112  	return &Manager{
   113  		crypter: crypter,
   114  		state: cloudSecretsManagerState{
   115  			URL:          url,
   116  			EncryptedKey: encryptedDataKey,
   117  		},
   118  	}, nil
   119  }
   120  
   121  // Manager is the secrets.Manager implementation for cloud key management services
   122  type Manager struct {
   123  	state   cloudSecretsManagerState
   124  	crypter config.Crypter
   125  }
   126  
   127  func (m *Manager) Type() string                         { return Type }
   128  func (m *Manager) State() interface{}                   { return m.state }
   129  func (m *Manager) Encrypter() (config.Encrypter, error) { return m.crypter, nil }
   130  func (m *Manager) Decrypter() (config.Decrypter, error) { return m.crypter, nil }
   131  func (m *Manager) EncryptedKey() []byte                 { return m.state.EncryptedKey }