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

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package hashivault
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"strings"
    12  
    13  	"github.com/hashicorp/vault/api"
    14  	"github.com/rs/zerolog/log"
    15  	"go.mondoo.com/cnquery/providers-sdk/v1/vault"
    16  )
    17  
    18  var notImplemented = errors.New("not implemented")
    19  
    20  func New(serverURL string, token string) *Vault {
    21  	log.Debug().Bool("token-sec", len(token) > 0).Msgf("Using HashiCorp Vault at %s", serverURL)
    22  	return &Vault{
    23  		Token: token,
    24  		APIConfig: api.Config{
    25  			Address: serverURL,
    26  		},
    27  	}
    28  }
    29  
    30  type Vault struct {
    31  	// Token is the access token the Vault client uses to talk to the server.
    32  	// See https://www.vaultproject.io/docs/concepts/tokens.html for more
    33  	// information.
    34  	Token string
    35  	// APIConfig is used to configure the creation of the client.
    36  	APIConfig api.Config
    37  }
    38  
    39  func (v *Vault) About(context.Context, *vault.Empty) (*vault.VaultInfo, error) {
    40  	return &vault.VaultInfo{Name: "Hashicorp Vault"}, nil
    41  }
    42  
    43  // Dial gets a Vault client.
    44  func (v *Vault) client() (*api.Client, error) {
    45  	c, err := api.NewClient(&v.APIConfig)
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  	if v.Token != "" {
    50  		c.SetToken(v.Token)
    51  	}
    52  	return c, nil
    53  }
    54  
    55  func vaultSecretId(key string) string {
    56  	base := "secret/data/"
    57  	return base + key
    58  }
    59  
    60  // we need to remove the leading // from mrns, this should not be done here, therefore we just throw an error
    61  func validKey(key string) error {
    62  	if strings.HasPrefix(key, "/") {
    63  		return errors.New("leading / are not allowed")
    64  	}
    65  	return nil
    66  }
    67  
    68  // https://learn.hashicorp.com/tutorials/vault/versioned-kv?in=vault/secrets-management#step-2-write-secrets
    69  func (v *Vault) Get(ctx context.Context, id *vault.SecretID) (*vault.Secret, error) {
    70  	log.Debug().Str("secret", id.Key).Msg("gather secret from hashicorp-vault")
    71  	c, err := v.client()
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  
    76  	err = validKey(id.Key)
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  
    81  	secret, err := c.Logical().Read(vaultSecretId(id.Key))
    82  	if err != nil {
    83  		return nil, vault.NotFoundError
    84  	}
    85  
    86  	secretBytes, err := secretData(secret)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  
    91  	return &vault.Secret{
    92  		Key:      id.Key,
    93  		Data:     secretBytes,
    94  		Encoding: vault.SecretEncoding_encoding_json,
    95  	}, nil
    96  }
    97  
    98  // secretData returns the map of metadata associated with the secret
    99  func secretData(s *api.Secret) ([]byte, error) {
   100  	if s == nil {
   101  		return nil, nil
   102  	}
   103  
   104  	if s.Data == nil || (s.Data["data"] == nil) {
   105  		return nil, nil
   106  	}
   107  
   108  	data, ok := s.Data["data"].(map[string]interface{})
   109  	if !ok {
   110  		return nil, fmt.Errorf("unable to convert data field to expected format")
   111  	}
   112  
   113  	// when we resolve the secret in motor/discovery/resolve.go, we unmarshal to map[string]string, so things should match!
   114  	secretData := make(map[string]string, len(data))
   115  	for k, v := range data {
   116  		typed, ok := v.(string)
   117  		if !ok {
   118  			return nil, fmt.Errorf("unable to convert data value %v to string", v)
   119  		}
   120  		secretData[k] = typed
   121  	}
   122  
   123  	return json.Marshal(secretData)
   124  }
   125  
   126  func (v *Vault) Set(ctx context.Context, cred *vault.Secret) (*vault.SecretID, error) {
   127  	return nil, errors.New("not implemented")
   128  }