-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathclient.go
190 lines (159 loc) · 4.84 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
package client
import (
"errors"
"fmt"
"path/filepath"
"strconv"
"strings"
"github.com/fishi0x01/vsh/log"
"github.com/hashicorp/vault/api"
)
// Client wrapper for Vault API client
type Client struct {
Vault *api.Client
Name string
Pwd string
KVBackends map[string]int
listCache map[string][]string
}
// VaultConfig container to keep parameters for Client configuration
type VaultConfig struct {
Addr string
Token string
StartPath string
}
func verifyClientPwd(client *Client) (*Client, error) {
if client.Pwd == "" {
client.Pwd = "/"
}
if !strings.HasSuffix(client.Pwd, "/") {
client.Pwd = client.Pwd + "/"
}
if !strings.HasPrefix(client.Pwd, "/") {
client.Pwd = "/" + client.Pwd
}
t := client.GetType(client.Pwd)
if t != NODE && t != BACKEND {
return nil, errors.New("VAULT_PATH is not a valid directory path")
}
return client, nil
}
// NewClient creates a new Client Vault wrapper
func NewClient(conf *VaultConfig) (*Client, error) {
vault, err := api.NewClient(&api.Config{
Address: conf.Addr,
})
if err != nil {
return nil, err
}
vault.SetToken(conf.Token)
permissions, err := vault.Sys().CapabilitiesSelf("sys/mounts")
var mounts map[string]*api.MountOutput
if sliceContains(permissions, "list") || sliceContains(permissions, "root") {
mounts, err = vault.Sys().ListMounts()
} else {
log.UserDebug("Cannot auto-discover mount backends: Token does not have list permission on sys/mounts")
}
if err != nil {
log.AppTrace("%+v", err)
return nil, err
}
var backends = make(map[string]int)
for path, mount := range mounts {
if version, ok := mount.Options["version"]; ok {
v, err := strconv.Atoi(version)
if err != nil {
return nil, err
}
backends[path] = v
log.UserDebug("Found KV backend '%v' with version '%v'", path, v)
}
}
return verifyClientPwd(&Client{
Vault: vault,
Name: conf.Addr,
Pwd: conf.StartPath,
KVBackends: backends,
listCache: make(map[string][]string),
})
}
// Read returns secret at given path, using given Client
func (client *Client) Read(absolutePath string) (secret *Secret, err error) {
var apiSecret *api.Secret
if client.isTopLevelPath(absolutePath) {
apiSecret, err = client.topLevelRead(normalizedVaultPath(absolutePath))
} else {
apiSecret, err = client.lowLevelRead(normalizedVaultPath(absolutePath))
}
if apiSecret != nil {
secret = NewSecret(apiSecret)
}
return secret, err
}
// Write writes secret to given path, using given Client
func (client *Client) Write(absolutePath string, secret *Secret) (err error) {
if client.isTopLevelPath(absolutePath) {
err = client.topLevelWrite(normalizedVaultPath(absolutePath))
} else {
err = client.lowLevelWrite(normalizedVaultPath(absolutePath), secret.GetAPISecret())
}
return err
}
// Delete deletes secret at given absolutePath, using given client
func (client *Client) Delete(absolutePath string) (err error) {
if client.isTopLevelPath(absolutePath) {
err = client.topLevelDelete(normalizedVaultPath(absolutePath))
} else {
err = client.lowLevelDelete(normalizedVaultPath(absolutePath))
}
return err
}
// List elements at the given absolutePath, using the given client
func (client *Client) List(absolutePath string) (result []string, err error) {
if val, ok := client.listCache[absolutePath]; ok {
return val, nil
}
if client.isTopLevelPath(absolutePath) {
result = client.listTopLevel()
} else {
result, err = client.listLowLevel(normalizedVaultPath(absolutePath))
}
client.listCache[absolutePath] = result
return result, err
}
// GetType returns the file type the given absolutePath points to. Possible return values are BACKEND, NODE, LEAF or NONE
func (client *Client) GetType(absolutePath string) (kind PathKind) {
if client.isTopLevelPath(absolutePath) {
kind = client.topLevelType(normalizedVaultPath(absolutePath))
} else {
kind = client.lowLevelType(normalizedVaultPath(absolutePath))
}
return kind
}
// Traverse traverses given absolutePath via DFS and returns sub-paths in array
func (client *Client) Traverse(absolutePath string) (paths []string) {
if client.isTopLevelPath(absolutePath) {
paths = client.topLevelTraverse()
} else {
paths = client.lowLevelTraverse(normalizedVaultPath(absolutePath))
}
return paths
}
// SubpathsForPath will return an array of absolute paths at or below path
func (client *Client) SubpathsForPath(path string) (filePaths []string, err error) {
switch t := client.GetType(path); t {
case LEAF:
filePaths = append(filePaths, filepath.Clean(path))
case NODE:
for _, traversedPath := range client.Traverse(path) {
filePaths = append(filePaths, traversedPath)
}
default:
return filePaths, fmt.Errorf("Not a valid path for operation: %s", path)
}
return filePaths, nil
}
// ClearCache clears the list cache
func (client *Client) ClearCache() {
client.listCache = make(map[string][]string)
}