Set namespace on vault client if VAULT_NAMESPACE env is set (#6867)

This commit is contained in:
poornas 2018-11-27 14:42:32 -08:00 committed by kannappanr
parent b65cf281fd
commit 45bb11e020
25 changed files with 649 additions and 204 deletions

View file

@ -192,6 +192,11 @@ func NewVault(kmsConf KMSConfig) (KMS, error) {
if err != nil {
return nil, err
}
if ns, ok := os.LookupEnv("VAULT_NAMESPACE"); ok {
c.SetNamespace(ns)
}
accessToken, leaseDuration, err := getVaultAccessToken(c, config.Auth.AppRole.ID, config.Auth.AppRole.Secret)
if err != nil {
return nil, err

View file

@ -50,6 +50,8 @@ vault write -f auth/approle/role/my-role/secret-id
The AppRole ID, AppRole Secret Id, Vault endpoint and Vault key name can now be used to start minio server with Vault as KMS.
Note: If [Vault Namespaces](https://learn.hashicorp.com/vault/operations/namespaces) are in use, VAULT_NAMESPACE variable needs to be set before setting approle and transit secrets engine.
### 3. Environment variables
You'll need the Vault endpoint, AppRole ID, AppRole SecretID and encryption key-ring name defined in step 2.2
@ -67,6 +69,10 @@ Optionally set `MINIO_SSE_VAULT_CAPATH` as the path to a directory of PEM-encode
export MINIO_SSE_VAULT_CAPATH=/home/user/custom-pems
```
Optionally set `VAULT_NAMESPACE` if AppRole and Transit Secrets engine have been scoped to Vault Namespace
```
export VAULT_NAMESPACE=ns1
```
### 4. Test your setup
To test this setup, start minio server with environment variables set in Step 3, and server is ready to handle SSE-S3 requests.

View file

@ -271,4 +271,5 @@ type TokenCreateRequest struct {
DisplayName string `json:"display_name"`
NumUses int `json:"num_uses"`
Renewable *bool `json:"renewable,omitempty"`
Type string `json:"type"`
}

View file

@ -17,8 +17,9 @@ import (
"github.com/hashicorp/errwrap"
"github.com/hashicorp/go-cleanhttp"
retryablehttp "github.com/hashicorp/go-retryablehttp"
"github.com/hashicorp/go-retryablehttp"
"github.com/hashicorp/go-rootcerts"
"github.com/hashicorp/vault/helper/consts"
"github.com/hashicorp/vault/helper/parseutil"
"golang.org/x/net/http2"
"golang.org/x/time/rate"
@ -120,7 +121,7 @@ type TLSConfig struct {
func DefaultConfig() *Config {
config := &Config{
Address: "https://127.0.0.1:8200",
HttpClient: cleanhttp.DefaultClient(),
HttpClient: cleanhttp.DefaultPooledClient(),
}
config.HttpClient.Timeout = time.Second * 60
@ -464,6 +465,19 @@ func (c *Client) SetMFACreds(creds []string) {
c.mfaCreds = creds
}
// SetNamespace sets the namespace supplied either via the environment
// variable or via the command line.
func (c *Client) SetNamespace(namespace string) {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
if c.headers == nil {
c.headers = make(http.Header)
}
c.headers.Set(consts.NamespaceHeaderName, namespace)
}
// Token returns the access token being used by this client. It will
// return the empty string if there is no token set.
func (c *Client) Token() string {
@ -490,6 +504,26 @@ func (c *Client) ClearToken() {
c.token = ""
}
// Headers gets the current set of headers used for requests. This returns a
// copy; to modify it make modifications locally and use SetHeaders.
func (c *Client) Headers() http.Header {
c.modifyLock.RLock()
defer c.modifyLock.RUnlock()
if c.headers == nil {
return nil
}
ret := make(http.Header)
for k, v := range c.headers {
for _, val := range v {
ret[k] = append(ret[k], val)
}
}
return ret
}
// SetHeaders sets the headers to be used for future requests.
func (c *Client) SetHeaders(headers http.Header) {
c.modifyLock.Lock()
@ -512,6 +546,10 @@ func (c *Client) SetBackoff(backoff retryablehttp.Backoff) {
// underlying http.Client is used; modifying the client from more than one
// goroutine at once may not be safe, so modify the client as needed and then
// clone.
//
// Also, only the client's config is currently copied; this means items not in
// the api.Config struct, such as policy override and wrapping function
// behavior, must currently then be set as desired on the new client.
func (c *Client) Clone() (*Client, error) {
c.modifyLock.RLock()
c.config.modifyLock.RLock()

View file

@ -5,6 +5,7 @@ import (
"context"
"fmt"
"io"
"net/url"
"os"
"github.com/hashicorp/errwrap"
@ -46,8 +47,26 @@ func (c *Client) Logical() *Logical {
}
func (c *Logical) Read(path string) (*Secret, error) {
return c.ReadWithData(path, nil)
}
func (c *Logical) ReadWithData(path string, data map[string][]string) (*Secret, error) {
r := c.c.NewRequest("GET", "/v1/"+path)
var values url.Values
for k, v := range data {
if values == nil {
values = make(url.Values)
}
for _, val := range v {
values.Add(k, val)
}
}
if values != nil {
r.Params = values
}
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)

View file

@ -8,6 +8,8 @@ import (
"net/http"
"net/url"
"github.com/hashicorp/vault/helper/consts"
retryablehttp "github.com/hashicorp/go-retryablehttp"
)
@ -124,7 +126,7 @@ func (r *Request) toRetryableHTTP() (*retryablehttp.Request, error) {
}
if len(r.ClientToken) != 0 {
req.Header.Set("X-Vault-Token", r.ClientToken)
req.Header.Set(consts.AuthHeaderName, r.ClientToken)
}
if len(r.WrapTTL) != 0 {

View file

@ -2,6 +2,7 @@ package api
import (
"context"
"errors"
"fmt"
"github.com/mitchellh/mapstructure"
@ -25,17 +26,24 @@ func (c *Sys) AuditHash(path string, input string) (string, error) {
}
defer resp.Body.Close()
type d struct {
Hash string `json:"hash"`
}
var result d
err = resp.DecodeJSON(&result)
secret, err := ParseSecret(resp.Body)
if err != nil {
return "", err
}
if secret == nil || secret.Data == nil {
return "", errors.New("data from server response is empty")
}
return result.Hash, err
hash, ok := secret.Data["hash"]
if !ok {
return "", errors.New("hash not found in response data")
}
hashStr, ok := hash.(string)
if !ok {
return "", errors.New("could not parse hash in response data")
}
return hashStr, nil
}
func (c *Sys) ListAudit() (map[string]*Audit, error) {
@ -50,29 +58,18 @@ func (c *Sys) ListAudit() (map[string]*Audit, error) {
}
defer resp.Body.Close()
var result map[string]interface{}
err = resp.DecodeJSON(&result)
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")
}
mounts := map[string]*Audit{}
for k, v := range result {
switch v.(type) {
case map[string]interface{}:
default:
continue
}
var res Audit
err = mapstructure.Decode(v, &res)
if err != nil {
return nil, err
}
// Not a mount, some other api.Secret data
if res.Type == "" {
continue
}
mounts[k] = &res
err = mapstructure.Decode(secret.Data, &mounts)
if err != nil {
return nil, err
}
return mounts, nil
@ -124,16 +121,16 @@ func (c *Sys) DisableAudit(path string) error {
// documentation. Please refer to that documentation for more details.
type EnableAuditOptions struct {
Type string `json:"type"`
Description string `json:"description"`
Options map[string]string `json:"options"`
Local bool `json:"local"`
Type string `json:"type" mapstructure:"type"`
Description string `json:"description" mapstructure:"description"`
Options map[string]string `json:"options" mapstructure:"options"`
Local bool `json:"local" mapstructure:"local"`
}
type Audit struct {
Path string
Type string
Description string
Options map[string]string
Local bool
Type string `json:"type" mapstructure:"type"`
Description string `json:"description" mapstructure:"description"`
Options map[string]string `json:"options" mapstructure:"options"`
Local bool `json:"local" mapstructure:"local"`
Path string `json:"path" mapstructure:"path"`
}

View file

@ -2,6 +2,7 @@ package api
import (
"context"
"errors"
"fmt"
"github.com/mitchellh/mapstructure"
@ -18,29 +19,18 @@ func (c *Sys) ListAuth() (map[string]*AuthMount, error) {
}
defer resp.Body.Close()
var result map[string]interface{}
err = resp.DecodeJSON(&result)
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")
}
mounts := map[string]*AuthMount{}
for k, v := range result {
switch v.(type) {
case map[string]interface{}:
default:
continue
}
var res AuthMount
err = mapstructure.Decode(v, &res)
if err != nil {
return nil, err
}
// Not a mount, some other api.Secret data
if res.Type == "" {
continue
}
mounts[k] = &res
err = mapstructure.Decode(secret.Data, &mounts)
if err != nil {
return nil, err
}
return mounts, nil
@ -83,46 +73,8 @@ func (c *Sys) DisableAuth(path string) error {
return err
}
// Structures for the requests/resposne are all down here. They aren't
// individually documented because the map almost directly to the raw HTTP API
// documentation. Please refer to that documentation for more details.
type EnableAuthOptions struct {
Type string `json:"type"`
Description string `json:"description"`
Config AuthConfigInput `json:"config"`
Local bool `json:"local"`
PluginName string `json:"plugin_name,omitempty"`
SealWrap bool `json:"seal_wrap" mapstructure:"seal_wrap"`
Options map[string]string `json:"options" mapstructure:"options"`
}
type AuthConfigInput struct {
DefaultLeaseTTL string `json:"default_lease_ttl" mapstructure:"default_lease_ttl"`
MaxLeaseTTL string `json:"max_lease_ttl" mapstructure:"max_lease_ttl"`
PluginName string `json:"plugin_name,omitempty" mapstructure:"plugin_name"`
AuditNonHMACRequestKeys []string `json:"audit_non_hmac_request_keys,omitempty" mapstructure:"audit_non_hmac_request_keys"`
AuditNonHMACResponseKeys []string `json:"audit_non_hmac_response_keys,omitempty" mapstructure:"audit_non_hmac_response_keys"`
ListingVisibility string `json:"listing_visibility,omitempty" mapstructure:"listing_visibility"`
PassthroughRequestHeaders []string `json:"passthrough_request_headers,omitempty" mapstructure:"passthrough_request_headers"`
}
type AuthMount struct {
Type string `json:"type" mapstructure:"type"`
Description string `json:"description" mapstructure:"description"`
Accessor string `json:"accessor" mapstructure:"accessor"`
Config AuthConfigOutput `json:"config" mapstructure:"config"`
Local bool `json:"local" mapstructure:"local"`
SealWrap bool `json:"seal_wrap" mapstructure:"seal_wrap"`
Options map[string]string `json:"options" mapstructure:"options"`
}
type AuthConfigOutput struct {
DefaultLeaseTTL int `json:"default_lease_ttl" mapstructure:"default_lease_ttl"`
MaxLeaseTTL int `json:"max_lease_ttl" mapstructure:"max_lease_ttl"`
PluginName string `json:"plugin_name,omitempty" mapstructure:"plugin_name"`
AuditNonHMACRequestKeys []string `json:"audit_non_hmac_request_keys,omitempty" mapstructure:"audit_non_hmac_request_keys"`
AuditNonHMACResponseKeys []string `json:"audit_non_hmac_response_keys,omitempty" mapstructure:"audit_non_hmac_response_keys"`
ListingVisibility string `json:"listing_visibility,omitempty" mapstructure:"listing_visibility"`
PassthroughRequestHeaders []string `json:"passthrough_request_headers,omitempty" mapstructure:"passthrough_request_headers"`
}
// Rather than duplicate, we can use modern Go's type aliasing
type EnableAuthOptions = MountInput
type AuthConfigInput = MountConfigInput
type AuthMount = MountOutput
type AuthConfigOutput = MountConfigOutput

View file

@ -2,7 +2,10 @@ package api
import (
"context"
"errors"
"fmt"
"github.com/mitchellh/mapstructure"
)
func (c *Sys) CapabilitiesSelf(path string) ([]string, error) {
@ -33,22 +36,29 @@ func (c *Sys) Capabilities(token, path string) ([]string, error) {
}
defer resp.Body.Close()
var result map[string]interface{}
err = resp.DecodeJSON(&result)
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")
}
var res []string
err = mapstructure.Decode(secret.Data[path], &res)
if err != nil {
return nil, err
}
if result["capabilities"] == nil {
return nil, nil
if len(res) == 0 {
_, ok := secret.Data["capabilities"]
if ok {
err = mapstructure.Decode(secret.Data["capabilities"], &res)
if err != nil {
return nil, err
}
}
}
var capabilities []string
capabilitiesRaw, ok := result["capabilities"].([]interface{})
if !ok {
return nil, fmt.Errorf("error interpreting returned capabilities")
}
for _, capability := range capabilitiesRaw {
capabilities = append(capabilities, capability.(string))
}
return capabilities, nil
return res, nil
}

View file

@ -1,6 +1,11 @@
package api
import "context"
import (
"context"
"errors"
"github.com/mitchellh/mapstructure"
)
func (c *Sys) CORSStatus() (*CORSResponse, error) {
r := c.c.NewRequest("GET", "/v1/sys/config/cors")
@ -13,8 +18,20 @@ func (c *Sys) CORSStatus() (*CORSResponse, error) {
}
defer resp.Body.Close()
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")
}
var result CORSResponse
err = resp.DecodeJSON(&result)
err = mapstructure.Decode(secret.Data, &result)
if err != nil {
return nil, err
}
return &result, err
}
@ -32,8 +49,20 @@ func (c *Sys) ConfigureCORS(req *CORSRequest) (*CORSResponse, error) {
}
defer resp.Body.Close()
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")
}
var result CORSResponse
err = resp.DecodeJSON(&result)
err = mapstructure.Decode(secret.Data, &result)
if err != nil {
return nil, err
}
return &result, err
}
@ -48,18 +77,29 @@ func (c *Sys) DisableCORS() (*CORSResponse, error) {
}
defer resp.Body.Close()
var result CORSResponse
err = resp.DecodeJSON(&result)
return &result, err
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")
}
var result CORSResponse
err = mapstructure.Decode(secret.Data, &result)
if err != nil {
return nil, err
}
return &result, err
}
type CORSRequest struct {
AllowedOrigins string `json:"allowed_origins"`
Enabled bool `json:"enabled"`
AllowedOrigins string `json:"allowed_origins" mapstructure:"allowed_origins"`
Enabled bool `json:"enabled" mapstructure:"enabled"`
}
type CORSResponse struct {
AllowedOrigins string `json:"allowed_origins"`
Enabled bool `json:"enabled"`
AllowedOrigins string `json:"allowed_origins" mapstructure:"allowed_origins"`
Enabled bool `json:"enabled" mapstructure:"enabled"`
}

View file

@ -119,4 +119,6 @@ type GenerateRootStatusResponse struct {
EncodedToken string `json:"encoded_token"`
EncodedRootToken string `json:"encoded_root_token"`
PGPFingerprint string `json:"pgp_fingerprint"`
OTP string `json:"otp"`
OTPLength int `json:"otp_length"`
}

View file

@ -11,6 +11,7 @@ func (c *Sys) Health() (*HealthResponse, error) {
r.Params.Add("sealedcode", "299")
r.Params.Add("standbycode", "299")
r.Params.Add("drsecondarycode", "299")
r.Params.Add("performancestandbycode", "299")
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
@ -35,4 +36,5 @@ type HealthResponse struct {
Version string `json:"version"`
ClusterName string `json:"cluster_name,omitempty"`
ClusterID string `json:"cluster_id,omitempty"`
LastWAL uint64 `json:"last_wal,omitempty"`
}

View file

@ -19,8 +19,11 @@ func (c *Sys) Leader() (*LeaderResponse, error) {
}
type LeaderResponse struct {
HAEnabled bool `json:"ha_enabled"`
IsSelf bool `json:"is_self"`
LeaderAddress string `json:"leader_address"`
LeaderClusterAddress string `json:"leader_cluster_address"`
HAEnabled bool `json:"ha_enabled"`
IsSelf bool `json:"is_self"`
LeaderAddress string `json:"leader_address"`
LeaderClusterAddress string `json:"leader_cluster_address"`
PerfStandby bool `json:"performance_standby"`
PerfStandbyLastRemoteWAL uint64 `json:"performance_standby_last_remote_wal"`
LastWAL uint64 `json:"last_wal"`
}

View file

@ -2,6 +2,7 @@ package api
import (
"context"
"errors"
"fmt"
"github.com/mitchellh/mapstructure"
@ -18,29 +19,18 @@ func (c *Sys) ListMounts() (map[string]*MountOutput, error) {
}
defer resp.Body.Close()
var result map[string]interface{}
err = resp.DecodeJSON(&result)
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")
}
mounts := map[string]*MountOutput{}
for k, v := range result {
switch v.(type) {
case map[string]interface{}:
default:
continue
}
var res MountOutput
err = mapstructure.Decode(v, &res)
if err != nil {
return nil, err
}
// Not a mount, some other api.Secret data
if res.Type == "" {
continue
}
mounts[k] = &res
err = mapstructure.Decode(secret.Data, &mounts)
if err != nil {
return nil, err
}
return mounts, nil
@ -121,8 +111,16 @@ func (c *Sys) MountConfig(path string) (*MountConfigOutput, error) {
}
defer resp.Body.Close()
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")
}
var result MountConfigOutput
err = resp.DecodeJSON(&result)
err = mapstructure.Decode(secret.Data, &result)
if err != nil {
return nil, err
}
@ -134,10 +132,13 @@ type MountInput struct {
Type string `json:"type"`
Description string `json:"description"`
Config MountConfigInput `json:"config"`
Options map[string]string `json:"options"`
Local bool `json:"local"`
PluginName string `json:"plugin_name,omitempty"`
SealWrap bool `json:"seal_wrap" mapstructure:"seal_wrap"`
Options map[string]string `json:"options"`
// Deprecated: Newer server responses should be returning this information in the
// Type field (json: "type") instead.
PluginName string `json:"plugin_name,omitempty"`
}
type MountConfigInput struct {
@ -146,11 +147,14 @@ type MountConfigInput struct {
Description *string `json:"description,omitempty" mapstructure:"description"`
MaxLeaseTTL string `json:"max_lease_ttl" mapstructure:"max_lease_ttl"`
ForceNoCache bool `json:"force_no_cache" mapstructure:"force_no_cache"`
PluginName string `json:"plugin_name,omitempty" mapstructure:"plugin_name"`
AuditNonHMACRequestKeys []string `json:"audit_non_hmac_request_keys,omitempty" mapstructure:"audit_non_hmac_request_keys"`
AuditNonHMACResponseKeys []string `json:"audit_non_hmac_response_keys,omitempty" mapstructure:"audit_non_hmac_response_keys"`
ListingVisibility string `json:"listing_visibility,omitempty" mapstructure:"listing_visibility"`
PassthroughRequestHeaders []string `json:"passthrough_request_headers,omitempty" mapstructure:"passthrough_request_headers"`
TokenType string `json:"token_type,omitempty" mapstructure:"token_type"`
// Deprecated: This field will always be blank for newer server responses.
PluginName string `json:"plugin_name,omitempty" mapstructure:"plugin_name"`
}
type MountOutput struct {
@ -167,9 +171,12 @@ type MountConfigOutput struct {
DefaultLeaseTTL int `json:"default_lease_ttl" mapstructure:"default_lease_ttl"`
MaxLeaseTTL int `json:"max_lease_ttl" mapstructure:"max_lease_ttl"`
ForceNoCache bool `json:"force_no_cache" mapstructure:"force_no_cache"`
PluginName string `json:"plugin_name,omitempty" mapstructure:"plugin_name"`
AuditNonHMACRequestKeys []string `json:"audit_non_hmac_request_keys,omitempty" mapstructure:"audit_non_hmac_request_keys"`
AuditNonHMACResponseKeys []string `json:"audit_non_hmac_response_keys,omitempty" mapstructure:"audit_non_hmac_response_keys"`
ListingVisibility string `json:"listing_visibility,omitempty" mapstructure:"listing_visibility"`
PassthroughRequestHeaders []string `json:"passthrough_request_headers,omitempty" mapstructure:"passthrough_request_headers"`
TokenType string `json:"token_type,omitempty" mapstructure:"token_type"`
// Deprecated: This field will always be blank for newer server responses.
PluginName string `json:"plugin_name,omitempty" mapstructure:"plugin_name"`
}

View file

@ -2,24 +2,46 @@ package api
import (
"context"
"errors"
"fmt"
"net/http"
"github.com/hashicorp/vault/helper/consts"
"github.com/mitchellh/mapstructure"
)
// ListPluginsInput is used as input to the ListPlugins function.
type ListPluginsInput struct{}
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.
Names []string
//
// 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 := "/v1/sys/plugins/catalog"
req := c.c.NewRequest("LIST", path)
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)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
@ -29,21 +51,76 @@ func (c *Sys) ListPlugins(i *ListPluginsInput) (*ListPluginsResponse, error) {
}
defer resp.Body.Close()
var result struct {
Data struct {
Keys []string `json:"keys"`
} `json:"data"`
}
if err := resp.DecodeJSON(&result); err != 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")
}
return &ListPluginsResponse{Names: result.Data.Keys}, nil
if resp.StatusCode == 405 && req.Method == "GET" {
// We received an Unsupported Operation response from Vault, indicating
// Vault of an older version that doesn't support the READ method yet.
req.Method = "LIST"
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
}
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.
@ -55,8 +132,9 @@ type GetPluginResponse struct {
SHA256 string `json:"sha256"`
}
// GetPlugin retrieves information about the plugin.
func (c *Sys) GetPlugin(i *GetPluginInput) (*GetPluginResponse, error) {
path := fmt.Sprintf("/v1/sys/plugins/catalog/%s", i.Name)
path := catalogPathByType(i.Type, i.Name)
req := c.c.NewRequest(http.MethodGet, path)
ctx, cancelFunc := context.WithCancel(context.Background())
@ -68,13 +146,13 @@ func (c *Sys) GetPlugin(i *GetPluginInput) (*GetPluginResponse, error) {
defer resp.Body.Close()
var result struct {
Data GetPluginResponse
Data *GetPluginResponse
}
err = resp.DecodeJSON(&result)
if err != nil {
return nil, err
}
return &result.Data, err
return result.Data, err
}
// RegisterPluginInput is used as input to the RegisterPlugin function.
@ -82,6 +160,9 @@ 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"`
@ -94,8 +175,9 @@ type RegisterPluginInput struct {
// RegisterPlugin registers the plugin with the given information.
func (c *Sys) RegisterPlugin(i *RegisterPluginInput) error {
path := fmt.Sprintf("/v1/sys/plugins/catalog/%s", i.Name)
path := catalogPathByType(i.Type, i.Name)
req := c.c.NewRequest(http.MethodPut, path)
if err := req.SetJSONBody(i); err != nil {
return err
}
@ -113,12 +195,15 @@ func (c *Sys) RegisterPlugin(i *RegisterPluginInput) error {
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 := fmt.Sprintf("/v1/sys/plugins/catalog/%s", i.Name)
path := catalogPathByType(i.Type, i.Name)
req := c.c.NewRequest(http.MethodDelete, path)
ctx, cancelFunc := context.WithCancel(context.Background())
@ -129,3 +214,15 @@ func (c *Sys) DeregisterPlugin(i *DeregisterPluginInput) error {
}
return err
}
// 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
}

View file

@ -2,11 +2,14 @@ package api
import (
"context"
"errors"
"fmt"
"github.com/mitchellh/mapstructure"
)
func (c *Sys) ListPolicies() ([]string, error) {
r := c.c.NewRequest("GET", "/v1/sys/policy")
r := c.c.NewRequest("LIST", "/v1/sys/policies/acl")
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
@ -16,29 +19,25 @@ func (c *Sys) ListPolicies() ([]string, error) {
}
defer resp.Body.Close()
var result map[string]interface{}
err = resp.DecodeJSON(&result)
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")
}
var result []string
err = mapstructure.Decode(secret.Data["keys"], &result)
if err != nil {
return nil, err
}
var ok bool
if _, ok = result["policies"]; !ok {
return nil, fmt.Errorf("policies not found in response")
}
listRaw := result["policies"].([]interface{})
var policies []string
for _, val := range listRaw {
policies = append(policies, val.(string))
}
return policies, err
return result, err
}
func (c *Sys) GetPolicy(name string) (string, error) {
r := c.c.NewRequest("GET", fmt.Sprintf("/v1/sys/policy/%s", name))
r := c.c.NewRequest("GET", fmt.Sprintf("/v1/sys/policies/acl/%s", name))
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
@ -53,16 +52,15 @@ func (c *Sys) GetPolicy(name string) (string, error) {
return "", err
}
var result map[string]interface{}
err = resp.DecodeJSON(&result)
secret, err := ParseSecret(resp.Body)
if err != nil {
return "", err
}
if rulesRaw, ok := result["rules"]; ok {
return rulesRaw.(string), nil
if secret == nil || secret.Data == nil {
return "", errors.New("data from server response is empty")
}
if policyRaw, ok := result["policy"]; ok {
if policyRaw, ok := secret.Data["policy"]; ok {
return policyRaw.(string), nil
}
@ -71,10 +69,10 @@ func (c *Sys) GetPolicy(name string) (string, error) {
func (c *Sys) PutPolicy(name, rules string) error {
body := map[string]string{
"rules": rules,
"policy": rules,
}
r := c.c.NewRequest("PUT", fmt.Sprintf("/v1/sys/policy/%s", name))
r := c.c.NewRequest("PUT", fmt.Sprintf("/v1/sys/policies/acl/%s", name))
if err := r.SetJSONBody(body); err != nil {
return err
}
@ -91,7 +89,7 @@ func (c *Sys) PutPolicy(name, rules string) error {
}
func (c *Sys) DeletePolicy(name string) error {
r := c.c.NewRequest("DELETE", fmt.Sprintf("/v1/sys/policy/%s", name))
r := c.c.NewRequest("DELETE", fmt.Sprintf("/v1/sys/policies/acl/%s", name))
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()

View file

@ -1,6 +1,11 @@
package api
import "context"
import (
"context"
"errors"
"github.com/mitchellh/mapstructure"
)
func (c *Sys) RekeyStatus() (*RekeyStatusResponse, error) {
r := c.c.NewRequest("GET", "/v1/sys/rekey/init")
@ -211,8 +216,20 @@ func (c *Sys) RekeyRetrieveBackup() (*RekeyRetrieveResponse, error) {
}
defer resp.Body.Close()
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")
}
var result RekeyRetrieveResponse
err = resp.DecodeJSON(&result)
err = mapstructure.Decode(secret.Data, &result)
if err != nil {
return nil, err
}
return &result, err
}
@ -227,8 +244,20 @@ func (c *Sys) RekeyRetrieveRecoveryBackup() (*RekeyRetrieveResponse, error) {
}
defer resp.Body.Close()
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")
}
var result RekeyRetrieveResponse
err = resp.DecodeJSON(&result)
err = mapstructure.Decode(secret.Data, &result)
if err != nil {
return nil, err
}
return &result, err
}
@ -340,9 +369,9 @@ type RekeyUpdateResponse struct {
}
type RekeyRetrieveResponse struct {
Nonce string `json:"nonce"`
Keys map[string][]string `json:"keys"`
KeysB64 map[string][]string `json:"keys_base64"`
Nonce string `json:"nonce" mapstructure:"nonce"`
Keys map[string][]string `json:"keys" mapstructure:"keys"`
KeysB64 map[string][]string `json:"keys_base64" mapstructure:"keys_base64"`
}
type RekeyVerificationStatusResponse struct {

View file

@ -2,6 +2,8 @@ package api
import (
"context"
"encoding/json"
"errors"
"time"
)
@ -28,9 +30,45 @@ func (c *Sys) KeyStatus() (*KeyStatus, error) {
}
defer resp.Body.Close()
result := new(KeyStatus)
err = resp.DecodeJSON(result)
return result, err
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")
}
var result KeyStatus
termRaw, ok := secret.Data["term"]
if !ok {
return nil, errors.New("term not found in response")
}
term, ok := termRaw.(json.Number)
if !ok {
return nil, errors.New("could not convert term to a number")
}
term64, err := term.Int64()
if err != nil {
return nil, err
}
result.Term = int(term64)
installTimeRaw, ok := secret.Data["install_time"]
if !ok {
return nil, errors.New("install_time not found in response")
}
installTimeStr, ok := installTimeRaw.(string)
if !ok {
return nil, errors.New("could not convert install_time to a string")
}
installTime, err := time.Parse(time.RFC3339Nano, installTimeStr)
if err != nil {
return nil, err
}
result.InstallTime = installTime
return &result, err
}
type KeyStatus struct {

View file

@ -41,6 +41,15 @@ func (c *Sys) Unseal(shard string) (*SealStatusResponse, error) {
return sealStatusRequest(c, r)
}
func (c *Sys) UnsealWithOptions(opts *UnsealOpts) (*SealStatusResponse, error) {
r := c.c.NewRequest("PUT", "/v1/sys/unseal")
if err := r.SetJSONBody(opts); err != nil {
return nil, err
}
return sealStatusRequest(c, r)
}
func sealStatusRequest(c *Sys, r *Request) (*SealStatusResponse, error) {
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
@ -57,13 +66,21 @@ func sealStatusRequest(c *Sys, r *Request) (*SealStatusResponse, error) {
type SealStatusResponse struct {
Type string `json:"type"`
Initialized bool `json:"initialized"`
Sealed bool `json:"sealed"`
T int `json:"t"`
N int `json:"n"`
Progress int `json:"progress"`
Nonce string `json:"nonce"`
Version string `json:"version"`
Migration bool `json:"migration"`
ClusterName string `json:"cluster_name,omitempty"`
ClusterID string `json:"cluster_id,omitempty"`
RecoverySeal bool `json:"recovery_seal"`
}
type UnsealOpts struct {
Key string `json:"key"`
Reset bool `json:"reset"`
Migrate bool `json:"migrate"`
}

View file

@ -8,8 +8,8 @@ func (c *Sys) StepDown() error {
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
resp, err := c.c.RawRequestWithContext(ctx, r)
if err == nil {
defer resp.Body.Close()
if resp != nil && resp.Body != nil {
resp.Body.Close()
}
return err
}

View file

@ -0,0 +1,14 @@
package consts
const (
// ExpirationRestoreWorkerCount specifies the number of workers to use while
// restoring leases into the expiration manager
ExpirationRestoreWorkerCount = 64
// NamespaceHeaderName is the header set to specify which namespace the
// request is indented for.
NamespaceHeaderName = "X-Vault-Namespace"
// AuthHeaderName is the name of the header containing the token.
AuthHeaderName = "X-Vault-Token"
)

View file

@ -0,0 +1,16 @@
package consts
import "errors"
var (
// ErrSealed is returned if an operation is performed on a sealed barrier.
// No operation is expected to succeed before unsealing
ErrSealed = errors.New("Vault is sealed")
// ErrStandby is returned if an operation is performed on a standby Vault.
// No operation is expected to succeed until active.
ErrStandby = errors.New("Vault is in standby mode")
// Used when .. is used in a path
ErrPathContainsParentReferences = errors.New("path cannot contain parent references")
)

View file

@ -0,0 +1,59 @@
package consts
import "fmt"
var PluginTypes = []PluginType{
PluginTypeUnknown,
PluginTypeCredential,
PluginTypeDatabase,
PluginTypeSecrets,
}
type PluginType uint32
// This is a list of PluginTypes used by Vault.
// If we need to add any in the future, it would
// be best to add them to the _end_ of the list below
// because they resolve to incrementing numbers,
// which may be saved in state somewhere. Thus if
// the name for one of those numbers changed because
// a value were added to the middle, that could cause
// the wrong plugin types to be read from storage
// for a given underlying number. Example of the problem
// here: https://play.golang.org/p/YAaPw5ww3er
const (
PluginTypeUnknown PluginType = iota
PluginTypeCredential
PluginTypeDatabase
PluginTypeSecrets
)
func (p PluginType) String() string {
switch p {
case PluginTypeUnknown:
return "unknown"
case PluginTypeCredential:
return "auth"
case PluginTypeDatabase:
return "database"
case PluginTypeSecrets:
return "secret"
default:
return "unsupported"
}
}
func ParsePluginType(pluginType string) (PluginType, error) {
switch pluginType {
case "unknown":
return PluginTypeUnknown, nil
case "auth":
return PluginTypeCredential, nil
case "database":
return PluginTypeDatabase, nil
case "secret":
return PluginTypeSecrets, nil
default:
return PluginTypeUnknown, fmt.Errorf("%q is not a supported plugin type", pluginType)
}
}

View file

@ -0,0 +1,87 @@
package consts
import "time"
type ReplicationState uint32
var ReplicationStaleReadTimeout = 2 * time.Second
const (
_ ReplicationState = iota
OldReplicationPrimary
OldReplicationSecondary
OldReplicationBootstrapping
// Don't add anything here. Adding anything to this Old block would cause
// the rest of the values to change below. This was done originally to
// ensure no overlap between old and new values.
ReplicationUnknown ReplicationState = 0
ReplicationPerformancePrimary ReplicationState = 1 << iota
ReplicationPerformanceSecondary
OldSplitReplicationBootstrapping
ReplicationDRPrimary
ReplicationDRSecondary
ReplicationPerformanceBootstrapping
ReplicationDRBootstrapping
ReplicationPerformanceDisabled
ReplicationDRDisabled
ReplicationPerformanceStandby
)
func (r ReplicationState) string() string {
switch r {
case ReplicationPerformanceSecondary:
return "secondary"
case ReplicationPerformancePrimary:
return "primary"
case ReplicationPerformanceBootstrapping:
return "bootstrapping"
case ReplicationPerformanceDisabled:
return "disabled"
case ReplicationDRPrimary:
return "primary"
case ReplicationDRSecondary:
return "secondary"
case ReplicationDRBootstrapping:
return "bootstrapping"
case ReplicationDRDisabled:
return "disabled"
}
return "unknown"
}
func (r ReplicationState) GetDRString() string {
switch {
case r.HasState(ReplicationDRBootstrapping):
return ReplicationDRBootstrapping.string()
case r.HasState(ReplicationDRPrimary):
return ReplicationDRPrimary.string()
case r.HasState(ReplicationDRSecondary):
return ReplicationDRSecondary.string()
case r.HasState(ReplicationDRDisabled):
return ReplicationDRDisabled.string()
default:
return "unknown"
}
}
func (r ReplicationState) GetPerformanceString() string {
switch {
case r.HasState(ReplicationPerformanceBootstrapping):
return ReplicationPerformanceBootstrapping.string()
case r.HasState(ReplicationPerformancePrimary):
return ReplicationPerformancePrimary.string()
case r.HasState(ReplicationPerformanceSecondary):
return ReplicationPerformanceSecondary.string()
case r.HasState(ReplicationPerformanceDisabled):
return ReplicationPerformanceDisabled.string()
default:
return "unknown"
}
}
func (r ReplicationState) HasState(flag ReplicationState) bool { return r&flag != 0 }
func (r *ReplicationState) AddState(flag ReplicationState) { *r |= flag }
func (r *ReplicationState) ClearState(flag ReplicationState) { *r &= ^flag }
func (r *ReplicationState) ToggleState(flag ReplicationState) { *r ^= flag }

12
vendor/vendor.json vendored
View file

@ -438,10 +438,10 @@
"revisionTime": "2016-01-19T13:13:26-08:00"
},
{
"checksumSHA1": "nBmnbC438E5CNF1kRCnivCnqsEM=",
"checksumSHA1": "fmBqxy85Q0iaiJ144S8+nYUn0rs=",
"path": "github.com/hashicorp/vault/api",
"revision": "f348177b5d55dbd38d039b814bf34cbc9cfc093b",
"revisionTime": "2018-07-27T12:33:20Z"
"revision": "d4367e581fe117356c4d4ac9a8c8c4e514457449",
"revisionTime": "2018-11-21T18:10:53Z"
},
{
"checksumSHA1": "bSdPFOHaTwEvM4PIvn0PZfn75jM=",
@ -449,6 +449,12 @@
"revision": "f348177b5d55dbd38d039b814bf34cbc9cfc093b",
"revisionTime": "2018-07-27T12:33:20Z"
},
{
"checksumSHA1": "MQKcgExxbVHltqyTjVbDuHlkfLw=",
"path": "github.com/hashicorp/vault/helper/consts",
"revision": "d4367e581fe117356c4d4ac9a8c8c4e514457449",
"revisionTime": "2018-11-21T18:10:53Z"
},
{
"checksumSHA1": "RlqPBLOexQ0jj6jomhiompWKaUg=",
"path": "github.com/hashicorp/vault/helper/hclutil",