-
Notifications
You must be signed in to change notification settings - Fork 2
/
sys.go
250 lines (219 loc) · 6.66 KB
/
sys.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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
package vaultkv
import (
"encoding/json"
"errors"
"io/ioutil"
"net/url"
"strings"
)
func (v *Client) doSysRequest(
method, path string,
input interface{},
output interface{}) error {
err := v.doRequest(method, path, input, output)
//In sys contexts, 400 can mean that the Vault is uninitialized.
if _, is400 := err.(*ErrBadRequest); is400 {
initialized, initErr := v.IsInitialized()
if initErr != nil {
return initErr
}
if !initialized {
return &ErrUninitialized{message: "Your Vault is not initialized"}
}
}
return err
}
//IsInitialized returns true if the targeted Vault is initialized
func (v *Client) IsInitialized() (is bool, err error) {
//Don't call doSysRequest from here because it calls IsInitialized
// and that could get ugly
err = v.doRequest(
"GET",
"/sys/init",
nil,
&struct {
Initialized *bool `json:"initialized"`
}{
Initialized: &is,
})
return
}
//SealState is the return value from Unseal and SealStatus. Type is only
//populated by SealStatus. ClusterName and ClusterID are only populated is
//Vault is unsealed.
type SealState struct {
//Type is the type of unseal key. It is not returned from Unseal
Type string `json:"type,omitempty"`
Sealed bool `json:"sealed"`
//Threshold is the number of keys required to reconstruct the master key
Threshold int `json:"t"`
//NumShares is the number of keys the master key has been split into
NumShares int `json:"n"`
//Progress is the number of keys that have been provided in the current unseal attempt
Progress int `json:"progress"`
Nonce string `json:"nonce"`
Version string `json:"version"`
//ClusterName is only returned from an unsealed Vault.
ClusterName string `json:"cluster_name,omitempty"`
//ClusterID is only returned from an unsealed Vault.
ClusterID string `json:"cluster_id,omitempty"`
}
//SealStatus calls the /sys/seal-status endpoint and returns the info therein
func (v *Client) SealStatus() (ret *SealState, err error) {
err = v.doSysRequest(
"GET",
"/sys/seal-status",
nil,
&ret)
return
}
//InitConfig is the information passed to InitVault to configure the Vault.
//Shares and Threshold are required.
type InitConfig struct {
//Split the master key into this many shares
Shares int `json:"secret_shares"`
//This many shares are required to reconstruct the master key
Threshold int `json:"secret_threshold"`
RootTokenPGPKey string `json:"root_token_pgp_key"`
PGPKeys []string `json:"pgp_keys"`
}
//InitVaultOutput is the return value of InitVault, and contains the generated
//Keys and RootToken.
type InitVaultOutput struct {
client *Client
Keys []string `json:"keys"`
KeysBase64 []string `json:"keys_base64"`
RootToken string `json:"root_token"`
}
//Unseal takes the keys in the InitVaultOutput object and sends each one to the
//unseal endpoint. If any of the unseal calls are unsuccessful, an error is
//returned.
func (i *InitVaultOutput) Unseal() error {
for _, key := range i.Keys {
sealState, err := i.client.Unseal(key)
if err != nil {
return err
}
if !sealState.Sealed {
break
}
}
return nil
}
//InitVault puts to the /sys/init endpoint to initialize the Vault, and returns
// the root token and unseal keys that were generated. The token of the client
// object is automatically set to the root token if the init is successful.
//If the vault has already been initialized, this returns *ErrBadRequest
func (v *Client) InitVault(in InitConfig) (out *InitVaultOutput, err error) {
out = &InitVaultOutput{}
err = v.doSysRequest(
"PUT",
"/sys/init",
&in,
&out,
)
if err == nil {
v.AuthToken = out.RootToken
}
out.client = v
return
}
//Seal puts to the /sys/seal endpoint to seal the Vault.
// If the Vault is already sealed, this doesn't return an error.
// If the Vault is unsealed and an incorrect token is provided, then this
// returns *ErrForbidden. Newer versions of Vault (0.11.2+) APIs return errors
// if the Vault is uninitialized or already sealed. This function squelches
// these errors for consistency with earlier versions of Vault
func (v *Client) Seal() error {
err := v.doSysRequest("PUT", "/sys/seal", nil, nil)
if err != nil && (IsUninitialized(err) || IsSealed(err)) {
err = nil
}
return err
}
//Unseal puts to the /sys/unseal endpoint with a single key to progress the
//unseal attempt. If the unseal was successful, then the Sealed member of the
//returned struct will be false. If the given unseal key is improperly
//formatted, an *ErrBadRequest is returned. If the vault is already unsealed,
//no error is returned
func (v *Client) Unseal(key string) (out *SealState, err error) {
out = &SealState{}
err = v.doSysRequest(
"PUT",
"/sys/unseal",
&struct {
Key string `json:"key"`
}{
Key: key,
},
&out,
)
if IsInternalServer(err) {
if strings.Contains(err.Error(), "message authentication failed") {
err = &ErrBadRequest{message: err.Error()}
}
}
return
}
//ResetUnseal resets the current unseal attempt, such that the progress towards
//an unseal becomes 0. If the vault is unsealed, nothing happens and no error
//is returned.
func (v *Client) ResetUnseal() (err error) {
err = v.doSysRequest(
"PUT",
"/sys/unseal",
&struct {
Reset bool `json:"reset"`
}{
Reset: true,
},
nil,
)
return
}
//Health gives information about the current state of the Vault. If standbyok
//is set to true, no error will be returned in the case that the targeted vault
//is a standby node or a performance standby node. If the targeted node is a
//standby and standbyok is false, then ErrStandby will be returned. If the
//Vault is not yet initialized, ErrUninitialized will be returned. If the Vault
//is initialized but sealed, then ErrSealed will be returned. If none of these
//are the case, no error is returned.
func (v *Client) Health(standbyok bool) error {
//Don't call doRequest from Health because ParseError calls Health
query := url.Values{}
if standbyok {
query.Add("standbyok", "true")
query.Add("perfstandbyok", "true")
}
resp, err := v.Curl("GET", "/sys/health", query, nil)
if err != nil {
return err
}
errorsStruct := apiError{}
err = json.NewDecoder(resp.Body).Decode(&errorsStruct)
if err != nil {
return err
}
_, err = ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
errorMessage := strings.Join(errorsStruct.Errors, "\n")
switch resp.StatusCode {
case 200:
err = nil
case 429:
err = &ErrStandby{message: errorMessage}
case 472:
err = &ErrDRSecondary{message: errorMessage}
case 473:
err = &ErrPerfStandby{message: errorMessage}
case 501:
err = &ErrUninitialized{message: errorMessage}
case 503:
err = &ErrSealed{message: errorMessage}
default:
err = errors.New(errorMessage)
}
return err
}