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 }