1package api
2
3import (
4	"bytes"
5	"context"
6	"fmt"
7	"io"
8	"net/url"
9	"os"
10
11	"github.com/hashicorp/errwrap"
12	"github.com/hashicorp/vault/sdk/helper/jsonutil"
13)
14
15const (
16	wrappedResponseLocation = "cubbyhole/response"
17)
18
19var (
20	// The default TTL that will be used with `sys/wrapping/wrap`, can be
21	// changed
22	DefaultWrappingTTL = "5m"
23
24	// The default function used if no other function is set, which honors the
25	// env var and wraps `sys/wrapping/wrap`
26	DefaultWrappingLookupFunc = func(operation, path string) string {
27		if os.Getenv(EnvVaultWrapTTL) != "" {
28			return os.Getenv(EnvVaultWrapTTL)
29		}
30
31		if (operation == "PUT" || operation == "POST") && path == "sys/wrapping/wrap" {
32			return DefaultWrappingTTL
33		}
34
35		return ""
36	}
37)
38
39// Logical is used to perform logical backend operations on Vault.
40type Logical struct {
41	c *Client
42}
43
44// Logical is used to return the client for logical-backend API calls.
45func (c *Client) Logical() *Logical {
46	return &Logical{c: c}
47}
48
49func (c *Logical) Read(path string) (*Secret, error) {
50	return c.ReadWithData(path, nil)
51}
52
53func (c *Logical) ReadWithData(path string, data map[string][]string) (*Secret, error) {
54	r := c.c.NewRequest("GET", "/v1/"+path)
55
56	var values url.Values
57	for k, v := range data {
58		if values == nil {
59			values = make(url.Values)
60		}
61		for _, val := range v {
62			values.Add(k, val)
63		}
64	}
65
66	if values != nil {
67		r.Params = values
68	}
69
70	ctx, cancelFunc := context.WithCancel(context.Background())
71	defer cancelFunc()
72	resp, err := c.c.RawRequestWithContext(ctx, r)
73	if resp != nil {
74		defer resp.Body.Close()
75	}
76	if resp != nil && resp.StatusCode == 404 {
77		secret, parseErr := ParseSecret(resp.Body)
78		switch parseErr {
79		case nil:
80		case io.EOF:
81			return nil, nil
82		default:
83			return nil, err
84		}
85		if secret != nil && (len(secret.Warnings) > 0 || len(secret.Data) > 0) {
86			return secret, nil
87		}
88		return nil, nil
89	}
90	if err != nil {
91		return nil, err
92	}
93
94	return ParseSecret(resp.Body)
95}
96
97func (c *Logical) List(path string) (*Secret, error) {
98	r := c.c.NewRequest("LIST", "/v1/"+path)
99	// Set this for broader compatibility, but we use LIST above to be able to
100	// handle the wrapping lookup function
101	r.Method = "GET"
102	r.Params.Set("list", "true")
103
104	ctx, cancelFunc := context.WithCancel(context.Background())
105	defer cancelFunc()
106	resp, err := c.c.RawRequestWithContext(ctx, r)
107	if resp != nil {
108		defer resp.Body.Close()
109	}
110	if resp != nil && resp.StatusCode == 404 {
111		secret, parseErr := ParseSecret(resp.Body)
112		switch parseErr {
113		case nil:
114		case io.EOF:
115			return nil, nil
116		default:
117			return nil, err
118		}
119		if secret != nil && (len(secret.Warnings) > 0 || len(secret.Data) > 0) {
120			return secret, nil
121		}
122		return nil, nil
123	}
124	if err != nil {
125		return nil, err
126	}
127
128	return ParseSecret(resp.Body)
129}
130
131func (c *Logical) Write(path string, data map[string]interface{}) (*Secret, error) {
132	r := c.c.NewRequest("PUT", "/v1/"+path)
133	if err := r.SetJSONBody(data); err != nil {
134		return nil, err
135	}
136
137	return c.write(path, r)
138}
139
140func (c *Logical) WriteBytes(path string, data []byte) (*Secret, error) {
141	r := c.c.NewRequest("PUT", "/v1/"+path)
142	r.BodyBytes = data
143
144	return c.write(path, r)
145}
146
147func (c *Logical) write(path string, request *Request) (*Secret, error) {
148	ctx, cancelFunc := context.WithCancel(context.Background())
149	defer cancelFunc()
150	resp, err := c.c.RawRequestWithContext(ctx, request)
151	if resp != nil {
152		defer resp.Body.Close()
153	}
154	if resp != nil && resp.StatusCode == 404 {
155		secret, parseErr := ParseSecret(resp.Body)
156		switch parseErr {
157		case nil:
158		case io.EOF:
159			return nil, nil
160		default:
161			return nil, err
162		}
163		if secret != nil && (len(secret.Warnings) > 0 || len(secret.Data) > 0) {
164			return secret, err
165		}
166	}
167	if err != nil {
168		return nil, err
169	}
170
171	return ParseSecret(resp.Body)
172}
173
174func (c *Logical) Delete(path string) (*Secret, error) {
175	return c.DeleteWithData(path, nil)
176}
177
178func (c *Logical) DeleteWithData(path string, data map[string][]string) (*Secret, error) {
179	r := c.c.NewRequest("DELETE", "/v1/"+path)
180
181	var values url.Values
182	for k, v := range data {
183		if values == nil {
184			values = make(url.Values)
185		}
186		for _, val := range v {
187			values.Add(k, val)
188		}
189	}
190
191	if values != nil {
192		r.Params = values
193	}
194
195	ctx, cancelFunc := context.WithCancel(context.Background())
196	defer cancelFunc()
197	resp, err := c.c.RawRequestWithContext(ctx, r)
198	if resp != nil {
199		defer resp.Body.Close()
200	}
201	if resp != nil && resp.StatusCode == 404 {
202		secret, parseErr := ParseSecret(resp.Body)
203		switch parseErr {
204		case nil:
205		case io.EOF:
206			return nil, nil
207		default:
208			return nil, err
209		}
210		if secret != nil && (len(secret.Warnings) > 0 || len(secret.Data) > 0) {
211			return secret, err
212		}
213	}
214	if err != nil {
215		return nil, err
216	}
217
218	return ParseSecret(resp.Body)
219}
220
221func (c *Logical) Unwrap(wrappingToken string) (*Secret, error) {
222	var data map[string]interface{}
223	if wrappingToken != "" {
224		if c.c.Token() == "" {
225			c.c.SetToken(wrappingToken)
226		} else if wrappingToken != c.c.Token() {
227			data = map[string]interface{}{
228				"token": wrappingToken,
229			}
230		}
231	}
232
233	r := c.c.NewRequest("PUT", "/v1/sys/wrapping/unwrap")
234	if err := r.SetJSONBody(data); err != nil {
235		return nil, err
236	}
237
238	ctx, cancelFunc := context.WithCancel(context.Background())
239	defer cancelFunc()
240	resp, err := c.c.RawRequestWithContext(ctx, r)
241	if resp != nil {
242		defer resp.Body.Close()
243	}
244	if resp == nil || resp.StatusCode != 404 {
245		if err != nil {
246			return nil, err
247		}
248		if resp == nil {
249			return nil, nil
250		}
251		return ParseSecret(resp.Body)
252	}
253
254	// In the 404 case this may actually be a wrapped 404 error
255	secret, parseErr := ParseSecret(resp.Body)
256	switch parseErr {
257	case nil:
258	case io.EOF:
259		return nil, nil
260	default:
261		return nil, err
262	}
263	if secret != nil && (len(secret.Warnings) > 0 || len(secret.Data) > 0) {
264		return secret, nil
265	}
266
267	// Otherwise this might be an old-style wrapping token so attempt the old
268	// method
269	if wrappingToken != "" {
270		origToken := c.c.Token()
271		defer c.c.SetToken(origToken)
272		c.c.SetToken(wrappingToken)
273	}
274
275	secret, err = c.Read(wrappedResponseLocation)
276	if err != nil {
277		return nil, errwrap.Wrapf(fmt.Sprintf("error reading %q: {{err}}", wrappedResponseLocation), err)
278	}
279	if secret == nil {
280		return nil, fmt.Errorf("no value found at %q", wrappedResponseLocation)
281	}
282	if secret.Data == nil {
283		return nil, fmt.Errorf("\"data\" not found in wrapping response")
284	}
285	if _, ok := secret.Data["response"]; !ok {
286		return nil, fmt.Errorf("\"response\" not found in wrapping response \"data\" map")
287	}
288
289	wrappedSecret := new(Secret)
290	buf := bytes.NewBufferString(secret.Data["response"].(string))
291	if err := jsonutil.DecodeJSONFromReader(buf, wrappedSecret); err != nil {
292		return nil, errwrap.Wrapf("error unmarshalling wrapped secret: {{err}}", err)
293	}
294
295	return wrappedSecret, nil
296}
297