Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

command/server: add dev-tls flag #16421

Merged
merged 16 commits into from Jul 22, 2022
Merged
3 changes: 3 additions & 0 deletions changelog/16421.txt
@@ -0,0 +1,3 @@
```release-note:improvement
command/server: add `-dev-tls` subcommand to create a Vault dev server with generated certificates and private key.
```
53 changes: 46 additions & 7 deletions command/server.go
Expand Up @@ -117,6 +117,7 @@ type ServerCommand struct {
flagLogFormat string
flagRecovery bool
flagDev bool
flagDevTLS bool
flagDevRootTokenID string
flagDevListenAddr string
flagDevNoStoreToken bool
Expand Down Expand Up @@ -245,6 +246,15 @@ 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-root-token-id",
Target: &c.flagDevRootTokenID,
Expand Down Expand Up @@ -1026,7 +1036,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
}

Expand Down Expand Up @@ -1062,6 +1072,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 {
Expand All @@ -1076,11 +1087,18 @@ func (c *ServerCommand) Run(args []string) int {
default:
devStorageType = "inmem"
}
config, err = server.DevConfig(devStorageType)

if c.flagDevTLS {
config, certDir, err = server.DevTLSConfig(devStorageType)
} 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
}
Expand Down Expand Up @@ -1495,7 +1513,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
Expand Down Expand Up @@ -2442,7 +2460,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
}
Expand Down Expand Up @@ -2604,7 +2626,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)
Expand Down Expand Up @@ -2655,10 +2677,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))
Expand All @@ -2668,6 +2695,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("")
Expand Down
61 changes: 61 additions & 0 deletions command/server/config.go
Expand Up @@ -151,6 +151,67 @@ ui = true
return parsed, nil
}

// DevTLSConfig is a Config that is used for dev tls mode of Vault.
func DevTLSConfig(storageType string) (*Config, string, error) {
dir, err := os.MkdirTemp("", "vault-tls")
jasonodonnell marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, "", err
}

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/vault-ca.pem", dir), []byte(ca.PEM), 0o644); err != nil {
jasonodonnell marked this conversation as resolved.
Show resolved Hide resolved
return nil, "", err
}

if err := os.WriteFile(fmt.Sprintf("%s/vault-cert.pem", dir), []byte(cert), 0o644); err != nil {
cipherboy marked this conversation as resolved.
Show resolved Hide resolved
return nil, "", err
}

if err := os.WriteFile(fmt.Sprintf("%s/vault-key.pem", dir), []byte(key), 0o644); 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, dir, dir, storageType)
parsed, err := ParseConfig(hclStr, "")
if err != nil {
return nil, "", err
}

return parsed, dir, nil
}

// Storage is the underlying storage configuration for the server.
type Storage struct {
Type string
Expand Down
167 changes: 167 additions & 0 deletions command/server/tls_util.go
@@ -0,0 +1,167 @@
package server
jasonodonnell marked this conversation as resolved.
Show resolved Hide resolved

import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"net"
"strings"
"time"
)

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 "", "", err
jasonodonnell marked this conversation as resolved.
Show resolved Hide resolved
}

// The serial number for the cert
sn, err := serialNumber()
if err != nil {
return "", "", err
}

// Create the leaf cert
template := x509.Certificate{
SerialNumber: sn,
Subject: pkix.Name{CommonName: "Vault server"},
cipherboy marked this conversation as resolved.
Show resolved Hide resolved
BasicConstraintsValid: true,
cipherboy marked this conversation as resolved.
Show resolved Hide resolved
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
cipherboy marked this conversation as resolved.
Show resolved Hide resolved
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
cipherboy marked this conversation as resolved.
Show resolved Hide resolved
NotAfter: time.Now().Add(365 * 24 * time.Hour),
NotBefore: time.Now().Add(-1 * time.Minute),
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
cipherboy marked this conversation as resolved.
Show resolved Hide resolved
}

bs, err := x509.CreateCertificate(
rand.Reader, &template, caCertTemplate, signer.Public(), caSigner)
if err != nil {
return "", "", err
}
var buf bytes.Buffer
err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: bs})
if err != nil {
return "", "", 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, err
}

// The serial number for the cert
sn, err := serialNumber()
if err != nil {
return nil, err
}

signerKeyId, err := keyId(signer.Public())
cipherboy marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}

// Create the CA cert
template := x509.Certificate{
SerialNumber: sn,
Subject: pkix.Name{CommonName: "Vault CA"},
BasicConstraintsValid: true,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
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, err
}

var buf bytes.Buffer
err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: bs})
if err != nil {
return nil, 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))
}

// keyId returns a x509 KeyId from the given signing key. The key must be
// an *ecdsa.PublicKey currently, but may support more types in the future.
func keyId(raw interface{}) ([]byte, error) {
switch raw.(type) {
case *ecdsa.PublicKey:
default:
return nil, fmt.Errorf("invalid key type: %T", raw)
}

// This is not standard; RFC allows any unique identifier as long as they
// match in subject/authority chains but suggests specific hashing of DER
// bytes of public key including DER tags.
bs, err := x509.MarshalPKIXPublicKey(raw)
if err != nil {
return nil, err
}

// String formatted
kID := sha256.Sum256(bs)
return []byte(strings.Replace(fmt.Sprintf("% x", kID), " ", ":", -1)), nil
cipherboy marked this conversation as resolved.
Show resolved Hide resolved
}
4 changes: 4 additions & 0 deletions website/content/docs/commands/server.mdx
Expand Up @@ -66,6 +66,10 @@ 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-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.
Expand Down