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:
parent
45be51df49
commit
140406143e
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue