github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/creds/vault/manager.go (about)

     1  package vault
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"net/url"
     8  	"path"
     9  	"time"
    10  
    11  	"code.cloudfoundry.org/lager"
    12  
    13  	"github.com/pf-qiu/concourse/v6/atc/creds"
    14  	"github.com/mitchellh/mapstructure"
    15  )
    16  
    17  
    18  type VaultManager struct {
    19  	URL string `mapstructure:"url" long:"url" description:"Vault server address used to access secrets."`
    20  
    21  	PathPrefix      string `mapstructure:"path_prefix" long:"path-prefix" default:"/concourse" description:"Path under which to namespace credential lookup."`
    22  	LookupTemplates []string `mapstructure:"lookup_templates" long:"lookup-templates" default:"/{{.Team}}/{{.Pipeline}}/{{.Secret}}" default:"/{{.Team}}/{{.Secret}}" description:"Path templates for credential lookup"`
    23  	SharedPath      string `mapstructure:"shared_path" long:"shared-path" description:"Path under which to lookup shared credentials."`
    24  	Namespace       string `mapstructure:"namespace" long:"namespace"   description:"Vault namespace to use for authentication and secret lookup."`
    25  
    26  	TLS TLSConfig  `mapstructure:",squash"`
    27  	Auth AuthConfig `mapstructure:",squash"`
    28  
    29  	Client        *APIClient
    30  	ReAuther      *ReAuther
    31  	SecretFactory *vaultFactory
    32  }
    33  
    34  type TLSConfig struct {
    35  	CACert     string `mapstructure:"ca_cert"`
    36  	CACertFile string `long:"ca-cert"              description:"Path to a PEM-encoded CA cert file to use to verify the vault server SSL cert."`
    37  	CAPath     string `long:"ca-path"              description:"Path to a directory of PEM-encoded CA cert files to verify the vault server SSL cert."`
    38  
    39  	ClientCert     string `mapstructure:"client_cert"`
    40  	ClientCertFile string `long:"client-cert"          description:"Path to the client certificate for Vault authorization."`
    41  
    42  	ClientKey     string `mapstructure:"client_key"`
    43  	ClientKeyFile string `long:"client-key"           description:"Path to the client private key for Vault authorization."`
    44  
    45  	ServerName string `mapstructure:"server_name" long:"server-name"          description:"If set, is used to set the SNI host when connecting via TLS."`
    46  	Insecure   bool   `mapstructure:"insecure_skip_verify" long:"insecure-skip-verify" description:"Enable insecure SSL verification."`
    47  }
    48  
    49  type AuthConfig struct {
    50  	ClientToken string `mapstructure:"client_token" long:"client-token" description:"Client token for accessing secrets within the Vault server."`
    51  
    52  	Backend       string        `mapstructure:"auth_backend" long:"auth-backend"               description:"Auth backend to use for logging in to Vault."`
    53  	BackendMaxTTL time.Duration `mapstructure:"auth_backend_max_ttl" long:"auth-backend-max-ttl"       description:"Time after which to force a re-login. If not set, the token will just be continuously renewed."`
    54  	RetryMax      time.Duration `mapstructure:"auth_retry_max" long:"retry-max"     default:"5m" description:"The maximum time between retries when logging in or re-authing a secret."`
    55  	RetryInitial  time.Duration `mapstructure:"auth_retry_initial" long:"retry-initial" default:"1s" description:"The initial time between retries when logging in or re-authing a secret."`
    56  
    57  	Params map[string]string `mapstructure:"auth_params" long:"auth-param"  description:"Paramter to pass when logging in via the backend. Can be specified multiple times." value-name:"NAME:VALUE"`
    58  }
    59  
    60  func (manager *VaultManager) Init(log lager.Logger) error {
    61  	var err error
    62  
    63  	manager.Client, err = NewAPIClient(log, manager.URL, manager.TLS, manager.Auth, manager.Namespace)
    64  	if err != nil {
    65  		return err
    66  	}
    67  
    68  	return nil
    69  }
    70  
    71  func (manager *VaultManager) MarshalJSON() ([]byte, error) {
    72  	health, err := manager.Health()
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	return json.Marshal(&map[string]interface{}{
    78  		"url":                manager.URL,
    79  		"path_prefix":        manager.PathPrefix,
    80  		"lookup_templates":   manager.LookupTemplates,
    81  		"shared_path":        manager.SharedPath,
    82  		"namespace":          manager.Namespace,
    83  		"ca_cert":            manager.TLS.CACert,
    84  		"server_name":        manager.TLS.ServerName,
    85  		"auth_backend":       manager.Auth.Backend,
    86  		"auth_max_ttl":       manager.Auth.BackendMaxTTL,
    87  		"auth_retry_max":     manager.Auth.RetryMax,
    88  		"auth_retry_initial": manager.Auth.RetryInitial,
    89  		"health":             health,
    90  	})
    91  }
    92  
    93  func (manager *VaultManager) Config(config map[string]interface{}) error {
    94  	// apply defaults
    95  	manager.PathPrefix = "/concourse"
    96  	manager.Auth.RetryMax = 5 * time.Minute
    97  	manager.Auth.RetryInitial = time.Second
    98  
    99  	decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   100  		DecodeHook:  mapstructure.StringToTimeDurationHookFunc(),
   101  		ErrorUnused: true,
   102  		Result:      &manager,
   103  	})
   104  	if err != nil {
   105  		return err
   106  	}
   107  
   108  	err = decoder.Decode(config)
   109  	if err != nil {
   110  		return err
   111  	}
   112  
   113  	// Fill in default templates if not otherwise set (done here so
   114  	// that these are effective all together or not at all, rather
   115  	// than combining the defaults with a user's custom setting)
   116  	if _, setsTemplates := config["lookup_templates"]; !setsTemplates {
   117  		manager.LookupTemplates = []string{
   118  			"/{{.Team}}/{{.Pipeline}}/{{.Secret}}",
   119  			"/{{.Team}}/{{.Secret}}",
   120  		}
   121  	}
   122  
   123  	return nil
   124  }
   125  
   126  func (manager VaultManager) IsConfigured() bool {
   127  	return manager.URL != ""
   128  }
   129  
   130  func (manager VaultManager) Validate() error {
   131  	_, err := url.Parse(manager.URL)
   132  	if err != nil {
   133  		return fmt.Errorf("invalid URL: %s", err)
   134  	}
   135  
   136  	if manager.PathPrefix == "" {
   137  		return fmt.Errorf("path prefix must be a non-empty string")
   138  	}
   139  
   140  	for i, tmpl := range manager.LookupTemplates {
   141  		name := fmt.Sprintf("lookup-template-%d", i)
   142  		if _, err := creds.BuildSecretTemplate(name, manager.PathPrefix + tmpl); err != nil {
   143  			return err
   144  		}
   145  	}
   146  
   147  	if manager.Auth.ClientToken != "" {
   148  		return nil
   149  	}
   150  
   151  	if manager.Auth.Backend != "" {
   152  		return nil
   153  	}
   154  
   155  	return errors.New("must configure client token or auth backend")
   156  }
   157  
   158  func (manager VaultManager) Health() (*creds.HealthResponse, error) {
   159  	health := &creds.HealthResponse{
   160  		Method: "/v1/sys/health",
   161  	}
   162  
   163  	response, err := manager.Client.health()
   164  	if err != nil {
   165  		health.Error = err.Error()
   166  		return health, nil
   167  	}
   168  
   169  	health.Response = response
   170  	return health, nil
   171  }
   172  
   173  func (manager *VaultManager) NewSecretsFactory(logger lager.Logger) (creds.SecretsFactory, error) {
   174  	if manager.SecretFactory == nil {
   175  
   176  		templates := []*creds.SecretTemplate{}
   177  		for i, tmpl := range manager.LookupTemplates {
   178  			name := fmt.Sprintf("lookup-template-%d", i)
   179  			scopedTemplate := path.Join(manager.PathPrefix, tmpl)
   180  			if template, err := creds.BuildSecretTemplate(name, scopedTemplate); err != nil {
   181  				return nil, err
   182  			} else {
   183  				templates = append(templates, template)
   184  			}
   185  		}
   186  
   187  		manager.ReAuther = NewReAuther(
   188  			logger,
   189  			manager.Client,
   190  			manager.Auth.BackendMaxTTL,
   191  			manager.Auth.RetryInitial,
   192  			manager.Auth.RetryMax,
   193  		)
   194  
   195  		manager.SecretFactory = NewVaultFactory(
   196  			manager.Client,
   197  			manager.ReAuther.LoggedIn(),
   198  			manager.PathPrefix,
   199  			templates,
   200  			manager.SharedPath,
   201  		)
   202  	}
   203  
   204  	return manager.SecretFactory, nil
   205  }
   206  
   207  func (manager VaultManager) Close(logger lager.Logger) {
   208  	manager.ReAuther.Close()
   209  }