mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-11-04 04:28:08 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			338 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			338 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package api
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"net/http"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/hashicorp/vault/sdk/helper/consts"
 | 
						|
	"github.com/mitchellh/mapstructure"
 | 
						|
)
 | 
						|
 | 
						|
// ListPluginsInput is used as input to the ListPlugins function.
 | 
						|
type ListPluginsInput struct {
 | 
						|
	// Type of the plugin. Required.
 | 
						|
	Type consts.PluginType `json:"type"`
 | 
						|
}
 | 
						|
 | 
						|
// ListPluginsResponse is the response from the ListPlugins call.
 | 
						|
type ListPluginsResponse struct {
 | 
						|
	// PluginsByType is the list of plugins by type.
 | 
						|
	PluginsByType map[consts.PluginType][]string `json:"types"`
 | 
						|
 | 
						|
	// Names is the list of names of the plugins.
 | 
						|
	//
 | 
						|
	// Deprecated: Newer server responses should be returning PluginsByType (json:
 | 
						|
	// "types") instead.
 | 
						|
	Names []string `json:"names"`
 | 
						|
}
 | 
						|
 | 
						|
// ListPlugins lists all plugins in the catalog and returns their names as a
 | 
						|
// list of strings.
 | 
						|
func (c *Sys) ListPlugins(i *ListPluginsInput) (*ListPluginsResponse, error) {
 | 
						|
	path := ""
 | 
						|
	method := ""
 | 
						|
	if i.Type == consts.PluginTypeUnknown {
 | 
						|
		path = "/v1/sys/plugins/catalog"
 | 
						|
		method = "GET"
 | 
						|
	} else {
 | 
						|
		path = fmt.Sprintf("/v1/sys/plugins/catalog/%s", i.Type)
 | 
						|
		method = "LIST"
 | 
						|
	}
 | 
						|
 | 
						|
	req := c.c.NewRequest(method, path)
 | 
						|
	if method == "LIST" {
 | 
						|
		// Set this for broader compatibility, but we use LIST above to be able
 | 
						|
		// to handle the wrapping lookup function
 | 
						|
		req.Method = "GET"
 | 
						|
		req.Params.Set("list", "true")
 | 
						|
	}
 | 
						|
 | 
						|
	ctx, cancelFunc := context.WithCancel(context.Background())
 | 
						|
	defer cancelFunc()
 | 
						|
	resp, err := c.c.RawRequestWithContext(ctx, req)
 | 
						|
	if err != nil && resp == nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if resp == nil {
 | 
						|
		return nil, nil
 | 
						|
	}
 | 
						|
	defer resp.Body.Close()
 | 
						|
 | 
						|
	// We received an Unsupported Operation response from Vault, indicating
 | 
						|
	// Vault of an older version that doesn't support the GET method yet;
 | 
						|
	// switch it to a LIST.
 | 
						|
	if resp.StatusCode == 405 {
 | 
						|
		req.Params.Set("list", "true")
 | 
						|
		resp, err := c.c.RawRequestWithContext(ctx, req)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		defer resp.Body.Close()
 | 
						|
		var result struct {
 | 
						|
			Data struct {
 | 
						|
				Keys []string `json:"keys"`
 | 
						|
			} `json:"data"`
 | 
						|
		}
 | 
						|
		if err := resp.DecodeJSON(&result); err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		return &ListPluginsResponse{Names: result.Data.Keys}, nil
 | 
						|
	}
 | 
						|
 | 
						|
	secret, err := ParseSecret(resp.Body)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if secret == nil || secret.Data == nil {
 | 
						|
		return nil, errors.New("data from server response is empty")
 | 
						|
	}
 | 
						|
 | 
						|
	result := &ListPluginsResponse{
 | 
						|
		PluginsByType: make(map[consts.PluginType][]string),
 | 
						|
	}
 | 
						|
	if i.Type == consts.PluginTypeUnknown {
 | 
						|
		for pluginTypeStr, pluginsRaw := range secret.Data {
 | 
						|
			pluginType, err := consts.ParsePluginType(pluginTypeStr)
 | 
						|
			if err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
 | 
						|
			pluginsIfc, ok := pluginsRaw.([]interface{})
 | 
						|
			if !ok {
 | 
						|
				return nil, fmt.Errorf("unable to parse plugins for %q type", pluginTypeStr)
 | 
						|
			}
 | 
						|
 | 
						|
			plugins := make([]string, len(pluginsIfc))
 | 
						|
			for i, nameIfc := range pluginsIfc {
 | 
						|
				name, ok := nameIfc.(string)
 | 
						|
				if !ok {
 | 
						|
				}
 | 
						|
				plugins[i] = name
 | 
						|
			}
 | 
						|
			result.PluginsByType[pluginType] = plugins
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		var respKeys []string
 | 
						|
		if err := mapstructure.Decode(secret.Data["keys"], &respKeys); err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		result.PluginsByType[i.Type] = respKeys
 | 
						|
	}
 | 
						|
 | 
						|
	return result, nil
 | 
						|
}
 | 
						|
 | 
						|
// GetPluginInput is used as input to the GetPlugin function.
 | 
						|
type GetPluginInput struct {
 | 
						|
	Name string `json:"-"`
 | 
						|
 | 
						|
	// Type of the plugin. Required.
 | 
						|
	Type consts.PluginType `json:"type"`
 | 
						|
}
 | 
						|
 | 
						|
// GetPluginResponse is the response from the GetPlugin call.
 | 
						|
type GetPluginResponse struct {
 | 
						|
	Args    []string `json:"args"`
 | 
						|
	Builtin bool     `json:"builtin"`
 | 
						|
	Command string   `json:"command"`
 | 
						|
	Name    string   `json:"name"`
 | 
						|
	SHA256  string   `json:"sha256"`
 | 
						|
}
 | 
						|
 | 
						|
// GetPlugin retrieves information about the plugin.
 | 
						|
func (c *Sys) GetPlugin(i *GetPluginInput) (*GetPluginResponse, error) {
 | 
						|
	path := catalogPathByType(i.Type, i.Name)
 | 
						|
	req := c.c.NewRequest(http.MethodGet, path)
 | 
						|
 | 
						|
	ctx, cancelFunc := context.WithCancel(context.Background())
 | 
						|
	defer cancelFunc()
 | 
						|
	resp, err := c.c.RawRequestWithContext(ctx, req)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	defer resp.Body.Close()
 | 
						|
 | 
						|
	var result struct {
 | 
						|
		Data *GetPluginResponse
 | 
						|
	}
 | 
						|
	err = resp.DecodeJSON(&result)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return result.Data, err
 | 
						|
}
 | 
						|
 | 
						|
// RegisterPluginInput is used as input to the RegisterPlugin function.
 | 
						|
type RegisterPluginInput struct {
 | 
						|
	// Name is the name of the plugin. Required.
 | 
						|
	Name string `json:"-"`
 | 
						|
 | 
						|
	// Type of the plugin. Required.
 | 
						|
	Type consts.PluginType `json:"type"`
 | 
						|
 | 
						|
	// Args is the list of args to spawn the process with.
 | 
						|
	Args []string `json:"args,omitempty"`
 | 
						|
 | 
						|
	// Command is the command to run.
 | 
						|
	Command string `json:"command,omitempty"`
 | 
						|
 | 
						|
	// SHA256 is the shasum of the plugin.
 | 
						|
	SHA256 string `json:"sha256,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
// RegisterPlugin registers the plugin with the given information.
 | 
						|
func (c *Sys) RegisterPlugin(i *RegisterPluginInput) error {
 | 
						|
	path := catalogPathByType(i.Type, i.Name)
 | 
						|
	req := c.c.NewRequest(http.MethodPut, path)
 | 
						|
 | 
						|
	if err := req.SetJSONBody(i); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	ctx, cancelFunc := context.WithCancel(context.Background())
 | 
						|
	defer cancelFunc()
 | 
						|
	resp, err := c.c.RawRequestWithContext(ctx, req)
 | 
						|
	if err == nil {
 | 
						|
		defer resp.Body.Close()
 | 
						|
	}
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
// DeregisterPluginInput is used as input to the DeregisterPlugin function.
 | 
						|
type DeregisterPluginInput struct {
 | 
						|
	// Name is the name of the plugin. Required.
 | 
						|
	Name string `json:"-"`
 | 
						|
 | 
						|
	// Type of the plugin. Required.
 | 
						|
	Type consts.PluginType `json:"type"`
 | 
						|
}
 | 
						|
 | 
						|
// DeregisterPlugin removes the plugin with the given name from the plugin
 | 
						|
// catalog.
 | 
						|
func (c *Sys) DeregisterPlugin(i *DeregisterPluginInput) error {
 | 
						|
	path := catalogPathByType(i.Type, i.Name)
 | 
						|
	req := c.c.NewRequest(http.MethodDelete, path)
 | 
						|
 | 
						|
	ctx, cancelFunc := context.WithCancel(context.Background())
 | 
						|
	defer cancelFunc()
 | 
						|
	resp, err := c.c.RawRequestWithContext(ctx, req)
 | 
						|
	if err == nil {
 | 
						|
		defer resp.Body.Close()
 | 
						|
	}
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
// ReloadPluginInput is used as input to the ReloadPlugin function.
 | 
						|
type ReloadPluginInput struct {
 | 
						|
	// Plugin is the name of the plugin to reload, as registered in the plugin catalog
 | 
						|
	Plugin string `json:"plugin"`
 | 
						|
 | 
						|
	// Mounts is the array of string mount paths of the plugin backends to reload
 | 
						|
	Mounts []string `json:"mounts"`
 | 
						|
 | 
						|
	// Scope is the scope of the plugin reload
 | 
						|
	Scope string `json:"scope"`
 | 
						|
}
 | 
						|
 | 
						|
// ReloadPlugin reloads mounted plugin backends, possibly returning
 | 
						|
// reloadId for a cluster scoped reload
 | 
						|
func (c *Sys) ReloadPlugin(i *ReloadPluginInput) (string, error) {
 | 
						|
	path := "/v1/sys/plugins/reload/backend"
 | 
						|
	req := c.c.NewRequest(http.MethodPut, path)
 | 
						|
 | 
						|
	if err := req.SetJSONBody(i); err != nil {
 | 
						|
		return "", err
 | 
						|
	}
 | 
						|
 | 
						|
	ctx, cancelFunc := context.WithCancel(context.Background())
 | 
						|
	defer cancelFunc()
 | 
						|
 | 
						|
	resp, err := c.c.RawRequestWithContext(ctx, req)
 | 
						|
	if err != nil {
 | 
						|
		return "", err
 | 
						|
	}
 | 
						|
	defer resp.Body.Close()
 | 
						|
 | 
						|
	if i.Scope == "global" {
 | 
						|
		// Get the reload id
 | 
						|
		secret, parseErr := ParseSecret(resp.Body)
 | 
						|
		if parseErr != nil {
 | 
						|
			return "", parseErr
 | 
						|
		}
 | 
						|
		if _, ok := secret.Data["reload_id"]; ok {
 | 
						|
			return secret.Data["reload_id"].(string), nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return "", err
 | 
						|
}
 | 
						|
 | 
						|
// ReloadStatus is the status of an individual node's plugin reload
 | 
						|
type ReloadStatus struct {
 | 
						|
	Timestamp time.Time `json:"timestamp" mapstructure:"timestamp"`
 | 
						|
	Error     string    `json:"error" mapstructure:"error"`
 | 
						|
}
 | 
						|
 | 
						|
// ReloadStatusResponse is the combined response of all known completed plugin reloads
 | 
						|
type ReloadStatusResponse struct {
 | 
						|
	ReloadID string                   `mapstructure:"reload_id"`
 | 
						|
	Results  map[string]*ReloadStatus `mapstructure:"results"`
 | 
						|
}
 | 
						|
 | 
						|
// ReloadPluginStatusInput is used as input to the ReloadStatusPlugin function.
 | 
						|
type ReloadPluginStatusInput struct {
 | 
						|
	// ReloadID is the ID of the reload operation
 | 
						|
	ReloadID string `json:"reload_id"`
 | 
						|
}
 | 
						|
 | 
						|
// ReloadPluginStatus retrieves the status of a reload operation
 | 
						|
func (c *Sys) ReloadPluginStatus(reloadStatusInput *ReloadPluginStatusInput) (*ReloadStatusResponse, error) {
 | 
						|
	path := "/v1/sys/plugins/reload/backend/status"
 | 
						|
	req := c.c.NewRequest(http.MethodGet, path)
 | 
						|
	req.Params.Add("reload_id", reloadStatusInput.ReloadID)
 | 
						|
 | 
						|
	ctx, cancelFunc := context.WithCancel(context.Background())
 | 
						|
	defer cancelFunc()
 | 
						|
 | 
						|
	resp, err := c.c.RawRequestWithContext(ctx, req)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	defer resp.Body.Close()
 | 
						|
	if resp != nil {
 | 
						|
		secret, parseErr := ParseSecret(resp.Body)
 | 
						|
		if parseErr != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		var r ReloadStatusResponse
 | 
						|
		d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
 | 
						|
			DecodeHook: mapstructure.StringToTimeHookFunc(time.RFC3339),
 | 
						|
			Result:     &r,
 | 
						|
		})
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		err = d.Decode(secret.Data)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		return &r, nil
 | 
						|
	}
 | 
						|
	return nil, nil
 | 
						|
}
 | 
						|
 | 
						|
// catalogPathByType is a helper to construct the proper API path by plugin type
 | 
						|
func catalogPathByType(pluginType consts.PluginType, name string) string {
 | 
						|
	path := fmt.Sprintf("/v1/sys/plugins/catalog/%s/%s", pluginType, name)
 | 
						|
 | 
						|
	// Backwards compat, if type is not provided then use old path
 | 
						|
	if pluginType == consts.PluginTypeUnknown {
 | 
						|
		path = fmt.Sprintf("/v1/sys/plugins/catalog/%s", name)
 | 
						|
	}
 | 
						|
 | 
						|
	return path
 | 
						|
}
 |