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 }