@@ -2,7 +2,8 @@ package vault
22
33import (
44 "context"
5- "path/filepath"
5+ "path"
6+ "strings"
67 "time"
78
89 "github.com/hashicorp/go-cleanhttp"
@@ -17,36 +18,55 @@ import (
1718type VaultSecrets struct {
1819 client * api.Client
1920 path string
21+ version int
2022 renewal time.Duration
2123}
2224
2325var _ secret.Store = & VaultSecrets {}
2426
2527// New creates a new Vault client and pings the server
26- func New (addr , path , token string , renewal time.Duration ) (* VaultSecrets , error ) {
27- client , err := api .NewClient (& api.Config {
28+ func New (addr , basepath , token string , renewal time.Duration ) (v * VaultSecrets , err error ) {
29+ if strings .HasPrefix (basepath , "/" ) {
30+ basepath = basepath [1 :]
31+ }
32+
33+ v = & VaultSecrets {
34+ path : basepath ,
35+ renewal : renewal ,
36+ }
37+
38+ if v .client , err = api .NewClient (& api.Config {
2839 Address : addr ,
2940 HttpClient : cleanhttp .DefaultClient (),
30- })
31- if err != nil {
41+ }); err != nil {
3242 return nil , errors .Wrap (err , "failed to create vault client" )
3343 }
34- client .SetToken (token )
44+ v . client .SetToken (token )
3545
36- if _ , err = client .Auth ().Token ().LookupSelf (); err != nil {
46+ if _ , err = v . client .Auth ().Token ().LookupSelf (); err != nil {
3747 return nil , errors .Wrap (err , "failed to connect to vault server" )
3848 }
3949
40- return & VaultSecrets {
41- client : client ,
42- path : path ,
43- renewal : renewal ,
44- }, nil
50+ enginepath := strings .Split (basepath , "/" )[0 ]
51+ if len (enginepath ) == 0 {
52+ enginepath = basepath
53+ }
54+
55+ if v .version , err = getKVEngineVersion (v .client , enginepath ); err != nil {
56+ return nil , errors .Wrapf (err , "failed to determine KV engine version at '/%s'" , enginepath )
57+ }
58+
59+ zap .L ().Debug ("created new vault client for secrets engine" ,
60+ zap .Int ("kv_version" , v .version ),
61+ zap .String ("basepath" , basepath ),
62+ zap .String ("enginepath" , enginepath ))
63+
64+ return v , nil
4565}
4666
4767// GetSecretsForTarget implements secret.Store
4868func (v * VaultSecrets ) GetSecretsForTarget (name string ) (map [string ]string , error ) {
49- path := filepath . Join ( v . path , name )
69+ path := v . buildPath ( name )
5070
5171 zap .L ().Debug ("looking for secrets in vault" ,
5272 zap .String ("name" , name ),
@@ -63,14 +83,13 @@ func (v *VaultSecrets) GetSecretsForTarget(name string) (map[string]string, erro
6383 return nil , nil
6484 }
6585
66- env := make ( map [ string ] string )
67- for k , v := range secret . Data {
68- env [ k ] = v .( string )
86+ env , err := kvToMap ( v . version , secret . Data )
87+ if err != nil {
88+ return nil , errors . Wrap ( err , "failed to unwrap secret data" )
6989 }
7090
7191 zap .L ().Debug ("found secrets in vault" ,
72- zap .Any ("secrets" , env ),
73- zap .Int ("count" , len (env )))
92+ zap .Any ("secret" , secret ))
7493
7594 return env , nil
7695}
@@ -92,3 +111,61 @@ func (v *VaultSecrets) Renew(ctx context.Context) error {
92111 }
93112 return nil
94113}
114+
115+ // builds the correct path to a secret based on the kv version
116+ func (v * VaultSecrets ) buildPath (item string ) string {
117+ if v .version == 1 {
118+ return path .Join (v .path , item )
119+ } else {
120+ return path .Join (v .path , "data" , item )
121+ }
122+ }
123+
124+ // pulls out the kv secret data for v1 and v2 secrets
125+ func kvToMap (version int , data map [string ]interface {}) (env map [string ]string , err error ) {
126+ if version == 1 {
127+ env = make (map [string ]string )
128+ for k , v := range data {
129+ env [k ] = v .(string )
130+ }
131+ } else if version == 2 {
132+ env = make (map [string ]string )
133+ if kv , ok := data ["data" ].(map [string ]interface {}); ok {
134+ for k , v := range kv {
135+ env [k ] = v .(string )
136+ }
137+ } else {
138+ return nil , errors .New ("could not interpret KV v2 response data as hashtable, this is likely a change in the KV v2 API, please open an issue." )
139+ }
140+ } else {
141+ return nil , errors .Errorf ("unrecognised KV version: %d" , version )
142+ }
143+ return
144+ }
145+
146+ // because Vault has no way to know if a kv engine is v1 or v2, we have to check
147+ // for the /config path and if it doesn't exist, attempt to LIST the path, if
148+ // that succeeds, it's a v1, if it doesn't succeed, it *might still* be a v1 but
149+ // empty, and in that case there's no way to know so it just bails. Amazing.
150+ func getKVEngineVersion (client * api.Client , basepath string ) (int , error ) {
151+ // only KV v2 has /config, /data, /metadata paths, so attempt to read one
152+ s , err := client .Logical ().Read (path .Join (basepath , "config" ))
153+ if err != nil {
154+ return 0 , errors .Wrap (err , "failed to check engine config path for version query" )
155+ }
156+ if s == nil {
157+ // no /config path present, now attempt to list the engine's base path
158+ l , err := client .Logical ().List (path .Join (basepath ))
159+ if err != nil {
160+ return 0 , errors .Wrap (err , "failed to list possible KV v1 engine" )
161+ }
162+ if l == nil {
163+ return 0 , errors .New ("could not read secrets engine, it's either an empty KV v1 engine or does not exist" )
164+ }
165+ // engine does not have a /config but contains elements, it's a v1.
166+ return 1 , nil
167+ }
168+
169+ // has a /config, it's a v2
170+ return 2 , nil
171+ }
0 commit comments