mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-11-04 04:28:08 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			386 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			386 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright (c) HashiCorp, Inc.
 | 
						|
// SPDX-License-Identifier: MPL-2.0
 | 
						|
 | 
						|
package api
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"encoding/json"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"reflect"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/hashicorp/errwrap"
 | 
						|
	"github.com/hashicorp/go-secure-stdlib/parseutil"
 | 
						|
)
 | 
						|
 | 
						|
// Secret is the structure returned for every secret within Vault.
 | 
						|
type Secret struct {
 | 
						|
	// The request ID that generated this response
 | 
						|
	RequestID string `json:"request_id"`
 | 
						|
 | 
						|
	LeaseID       string `json:"lease_id"`
 | 
						|
	LeaseDuration int    `json:"lease_duration"`
 | 
						|
	Renewable     bool   `json:"renewable"`
 | 
						|
 | 
						|
	// Data is the actual contents of the secret. The format of the data
 | 
						|
	// is arbitrary and up to the secret backend.
 | 
						|
	Data map[string]interface{} `json:"data"`
 | 
						|
 | 
						|
	// Warnings contains any warnings related to the operation. These
 | 
						|
	// are not issues that caused the command to fail, but that the
 | 
						|
	// client should be aware of.
 | 
						|
	Warnings []string `json:"warnings"`
 | 
						|
 | 
						|
	// Auth, if non-nil, means that there was authentication information
 | 
						|
	// attached to this response.
 | 
						|
	Auth *SecretAuth `json:"auth,omitempty"`
 | 
						|
 | 
						|
	// WrapInfo, if non-nil, means that the initial response was wrapped in the
 | 
						|
	// cubbyhole of the given token (which has a TTL of the given number of
 | 
						|
	// seconds)
 | 
						|
	WrapInfo *SecretWrapInfo `json:"wrap_info,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
// TokenID returns the standardized token ID (token) for the given secret.
 | 
						|
func (s *Secret) TokenID() (string, error) {
 | 
						|
	if s == nil {
 | 
						|
		return "", nil
 | 
						|
	}
 | 
						|
 | 
						|
	if s.Auth != nil && len(s.Auth.ClientToken) > 0 {
 | 
						|
		return s.Auth.ClientToken, nil
 | 
						|
	}
 | 
						|
 | 
						|
	if s.Data == nil || s.Data["id"] == nil {
 | 
						|
		return "", nil
 | 
						|
	}
 | 
						|
 | 
						|
	id, ok := s.Data["id"].(string)
 | 
						|
	if !ok {
 | 
						|
		return "", fmt.Errorf("token found but in the wrong format")
 | 
						|
	}
 | 
						|
 | 
						|
	return id, nil
 | 
						|
}
 | 
						|
 | 
						|
// TokenAccessor returns the standardized token accessor for the given secret.
 | 
						|
// If the secret is nil or does not contain an accessor, this returns the empty
 | 
						|
// string.
 | 
						|
func (s *Secret) TokenAccessor() (string, error) {
 | 
						|
	if s == nil {
 | 
						|
		return "", nil
 | 
						|
	}
 | 
						|
 | 
						|
	if s.Auth != nil && len(s.Auth.Accessor) > 0 {
 | 
						|
		return s.Auth.Accessor, nil
 | 
						|
	}
 | 
						|
 | 
						|
	if s.Data == nil || s.Data["accessor"] == nil {
 | 
						|
		return "", nil
 | 
						|
	}
 | 
						|
 | 
						|
	accessor, ok := s.Data["accessor"].(string)
 | 
						|
	if !ok {
 | 
						|
		return "", fmt.Errorf("token found but in the wrong format")
 | 
						|
	}
 | 
						|
 | 
						|
	return accessor, nil
 | 
						|
}
 | 
						|
 | 
						|
// TokenRemainingUses returns the standardized remaining uses for the given
 | 
						|
// secret. If the secret is nil or does not contain the "num_uses", this
 | 
						|
// returns -1. On error, this will return -1 and a non-nil error.
 | 
						|
func (s *Secret) TokenRemainingUses() (int, error) {
 | 
						|
	if s == nil || s.Data == nil || s.Data["num_uses"] == nil {
 | 
						|
		return -1, nil
 | 
						|
	}
 | 
						|
 | 
						|
	return parseutil.SafeParseInt(s.Data["num_uses"])
 | 
						|
}
 | 
						|
 | 
						|
// TokenPolicies returns the standardized list of policies for the given secret.
 | 
						|
// If the secret is nil or does not contain any policies, this returns nil. It
 | 
						|
// also populates the secret's Auth info with identity/token policy info.
 | 
						|
func (s *Secret) TokenPolicies() ([]string, error) {
 | 
						|
	if s == nil {
 | 
						|
		return nil, nil
 | 
						|
	}
 | 
						|
 | 
						|
	if s.Auth != nil && len(s.Auth.Policies) > 0 {
 | 
						|
		return s.Auth.Policies, nil
 | 
						|
	}
 | 
						|
 | 
						|
	if s.Data == nil || s.Data["policies"] == nil {
 | 
						|
		return nil, nil
 | 
						|
	}
 | 
						|
 | 
						|
	var tokenPolicies []string
 | 
						|
 | 
						|
	// Token policies
 | 
						|
	{
 | 
						|
		_, ok := s.Data["policies"]
 | 
						|
		if !ok {
 | 
						|
			goto TOKEN_DONE
 | 
						|
		}
 | 
						|
 | 
						|
		sList, ok := s.Data["policies"].([]string)
 | 
						|
		if ok {
 | 
						|
			tokenPolicies = sList
 | 
						|
			goto TOKEN_DONE
 | 
						|
		}
 | 
						|
 | 
						|
		list, ok := s.Data["policies"].([]interface{})
 | 
						|
		if !ok {
 | 
						|
			return nil, fmt.Errorf("unable to convert token policies to expected format")
 | 
						|
		}
 | 
						|
		for _, v := range list {
 | 
						|
			p, ok := v.(string)
 | 
						|
			if !ok {
 | 
						|
				return nil, fmt.Errorf("unable to convert policy %v to string", v)
 | 
						|
			}
 | 
						|
			tokenPolicies = append(tokenPolicies, p)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
TOKEN_DONE:
 | 
						|
	var identityPolicies []string
 | 
						|
 | 
						|
	// Identity policies
 | 
						|
	{
 | 
						|
		_, ok := s.Data["identity_policies"]
 | 
						|
		if !ok {
 | 
						|
			goto DONE
 | 
						|
		}
 | 
						|
 | 
						|
		sList, ok := s.Data["identity_policies"].([]string)
 | 
						|
		if ok {
 | 
						|
			identityPolicies = sList
 | 
						|
			goto DONE
 | 
						|
		}
 | 
						|
 | 
						|
		list, ok := s.Data["identity_policies"].([]interface{})
 | 
						|
		if !ok {
 | 
						|
			return nil, fmt.Errorf("unable to convert identity policies to expected format")
 | 
						|
		}
 | 
						|
		for _, v := range list {
 | 
						|
			p, ok := v.(string)
 | 
						|
			if !ok {
 | 
						|
				return nil, fmt.Errorf("unable to convert policy %v to string", v)
 | 
						|
			}
 | 
						|
			identityPolicies = append(identityPolicies, p)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
DONE:
 | 
						|
 | 
						|
	if s.Auth == nil {
 | 
						|
		s.Auth = &SecretAuth{}
 | 
						|
	}
 | 
						|
 | 
						|
	policies := append(tokenPolicies, identityPolicies...)
 | 
						|
 | 
						|
	s.Auth.TokenPolicies = tokenPolicies
 | 
						|
	s.Auth.IdentityPolicies = identityPolicies
 | 
						|
	s.Auth.Policies = policies
 | 
						|
 | 
						|
	return policies, nil
 | 
						|
}
 | 
						|
 | 
						|
// TokenMetadata returns the map of metadata associated with this token, if any
 | 
						|
// exists. If the secret is nil or does not contain the "metadata" key, this
 | 
						|
// returns nil.
 | 
						|
func (s *Secret) TokenMetadata() (map[string]string, error) {
 | 
						|
	if s == nil {
 | 
						|
		return nil, nil
 | 
						|
	}
 | 
						|
 | 
						|
	if s.Auth != nil && len(s.Auth.Metadata) > 0 {
 | 
						|
		return s.Auth.Metadata, nil
 | 
						|
	}
 | 
						|
 | 
						|
	if s.Data == nil || (s.Data["metadata"] == nil && s.Data["meta"] == nil) {
 | 
						|
		return nil, nil
 | 
						|
	}
 | 
						|
 | 
						|
	data, ok := s.Data["metadata"].(map[string]interface{})
 | 
						|
	if !ok {
 | 
						|
		data, ok = s.Data["meta"].(map[string]interface{})
 | 
						|
		if !ok {
 | 
						|
			return nil, fmt.Errorf("unable to convert metadata field to expected format")
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	metadata := make(map[string]string, len(data))
 | 
						|
	for k, v := range data {
 | 
						|
		typed, ok := v.(string)
 | 
						|
		if !ok {
 | 
						|
			return nil, fmt.Errorf("unable to convert metadata value %v to string", v)
 | 
						|
		}
 | 
						|
		metadata[k] = typed
 | 
						|
	}
 | 
						|
 | 
						|
	return metadata, nil
 | 
						|
}
 | 
						|
 | 
						|
// TokenIsRenewable returns the standardized token renewability for the given
 | 
						|
// secret. If the secret is nil or does not contain the "renewable" key, this
 | 
						|
// returns false.
 | 
						|
func (s *Secret) TokenIsRenewable() (bool, error) {
 | 
						|
	if s == nil {
 | 
						|
		return false, nil
 | 
						|
	}
 | 
						|
 | 
						|
	if s.Auth != nil && s.Auth.Renewable {
 | 
						|
		return s.Auth.Renewable, nil
 | 
						|
	}
 | 
						|
 | 
						|
	if s.Data == nil || s.Data["renewable"] == nil {
 | 
						|
		return false, nil
 | 
						|
	}
 | 
						|
 | 
						|
	renewable, err := parseutil.ParseBool(s.Data["renewable"])
 | 
						|
	if err != nil {
 | 
						|
		return false, errwrap.Wrapf("could not convert renewable value to a boolean: {{err}}", err)
 | 
						|
	}
 | 
						|
 | 
						|
	return renewable, nil
 | 
						|
}
 | 
						|
 | 
						|
// TokenTTL returns the standardized remaining token TTL for the given secret.
 | 
						|
// If the secret is nil or does not contain a TTL, this returns 0.
 | 
						|
func (s *Secret) TokenTTL() (time.Duration, error) {
 | 
						|
	if s == nil {
 | 
						|
		return 0, nil
 | 
						|
	}
 | 
						|
 | 
						|
	if s.Auth != nil && s.Auth.LeaseDuration > 0 {
 | 
						|
		return time.Duration(s.Auth.LeaseDuration) * time.Second, nil
 | 
						|
	}
 | 
						|
 | 
						|
	if s.Data == nil || s.Data["ttl"] == nil {
 | 
						|
		return 0, nil
 | 
						|
	}
 | 
						|
 | 
						|
	ttl, err := parseutil.ParseDurationSecond(s.Data["ttl"])
 | 
						|
	if err != nil {
 | 
						|
		return 0, err
 | 
						|
	}
 | 
						|
 | 
						|
	return ttl, nil
 | 
						|
}
 | 
						|
 | 
						|
// SecretWrapInfo contains wrapping information if we have it. If what is
 | 
						|
// contained is an authentication token, the accessor for the token will be
 | 
						|
// available in WrappedAccessor.
 | 
						|
type SecretWrapInfo struct {
 | 
						|
	Token           string    `json:"token"`
 | 
						|
	Accessor        string    `json:"accessor"`
 | 
						|
	TTL             int       `json:"ttl"`
 | 
						|
	CreationTime    time.Time `json:"creation_time"`
 | 
						|
	CreationPath    string    `json:"creation_path"`
 | 
						|
	WrappedAccessor string    `json:"wrapped_accessor"`
 | 
						|
}
 | 
						|
 | 
						|
type MFAMethodID struct {
 | 
						|
	Type         string `json:"type,omitempty"`
 | 
						|
	ID           string `json:"id,omitempty"`
 | 
						|
	UsesPasscode bool   `json:"uses_passcode,omitempty"`
 | 
						|
	Name         string `json:"name,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
type MFAConstraintAny struct {
 | 
						|
	Any []*MFAMethodID `json:"any,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
type MFARequirement struct {
 | 
						|
	MFARequestID   string                       `json:"mfa_request_id,omitempty"`
 | 
						|
	MFAConstraints map[string]*MFAConstraintAny `json:"mfa_constraints,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
// SecretAuth is the structure containing auth information if we have it.
 | 
						|
type SecretAuth struct {
 | 
						|
	ClientToken      string            `json:"client_token"`
 | 
						|
	Accessor         string            `json:"accessor"`
 | 
						|
	Policies         []string          `json:"policies"`
 | 
						|
	TokenPolicies    []string          `json:"token_policies"`
 | 
						|
	IdentityPolicies []string          `json:"identity_policies"`
 | 
						|
	Metadata         map[string]string `json:"metadata"`
 | 
						|
	Orphan           bool              `json:"orphan"`
 | 
						|
	EntityID         string            `json:"entity_id"`
 | 
						|
 | 
						|
	LeaseDuration int  `json:"lease_duration"`
 | 
						|
	Renewable     bool `json:"renewable"`
 | 
						|
 | 
						|
	MFARequirement *MFARequirement `json:"mfa_requirement"`
 | 
						|
}
 | 
						|
 | 
						|
// ParseSecret is used to parse a secret value from JSON from an io.Reader.
 | 
						|
func ParseSecret(r io.Reader) (*Secret, error) {
 | 
						|
	// First read the data into a buffer. Not super efficient but we want to
 | 
						|
	// know if we actually have a body or not.
 | 
						|
	var buf bytes.Buffer
 | 
						|
 | 
						|
	// io.Reader is treated like a stream and cannot be read
 | 
						|
	// multiple times. Duplicating this stream using TeeReader
 | 
						|
	// to use this data in case there is no top-level data from
 | 
						|
	// api response
 | 
						|
	var teebuf bytes.Buffer
 | 
						|
	tee := io.TeeReader(r, &teebuf)
 | 
						|
 | 
						|
	_, err := buf.ReadFrom(tee)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if buf.Len() == 0 {
 | 
						|
		return nil, nil
 | 
						|
	}
 | 
						|
 | 
						|
	// First decode the JSON into a map[string]interface{}
 | 
						|
	var secret Secret
 | 
						|
	dec := json.NewDecoder(&buf)
 | 
						|
	dec.UseNumber()
 | 
						|
	if err := dec.Decode(&secret); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// If the secret is null, add raw data to secret data if present
 | 
						|
	if reflect.DeepEqual(secret, Secret{}) {
 | 
						|
		data := make(map[string]interface{})
 | 
						|
		dec := json.NewDecoder(&teebuf)
 | 
						|
		dec.UseNumber()
 | 
						|
		if err := dec.Decode(&data); err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		errRaw, errPresent := data["errors"]
 | 
						|
 | 
						|
		// if only errors are present in the resp.Body return nil
 | 
						|
		// to return value not found as it does not have any raw data
 | 
						|
		if len(data) == 1 && errPresent {
 | 
						|
			return nil, nil
 | 
						|
		}
 | 
						|
 | 
						|
		// if errors are present along with raw data return the error
 | 
						|
		if errPresent {
 | 
						|
			var errStrArray []string
 | 
						|
			errBytes, err := json.Marshal(errRaw)
 | 
						|
			if err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
			if err := json.Unmarshal(errBytes, &errStrArray); err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
			return nil, fmt.Errorf(strings.Join(errStrArray, " "))
 | 
						|
		}
 | 
						|
 | 
						|
		// if any raw data is present in resp.Body, add it to secret
 | 
						|
		if len(data) > 0 {
 | 
						|
			secret.Data = data
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return &secret, nil
 | 
						|
}
 |