1package api
2
3import (
4	"context"
5	"errors"
6	"fmt"
7	"net/http"
8
9	"github.com/hashicorp/vault/sdk/helper/consts"
10	"github.com/mitchellh/mapstructure"
11)
12
13// ListPluginsInput is used as input to the ListPlugins function.
14type ListPluginsInput struct {
15	// Type of the plugin. Required.
16	Type consts.PluginType `json:"type"`
17}
18
19// ListPluginsResponse is the response from the ListPlugins call.
20type ListPluginsResponse struct {
21	// PluginsByType is the list of plugins by type.
22	PluginsByType map[consts.PluginType][]string `json:"types"`
23
24	// Names is the list of names of the plugins.
25	//
26	// Deprecated: Newer server responses should be returning PluginsByType (json:
27	// "types") instead.
28	Names []string `json:"names"`
29}
30
31// ListPlugins lists all plugins in the catalog and returns their names as a
32// list of strings.
33func (c *Sys) ListPlugins(i *ListPluginsInput) (*ListPluginsResponse, error) {
34	path := ""
35	method := ""
36	if i.Type == consts.PluginTypeUnknown {
37		path = "/v1/sys/plugins/catalog"
38		method = "GET"
39	} else {
40		path = fmt.Sprintf("/v1/sys/plugins/catalog/%s", i.Type)
41		method = "LIST"
42	}
43
44	req := c.c.NewRequest(method, path)
45	if method == "LIST" {
46		// Set this for broader compatibility, but we use LIST above to be able
47		// to handle the wrapping lookup function
48		req.Method = "GET"
49		req.Params.Set("list", "true")
50	}
51
52	ctx, cancelFunc := context.WithCancel(context.Background())
53	defer cancelFunc()
54	resp, err := c.c.RawRequestWithContext(ctx, req)
55	if err != nil && resp == nil {
56		return nil, err
57	}
58	if resp == nil {
59		return nil, nil
60	}
61	defer resp.Body.Close()
62
63	// We received an Unsupported Operation response from Vault, indicating
64	// Vault of an older version that doesn't support the GET method yet;
65	// switch it to a LIST.
66	if resp.StatusCode == 405 {
67		req.Params.Set("list", "true")
68		resp, err := c.c.RawRequestWithContext(ctx, req)
69		if err != nil {
70			return nil, err
71		}
72		defer resp.Body.Close()
73		var result struct {
74			Data struct {
75				Keys []string `json:"keys"`
76			} `json:"data"`
77		}
78		if err := resp.DecodeJSON(&result); err != nil {
79			return nil, err
80		}
81		return &ListPluginsResponse{Names: result.Data.Keys}, nil
82	}
83
84	secret, err := ParseSecret(resp.Body)
85	if err != nil {
86		return nil, err
87	}
88	if secret == nil || secret.Data == nil {
89		return nil, errors.New("data from server response is empty")
90	}
91
92	result := &ListPluginsResponse{
93		PluginsByType: make(map[consts.PluginType][]string),
94	}
95	if i.Type == consts.PluginTypeUnknown {
96		for pluginTypeStr, pluginsRaw := range secret.Data {
97			pluginType, err := consts.ParsePluginType(pluginTypeStr)
98			if err != nil {
99				return nil, err
100			}
101
102			pluginsIfc, ok := pluginsRaw.([]interface{})
103			if !ok {
104				return nil, fmt.Errorf("unable to parse plugins for %q type", pluginTypeStr)
105			}
106
107			plugins := make([]string, len(pluginsIfc))
108			for i, nameIfc := range pluginsIfc {
109				name, ok := nameIfc.(string)
110				if !ok {
111
112				}
113				plugins[i] = name
114			}
115			result.PluginsByType[pluginType] = plugins
116		}
117	} else {
118		var respKeys []string
119		if err := mapstructure.Decode(secret.Data["keys"], &respKeys); err != nil {
120			return nil, err
121		}
122		result.PluginsByType[i.Type] = respKeys
123	}
124
125	return result, nil
126}
127
128// GetPluginInput is used as input to the GetPlugin function.
129type GetPluginInput struct {
130	Name string `json:"-"`
131
132	// Type of the plugin. Required.
133	Type consts.PluginType `json:"type"`
134}
135
136// GetPluginResponse is the response from the GetPlugin call.
137type GetPluginResponse struct {
138	Args    []string `json:"args"`
139	Builtin bool     `json:"builtin"`
140	Command string   `json:"command"`
141	Name    string   `json:"name"`
142	SHA256  string   `json:"sha256"`
143}
144
145// GetPlugin retrieves information about the plugin.
146func (c *Sys) GetPlugin(i *GetPluginInput) (*GetPluginResponse, error) {
147	path := catalogPathByType(i.Type, i.Name)
148	req := c.c.NewRequest(http.MethodGet, path)
149
150	ctx, cancelFunc := context.WithCancel(context.Background())
151	defer cancelFunc()
152	resp, err := c.c.RawRequestWithContext(ctx, req)
153	if err != nil {
154		return nil, err
155	}
156	defer resp.Body.Close()
157
158	var result struct {
159		Data *GetPluginResponse
160	}
161	err = resp.DecodeJSON(&result)
162	if err != nil {
163		return nil, err
164	}
165	return result.Data, err
166}
167
168// RegisterPluginInput is used as input to the RegisterPlugin function.
169type RegisterPluginInput struct {
170	// Name is the name of the plugin. Required.
171	Name string `json:"-"`
172
173	// Type of the plugin. Required.
174	Type consts.PluginType `json:"type"`
175
176	// Args is the list of args to spawn the process with.
177	Args []string `json:"args,omitempty"`
178
179	// Command is the command to run.
180	Command string `json:"command,omitempty"`
181
182	// SHA256 is the shasum of the plugin.
183	SHA256 string `json:"sha256,omitempty"`
184}
185
186// RegisterPlugin registers the plugin with the given information.
187func (c *Sys) RegisterPlugin(i *RegisterPluginInput) error {
188	path := catalogPathByType(i.Type, i.Name)
189	req := c.c.NewRequest(http.MethodPut, path)
190
191	if err := req.SetJSONBody(i); err != nil {
192		return err
193	}
194
195	ctx, cancelFunc := context.WithCancel(context.Background())
196	defer cancelFunc()
197	resp, err := c.c.RawRequestWithContext(ctx, req)
198	if err == nil {
199		defer resp.Body.Close()
200	}
201	return err
202}
203
204// DeregisterPluginInput is used as input to the DeregisterPlugin function.
205type DeregisterPluginInput struct {
206	// Name is the name of the plugin. Required.
207	Name string `json:"-"`
208
209	// Type of the plugin. Required.
210	Type consts.PluginType `json:"type"`
211}
212
213// DeregisterPlugin removes the plugin with the given name from the plugin
214// catalog.
215func (c *Sys) DeregisterPlugin(i *DeregisterPluginInput) error {
216	path := catalogPathByType(i.Type, i.Name)
217	req := c.c.NewRequest(http.MethodDelete, path)
218
219	ctx, cancelFunc := context.WithCancel(context.Background())
220	defer cancelFunc()
221	resp, err := c.c.RawRequestWithContext(ctx, req)
222	if err == nil {
223		defer resp.Body.Close()
224	}
225	return err
226}
227
228// catalogPathByType is a helper to construct the proper API path by plugin type
229func catalogPathByType(pluginType consts.PluginType, name string) string {
230	path := fmt.Sprintf("/v1/sys/plugins/catalog/%s/%s", pluginType, name)
231
232	// Backwards compat, if type is not provided then use old path
233	if pluginType == consts.PluginTypeUnknown {
234		path = fmt.Sprintf("/v1/sys/plugins/catalog/%s", name)
235	}
236
237	return path
238}
239