mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-30 18:17:55 +00:00 
			
		
		
		
	command/server: add dev-tls flag (#16421)
* command/server: add dev-tls flag * Add website documentation * changelog * Lower file permissions * Update cert gen per review * Add dev-tls-cert-dir flag and cert clean up * fmt * Update cert generation per review * Remove unused function * Add better error messages * Log errors in cleanup, fix directory not existing bug * Remove hidden flag from -dev-tls-cert-dir * Add usage * Update 16421.txt * Update variable names for files * Remove directory on cleanup
This commit is contained in:
		
							
								
								
									
										3
									
								
								changelog/16421.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								changelog/16421.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| ```release-note:improvement | ||||
| command/server: add `-dev-tls` and `-dev-tls-cert-dir` subcommands to create a Vault dev server with generated certificates and private key. | ||||
| ``` | ||||
| @@ -117,6 +117,8 @@ type ServerCommand struct { | ||||
| 	flagLogFormat          string | ||||
| 	flagRecovery           bool | ||||
| 	flagDev                bool | ||||
| 	flagDevTLS             bool | ||||
| 	flagDevTLSCertDir      string | ||||
| 	flagDevRootTokenID     string | ||||
| 	flagDevListenAddr      string | ||||
| 	flagDevNoStoreToken    bool | ||||
| @@ -245,6 +247,23 @@ func (c *ServerCommand) Flags() *FlagSets { | ||||
| 			"production.", | ||||
| 	}) | ||||
|  | ||||
| 	f.BoolVar(&BoolVar{ | ||||
| 		Name:   "dev-tls", | ||||
| 		Target: &c.flagDevTLS, | ||||
| 		Usage: "Enable TLS development mode. In this mode, Vault runs in-memory and " + | ||||
| 			"starts unsealed, with a generated TLS CA, certificate and key. " + | ||||
| 			"As the name implies, do not run \"dev-tls\" mode in " + | ||||
| 			"production.", | ||||
| 	}) | ||||
|  | ||||
| 	f.StringVar(&StringVar{ | ||||
| 		Name:    "dev-tls-cert-dir", | ||||
| 		Target:  &c.flagDevTLSCertDir, | ||||
| 		Default: "", | ||||
| 		Usage: "Directory where generated TLS files are created if `-dev-tls` is " + | ||||
| 			"specified. If left unset, files are generated in a temporary directory.", | ||||
| 	}) | ||||
|  | ||||
| 	f.StringVar(&StringVar{ | ||||
| 		Name:    "dev-root-token-id", | ||||
| 		Target:  &c.flagDevRootTokenID, | ||||
| @@ -1026,7 +1045,7 @@ func (c *ServerCommand) Run(args []string) int { | ||||
| 	} | ||||
|  | ||||
| 	// Automatically enable dev mode if other dev flags are provided. | ||||
| 	if c.flagDevConsul || c.flagDevHA || c.flagDevTransactional || c.flagDevLeasedKV || c.flagDevThreeNode || c.flagDevFourCluster || c.flagDevAutoSeal || c.flagDevKVV1 { | ||||
| 	if c.flagDevConsul || c.flagDevHA || c.flagDevTransactional || c.flagDevLeasedKV || c.flagDevThreeNode || c.flagDevFourCluster || c.flagDevAutoSeal || c.flagDevKVV1 || c.flagDevTLS { | ||||
| 		c.flagDev = true | ||||
| 	} | ||||
|  | ||||
| @@ -1062,6 +1081,7 @@ func (c *ServerCommand) Run(args []string) int { | ||||
| 	// Load the configuration | ||||
| 	var config *server.Config | ||||
| 	var err error | ||||
| 	var certDir string | ||||
| 	if c.flagDev { | ||||
| 		var devStorageType string | ||||
| 		switch { | ||||
| @@ -1076,11 +1096,59 @@ func (c *ServerCommand) Run(args []string) int { | ||||
| 		default: | ||||
| 			devStorageType = "inmem" | ||||
| 		} | ||||
| 		config, err = server.DevConfig(devStorageType) | ||||
|  | ||||
| 		if c.flagDevTLS { | ||||
| 			if c.flagDevTLSCertDir != "" { | ||||
| 				_, err := os.Stat(c.flagDevTLSCertDir) | ||||
| 				if err != nil { | ||||
| 					c.UI.Error(err.Error()) | ||||
| 					return 1 | ||||
| 				} | ||||
|  | ||||
| 				certDir = c.flagDevTLSCertDir | ||||
| 			} else { | ||||
| 				certDir, err = os.MkdirTemp("", "vault-tls") | ||||
| 				if err != nil { | ||||
| 					c.UI.Error(err.Error()) | ||||
| 					return 1 | ||||
| 				} | ||||
| 			} | ||||
| 			config, err = server.DevTLSConfig(devStorageType, certDir) | ||||
|  | ||||
| 			defer func() { | ||||
| 				err := os.Remove(fmt.Sprintf("%s/%s", certDir, server.VaultDevCAFilename)) | ||||
| 				if err != nil { | ||||
| 					c.UI.Error(err.Error()) | ||||
| 				} | ||||
|  | ||||
| 				err = os.Remove(fmt.Sprintf("%s/%s", certDir, server.VaultDevCertFilename)) | ||||
| 				if err != nil { | ||||
| 					c.UI.Error(err.Error()) | ||||
| 				} | ||||
|  | ||||
| 				err = os.Remove(fmt.Sprintf("%s/%s", certDir, server.VaultDevKeyFilename)) | ||||
| 				if err != nil { | ||||
| 					c.UI.Error(err.Error()) | ||||
| 				} | ||||
|  | ||||
| 				// Only delete temp directories we made. | ||||
| 				if c.flagDevTLSCertDir == "" { | ||||
| 					err = os.Remove(certDir) | ||||
| 					if err != nil { | ||||
| 						c.UI.Error(err.Error()) | ||||
| 					} | ||||
| 				} | ||||
| 			}() | ||||
|  | ||||
| 		} else { | ||||
| 			config, err = server.DevConfig(devStorageType) | ||||
| 		} | ||||
|  | ||||
| 		if err != nil { | ||||
| 			c.UI.Error(err.Error()) | ||||
| 			return 1 | ||||
| 		} | ||||
|  | ||||
| 		if c.flagDevListenAddr != "" { | ||||
| 			config.Listeners[0].Address = c.flagDevListenAddr | ||||
| 		} | ||||
| @@ -1495,7 +1563,7 @@ func (c *ServerCommand) Run(args []string) int { | ||||
| 	} | ||||
|  | ||||
| 	// If we're in Dev mode, then initialize the core | ||||
| 	err = initDevCore(c, &coreConfig, config, core) | ||||
| 	err = initDevCore(c, &coreConfig, config, core, certDir) | ||||
| 	if err != nil { | ||||
| 		c.UI.Error(err.Error()) | ||||
| 		return 1 | ||||
| @@ -2442,7 +2510,11 @@ func determineRedirectAddr(c *ServerCommand, coreConfig *vault.CoreConfig, confi | ||||
| 		} | ||||
| 	} | ||||
| 	if coreConfig.RedirectAddr == "" && c.flagDev { | ||||
| 		coreConfig.RedirectAddr = fmt.Sprintf("http://%s", config.Listeners[0].Address) | ||||
| 		protocol := "http" | ||||
| 		if c.flagDevTLS { | ||||
| 			protocol = "https" | ||||
| 		} | ||||
| 		coreConfig.RedirectAddr = fmt.Sprintf("%s://%s", protocol, config.Listeners[0].Address) | ||||
| 	} | ||||
| 	return retErr | ||||
| } | ||||
| @@ -2604,7 +2676,7 @@ func runListeners(c *ServerCommand, coreConfig *vault.CoreConfig, config *server | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func initDevCore(c *ServerCommand, coreConfig *vault.CoreConfig, config *server.Config, core *vault.Core) error { | ||||
| func initDevCore(c *ServerCommand, coreConfig *vault.CoreConfig, config *server.Config, core *vault.Core, certDir string) error { | ||||
| 	if c.flagDev && !c.flagDevSkipInit { | ||||
|  | ||||
| 		init, err := c.enableDev(core, coreConfig) | ||||
| @@ -2655,10 +2727,15 @@ func initDevCore(c *ServerCommand, coreConfig *vault.CoreConfig, config *server. | ||||
| 							"token is already authenticated to the CLI, so you can immediately " + | ||||
| 							"begin using Vault.")) | ||||
| 					c.UI.Warn("") | ||||
| 					c.UI.Warn("You may need to set the following environment variable:") | ||||
| 					c.UI.Warn("You may need to set the following environment variables:") | ||||
| 					c.UI.Warn("") | ||||
|  | ||||
| 					endpointURL := "http://" + config.Listeners[0].Address | ||||
| 					protocol := "http://" | ||||
| 					if c.flagDevTLS { | ||||
| 						protocol = "https://" | ||||
| 					} | ||||
|  | ||||
| 					endpointURL := protocol + config.Listeners[0].Address | ||||
| 					if runtime.GOOS == "windows" { | ||||
| 						c.UI.Warn("PowerShell:") | ||||
| 						c.UI.Warn(fmt.Sprintf("    $env:VAULT_ADDR=\"%s\"", endpointURL)) | ||||
| @@ -2668,6 +2745,18 @@ func initDevCore(c *ServerCommand, coreConfig *vault.CoreConfig, config *server. | ||||
| 						c.UI.Warn(fmt.Sprintf("    $ export VAULT_ADDR='%s'", endpointURL)) | ||||
| 					} | ||||
|  | ||||
| 					if c.flagDevTLS { | ||||
| 						if runtime.GOOS == "windows" { | ||||
| 							c.UI.Warn("PowerShell:") | ||||
| 							c.UI.Warn(fmt.Sprintf("    $env:VAULT_CACERT=\"%s/vault-ca.pem\"", certDir)) | ||||
| 							c.UI.Warn("cmd.exe:") | ||||
| 							c.UI.Warn(fmt.Sprintf("    set VAULT_CACERT=%s/vault-ca.pem", certDir)) | ||||
| 						} else { | ||||
| 							c.UI.Warn(fmt.Sprintf("    $ export VAULT_CACERT='%s/vault-ca.pem'", certDir)) | ||||
| 						} | ||||
| 						c.UI.Warn("") | ||||
| 					} | ||||
|  | ||||
| 					// Unseal key is not returned if stored shares is supported | ||||
| 					if len(init.SecretShares) > 0 { | ||||
| 						c.UI.Warn("") | ||||
|   | ||||
| @@ -22,6 +22,12 @@ import ( | ||||
| 	"github.com/hashicorp/vault/sdk/helper/consts" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	VaultDevCAFilename   = "vault-ca.pem" | ||||
| 	VaultDevCertFilename = "vault-cert.pem" | ||||
| 	VaultDevKeyFilename  = "vault-key.pem" | ||||
| ) | ||||
|  | ||||
| var entConfigValidate = func(_ *Config, _ string) []configutil.ConfigError { | ||||
| 	return nil | ||||
| } | ||||
| @@ -151,6 +157,62 @@ ui = true | ||||
| 	return parsed, nil | ||||
| } | ||||
|  | ||||
| // DevTLSConfig is a Config that is used for dev tls mode of Vault. | ||||
| func DevTLSConfig(storageType, certDir string) (*Config, error) { | ||||
| 	ca, err := GenerateCA() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	cert, key, err := GenerateCert(ca.Template, ca.Signer) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if err := os.WriteFile(fmt.Sprintf("%s/%s", certDir, VaultDevCAFilename), []byte(ca.PEM), 0o444); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if err := os.WriteFile(fmt.Sprintf("%s/%s", certDir, VaultDevCertFilename), []byte(cert), 0o400); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if err := os.WriteFile(fmt.Sprintf("%s/%s", certDir, VaultDevKeyFilename), []byte(key), 0o400); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	hclStr := ` | ||||
| disable_mlock = true | ||||
|  | ||||
| listener "tcp" { | ||||
| 	address = "[::]:8200" | ||||
| 	tls_cert_file = "%s/vault-cert.pem" | ||||
| 	tls_key_file = "%s/vault-key.pem" | ||||
| 	proxy_protocol_behavior = "allow_authorized" | ||||
| 	proxy_protocol_authorized_addrs = "[::]:8200" | ||||
| } | ||||
|  | ||||
| telemetry { | ||||
| 	prometheus_retention_time = "24h" | ||||
| 	disable_hostname = true | ||||
| } | ||||
| enable_raw_endpoint = true | ||||
|  | ||||
| storage "%s" { | ||||
| } | ||||
|  | ||||
| ui = true | ||||
| ` | ||||
|  | ||||
| 	hclStr = fmt.Sprintf(hclStr, certDir, certDir, storageType) | ||||
| 	parsed, err := ParseConfig(hclStr, "") | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return parsed, nil | ||||
| } | ||||
|  | ||||
| // Storage is the underlying storage configuration for the server. | ||||
| type Storage struct { | ||||
| 	Type              string | ||||
|   | ||||
							
								
								
									
										162
									
								
								command/server/tls_util.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								command/server/tls_util.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,162 @@ | ||||
| package server | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"crypto" | ||||
| 	"crypto/ecdsa" | ||||
| 	"crypto/elliptic" | ||||
| 	"crypto/rand" | ||||
| 	"crypto/x509" | ||||
| 	"crypto/x509/pkix" | ||||
| 	"encoding/pem" | ||||
| 	"fmt" | ||||
| 	"math/big" | ||||
| 	"net" | ||||
| 	"os" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/hashicorp/vault/sdk/helper/certutil" | ||||
| ) | ||||
|  | ||||
| type CaCert struct { | ||||
| 	PEM      string | ||||
| 	Template *x509.Certificate | ||||
| 	Signer   crypto.Signer | ||||
| } | ||||
|  | ||||
| // GenerateCert creates a new leaf cert from provided CA template and signer | ||||
| func GenerateCert(caCertTemplate *x509.Certificate, caSigner crypto.Signer) (string, string, error) { | ||||
| 	// Create the private key | ||||
| 	signer, keyPEM, err := privateKey() | ||||
| 	if err != nil { | ||||
| 		return "", "", fmt.Errorf("error generating private key for server certificate: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	// The serial number for the cert | ||||
| 	sn, err := serialNumber() | ||||
| 	if err != nil { | ||||
| 		return "", "", fmt.Errorf("error generating serial number: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	signerKeyId, err := certutil.GetSubjKeyID(signer) | ||||
| 	if err != nil { | ||||
| 		return "", "", fmt.Errorf("error getting subject key id from key: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	hostname, err := os.Hostname() | ||||
| 	if err != nil { | ||||
| 		return "", "", fmt.Errorf("error getting hostname: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	if hostname == "" { | ||||
| 		hostname = "localhost" | ||||
| 	} | ||||
|  | ||||
| 	// Create the leaf cert | ||||
| 	template := x509.Certificate{ | ||||
| 		SerialNumber:   sn, | ||||
| 		Subject:        pkix.Name{CommonName: hostname}, | ||||
| 		KeyUsage:       x509.KeyUsageDigitalSignature, | ||||
| 		ExtKeyUsage:    []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, | ||||
| 		NotAfter:       time.Now().Add(365 * 24 * time.Hour), | ||||
| 		NotBefore:      time.Now().Add(-1 * time.Minute), | ||||
| 		IPAddresses:    []net.IP{net.ParseIP("127.0.0.1")}, | ||||
| 		DNSNames:       []string{"localhost", "localhost4", "localhost6", "localhost.localdomain"}, | ||||
| 		AuthorityKeyId: caCertTemplate.AuthorityKeyId, | ||||
| 		SubjectKeyId:   signerKeyId, | ||||
| 	} | ||||
|  | ||||
| 	bs, err := x509.CreateCertificate( | ||||
| 		rand.Reader, &template, caCertTemplate, signer.Public(), caSigner) | ||||
| 	if err != nil { | ||||
| 		return "", "", fmt.Errorf("error creating server certificate: %v", err) | ||||
| 	} | ||||
| 	var buf bytes.Buffer | ||||
| 	err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: bs}) | ||||
| 	if err != nil { | ||||
| 		return "", "", fmt.Errorf("error encoding server certificate: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	return buf.String(), keyPEM, nil | ||||
| } | ||||
|  | ||||
| // GenerateCA generates a new self-signed CA cert and returns a | ||||
| // CaCert struct containing the PEM encoded cert, | ||||
| // X509 Certificate Template, and crypto.Signer | ||||
| func GenerateCA() (*CaCert, error) { | ||||
| 	// Create the private key we'll use for this CA cert. | ||||
| 	signer, _, err := privateKey() | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error generating private key for CA: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	signerKeyId, err := certutil.GetSubjKeyID(signer) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error getting subject key id from key: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	// The serial number for the cert | ||||
| 	sn, err := serialNumber() | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error generating serial number: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	// Create the CA cert | ||||
| 	template := x509.Certificate{ | ||||
| 		SerialNumber:          sn, | ||||
| 		Subject:               pkix.Name{CommonName: "Vault Dev CA"}, | ||||
| 		BasicConstraintsValid: true, | ||||
| 		KeyUsage:              x509.KeyUsageCertSign, | ||||
| 		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, | ||||
| 		IsCA:                  true, | ||||
| 		NotAfter:              time.Now().Add(10 * 365 * 24 * time.Hour), | ||||
| 		NotBefore:             time.Now().Add(-1 * time.Minute), | ||||
| 		AuthorityKeyId:        signerKeyId, | ||||
| 		SubjectKeyId:          signerKeyId, | ||||
| 		IPAddresses:           []net.IP{net.ParseIP("127.0.0.1")}, | ||||
| 	} | ||||
|  | ||||
| 	bs, err := x509.CreateCertificate( | ||||
| 		rand.Reader, &template, &template, signer.Public(), signer) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error creating CA certificate: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	var buf bytes.Buffer | ||||
| 	err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: bs}) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error encoding CA certificate: %v", err) | ||||
| 	} | ||||
| 	return &CaCert{ | ||||
| 		PEM:      buf.String(), | ||||
| 		Template: &template, | ||||
| 		Signer:   signer, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| // privateKey returns a new ECDSA-based private key. Both a crypto.Signer | ||||
| // and the key in PEM format are returned. | ||||
| func privateKey() (crypto.Signer, string, error) { | ||||
| 	pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) | ||||
| 	if err != nil { | ||||
| 		return nil, "", err | ||||
| 	} | ||||
|  | ||||
| 	bs, err := x509.MarshalECPrivateKey(pk) | ||||
| 	if err != nil { | ||||
| 		return nil, "", err | ||||
| 	} | ||||
|  | ||||
| 	var buf bytes.Buffer | ||||
| 	err = pem.Encode(&buf, &pem.Block{Type: "EC PRIVATE KEY", Bytes: bs}) | ||||
| 	if err != nil { | ||||
| 		return nil, "", err | ||||
| 	} | ||||
|  | ||||
| 	return pk, buf.String(), nil | ||||
| } | ||||
|  | ||||
| // serialNumber generates a new random serial number. | ||||
| func serialNumber() (*big.Int, error) { | ||||
| 	return rand.Int(rand.Reader, (&big.Int{}).Exp(big.NewInt(2), big.NewInt(159), nil)) | ||||
| } | ||||
| @@ -66,6 +66,12 @@ flags](/docs/commands) included on all commands. | ||||
|   in-memory and starts unsealed. As the name implies, do not run "dev" mode in | ||||
|   production. | ||||
|  | ||||
| - `-dev-tls` `(bool: false)` - Enable TLS development mode. In this mode, Vault runs | ||||
|   in-memory and starts unsealed with a generated TLS CA, certificate and key.  | ||||
|   As the name implies, do not run "dev" mode in production. | ||||
|  | ||||
| - `-dev-tls-cert-dir` `(string: "")` - Directory where generated TLS files are created if `-dev-tls` is specified. If left unset, files are generated in a temporary directory. | ||||
|  | ||||
| - `-dev-listen-address` `(string: "127.0.0.1:8200")` - Address to bind to in | ||||
|   "dev" mode. This can also be specified via the `VAULT_DEV_LISTEN_ADDRESS` | ||||
|   environment variable. | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Jason O'Donnell
					Jason O'Donnell