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