go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers-sdk/v1/vault/gcpberglas/berglas.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package gcpberglas
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"strings"
    11  
    12  	berglas "github.com/GoogleCloudPlatform/berglas/pkg/berglas"
    13  	"go.mondoo.com/cnquery/providers-sdk/v1/vault"
    14  	"go.mondoo.com/cnquery/utils/multierr"
    15  )
    16  
    17  type storageType string
    18  
    19  type Option func(*Vault)
    20  
    21  const cloudStorage storageType = "storage"
    22  
    23  // https://github.com/GoogleCloudPlatform/berglas
    24  func New(projectID string, opts ...Option) *Vault {
    25  	v := &Vault{projectID: projectID}
    26  	for _, opt := range opts {
    27  		opt(v)
    28  	}
    29  	return v
    30  }
    31  
    32  func WithBucket(bucket string) Option {
    33  	return func(v *Vault) {
    34  		v.bucket = bucket
    35  		v.storageType = cloudStorage
    36  	}
    37  }
    38  
    39  func WithKmsKey(kmsKeyID string) Option {
    40  	return func(v *Vault) {
    41  		v.kmsKeyID = kmsKeyID
    42  	}
    43  }
    44  
    45  type berglasStorageInfo struct {
    46  	bucket string
    47  	object string
    48  }
    49  
    50  type Vault struct {
    51  	projectID   string
    52  	storageType storageType
    53  	kmsKeyID    string
    54  	bucket      string
    55  }
    56  
    57  func (v *Vault) About(context.Context, *vault.Empty) (*vault.VaultInfo, error) {
    58  	return &vault.VaultInfo{Name: "GCP Berglas: " + v.projectID}, nil
    59  }
    60  
    61  func (v *Vault) client(ctx context.Context) (*berglas.Client, error) {
    62  	client, err := berglas.New(ctx)
    63  	if err != nil {
    64  		return nil, multierr.Wrap(err, "failed to setup gcp berglas client")
    65  	}
    66  	return client, nil
    67  }
    68  
    69  // expected berglas key format: {storage}/{bucketName}/{objectName}
    70  func getBerglasStorageInfo(key string) (berglasStorageInfo, error) {
    71  	split := strings.Split(key, "/")
    72  	if len(split) != 3 {
    73  		return berglasStorageInfo{}, errors.New("invalid berglas key provided")
    74  	}
    75  	// we omit storage for now, as berglas secrets manager is not yet supported
    76  	// however, the key contains the type as to not break any existing keys if we add support in the future
    77  	return berglasStorageInfo{
    78  		bucket: split[1],
    79  		object: split[2],
    80  	}, nil
    81  }
    82  
    83  func (v *Vault) assembleBerglasKeyId(key string) (string, error) {
    84  	if v.storageType == cloudStorage {
    85  		return fmt.Sprintf("%s/%s/%s", v.storageType, v.bucket, key), nil
    86  	}
    87  	return "", errors.New("invalid berglas storage type")
    88  }
    89  
    90  func (v *Vault) Get(ctx context.Context, id *vault.SecretID) (*vault.Secret, error) {
    91  	c, err := v.client(ctx)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	berglasReadInfo, err := getBerglasStorageInfo(id.Key)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	result, err := c.Read(ctx, &berglas.StorageReadRequest{
   102  		Bucket: berglasReadInfo.bucket,
   103  		Object: berglasReadInfo.object,
   104  	})
   105  	if err != nil {
   106  		return nil, vault.NotFoundError
   107  	}
   108  
   109  	return &vault.Secret{
   110  		Key:  id.Key,
   111  		Data: result.Plaintext,
   112  		// we do not know the encoding here, but the default is binary
   113  		Encoding: vault.SecretEncoding_encoding_binary,
   114  	}, nil
   115  }
   116  
   117  func (v *Vault) Set(ctx context.Context, cred *vault.Secret) (*vault.SecretID, error) {
   118  	if len(v.kmsKeyID) == 0 {
   119  		return nil, errors.New("specified KMS key id is empty")
   120  	}
   121  
   122  	if len(v.storageType) == 0 {
   123  		return nil, errors.New("cannot create vault secret without a storage type")
   124  	}
   125  
   126  	if len(v.bucket) == 0 && v.storageType == cloudStorage {
   127  		return nil, errors.New("specified bucket name is empty")
   128  	}
   129  
   130  	c, err := v.client(ctx)
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  
   135  	// assemble the berglas key that will be used to get this secret
   136  	// it uses the storage type and the passed in key to build a key
   137  	key, err := v.assembleBerglasKeyId(cred.Key)
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  
   142  	_, err = c.Create(ctx, &berglas.StorageCreateRequest{
   143  		Bucket:    v.bucket,
   144  		Object:    cred.Key,
   145  		Plaintext: cred.Data,
   146  		Key:       v.kmsKeyID,
   147  	})
   148  	if err != nil {
   149  		return nil, err
   150  	}
   151  	return &vault.SecretID{
   152  		Key: key,
   153  	}, nil
   154  }