Builtin tls helper (#5078)
* command: add tls subcommand * website: update docs and guide
This commit is contained in:
parent
1237bf5d11
commit
1a520d65b4
|
@ -70,6 +70,11 @@ import (
|
||||||
snapinspect "github.com/hashicorp/consul/command/snapshot/inspect"
|
snapinspect "github.com/hashicorp/consul/command/snapshot/inspect"
|
||||||
snaprestore "github.com/hashicorp/consul/command/snapshot/restore"
|
snaprestore "github.com/hashicorp/consul/command/snapshot/restore"
|
||||||
snapsave "github.com/hashicorp/consul/command/snapshot/save"
|
snapsave "github.com/hashicorp/consul/command/snapshot/save"
|
||||||
|
"github.com/hashicorp/consul/command/tls"
|
||||||
|
tlsca "github.com/hashicorp/consul/command/tls/ca"
|
||||||
|
tlscacreate "github.com/hashicorp/consul/command/tls/ca/create"
|
||||||
|
tlscert "github.com/hashicorp/consul/command/tls/cert"
|
||||||
|
tlscertcreate "github.com/hashicorp/consul/command/tls/cert/create"
|
||||||
"github.com/hashicorp/consul/command/validate"
|
"github.com/hashicorp/consul/command/validate"
|
||||||
"github.com/hashicorp/consul/command/version"
|
"github.com/hashicorp/consul/command/version"
|
||||||
"github.com/hashicorp/consul/command/watch"
|
"github.com/hashicorp/consul/command/watch"
|
||||||
|
@ -155,6 +160,11 @@ func init() {
|
||||||
Register("snapshot inspect", func(ui cli.Ui) (cli.Command, error) { return snapinspect.New(ui), nil })
|
Register("snapshot inspect", func(ui cli.Ui) (cli.Command, error) { return snapinspect.New(ui), nil })
|
||||||
Register("snapshot restore", func(ui cli.Ui) (cli.Command, error) { return snaprestore.New(ui), nil })
|
Register("snapshot restore", func(ui cli.Ui) (cli.Command, error) { return snaprestore.New(ui), nil })
|
||||||
Register("snapshot save", func(ui cli.Ui) (cli.Command, error) { return snapsave.New(ui), nil })
|
Register("snapshot save", func(ui cli.Ui) (cli.Command, error) { return snapsave.New(ui), nil })
|
||||||
|
Register("tls", func(ui cli.Ui) (cli.Command, error) { return tls.New(), nil })
|
||||||
|
Register("tls ca", func(ui cli.Ui) (cli.Command, error) { return tlsca.New(), nil })
|
||||||
|
Register("tls ca create", func(ui cli.Ui) (cli.Command, error) { return tlscacreate.New(ui), nil })
|
||||||
|
Register("tls cert", func(ui cli.Ui) (cli.Command, error) { return tlscert.New(), nil })
|
||||||
|
Register("tls cert create", func(ui cli.Ui) (cli.Command, error) { return tlscertcreate.New(ui), nil })
|
||||||
Register("validate", func(ui cli.Ui) (cli.Command, error) { return validate.New(ui), nil })
|
Register("validate", func(ui cli.Ui) (cli.Command, error) { return validate.New(ui), nil })
|
||||||
Register("version", func(ui cli.Ui) (cli.Command, error) { return version.New(ui, verHuman), nil })
|
Register("version", func(ui cli.Ui) (cli.Command, error) { return version.New(ui, verHuman), nil })
|
||||||
Register("watch", func(ui cli.Ui) (cli.Command, error) { return watch.New(ui, MakeShutdownCh()), nil })
|
Register("watch", func(ui cli.Ui) (cli.Command, error) { return watch.New(ui, MakeShutdownCh()), nil })
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCatalogCommand_noTabs(t *testing.T) {
|
func TestConnectCommand_noTabs(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
if strings.ContainsRune(New().Help(), '\t') {
|
if strings.ContainsRune(New().Help(), '\t') {
|
||||||
t.Fatal("help has tabs")
|
t.Fatal("help has tabs")
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
package create
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/command/flags"
|
||||||
|
"github.com/hashicorp/consul/command/tls"
|
||||||
|
"github.com/mitchellh/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func New(ui cli.Ui) *cmd {
|
||||||
|
c := &cmd{UI: ui}
|
||||||
|
c.init()
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
type cmd struct {
|
||||||
|
UI cli.Ui
|
||||||
|
flags *flag.FlagSet
|
||||||
|
help string
|
||||||
|
days int
|
||||||
|
domain string
|
||||||
|
constraint bool
|
||||||
|
additionalConstraints flags.AppendSliceValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmd) init() {
|
||||||
|
c.flags = flag.NewFlagSet("", flag.ContinueOnError)
|
||||||
|
c.flags.IntVar(&c.days, "days", 1825, "Provide number of days the CA is valid for from now on. Defaults to 5 years.")
|
||||||
|
c.flags.BoolVar(&c.constraint, "name-constraint", false, "Add name constraints for the CA. Results in rejecting "+
|
||||||
|
"certificates for other DNS than specified. If turned on localhost and -domain will be added to the allowed "+
|
||||||
|
"DNS. If the UI is going to be served over HTTPS its DNS has to be added with -additional-constraint. It is not "+
|
||||||
|
"possible to add that after the fact! Defaults to false.")
|
||||||
|
c.flags.StringVar(&c.domain, "domain", "consul", "Domain of consul cluster. Only used in combination with -name-constraint. Defaults to consul.")
|
||||||
|
c.flags.Var(&c.additionalConstraints, "additional-name-constraint", "Add name constraints for the CA. Results in rejecting certificates "+
|
||||||
|
"for other DNS than specified. Can be used multiple times. Only used in combination with -name-constraint.")
|
||||||
|
c.help = flags.Usage(help, c.flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmd) Run(args []string) int {
|
||||||
|
if err := c.flags.Parse(args); err != nil {
|
||||||
|
if err == flag.ErrHelp {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
c.UI.Error(fmt.Sprintf("Failed to parse args: %v", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
certFileName := fmt.Sprintf("%s-agent-ca.pem", c.domain)
|
||||||
|
pkFileName := fmt.Sprintf("%s-agent-ca-key.pem", c.domain)
|
||||||
|
|
||||||
|
if !(tls.FileDoesNotExist(certFileName)) {
|
||||||
|
c.UI.Error(certFileName + " already exists.")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if !(tls.FileDoesNotExist(pkFileName)) {
|
||||||
|
c.UI.Error(pkFileName + " already exists.")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
sn, err := tls.GenerateSerialNumber()
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
s, pk, err := tls.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(err.Error())
|
||||||
|
}
|
||||||
|
constraints := []string{}
|
||||||
|
if c.constraint {
|
||||||
|
constraints = append(c.additionalConstraints, []string{c.domain, "localhost"}...)
|
||||||
|
}
|
||||||
|
ca, err := tls.GenerateCA(s, sn, c.days, constraints)
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(err.Error())
|
||||||
|
}
|
||||||
|
caFile, err := os.Create(certFileName)
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(err.Error())
|
||||||
|
}
|
||||||
|
caFile.WriteString(ca)
|
||||||
|
c.UI.Output("==> Saved " + certFileName)
|
||||||
|
pkFile, err := os.Create(pkFileName)
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(err.Error())
|
||||||
|
}
|
||||||
|
pkFile.WriteString(pk)
|
||||||
|
c.UI.Output("==> Saved " + pkFileName)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmd) Synopsis() string {
|
||||||
|
return synopsis
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmd) Help() string {
|
||||||
|
return c.help
|
||||||
|
}
|
||||||
|
|
||||||
|
const synopsis = "Create a new consul CA"
|
||||||
|
const help = `
|
||||||
|
Usage: consul tls ca create [options]
|
||||||
|
|
||||||
|
Create a new consul CA:
|
||||||
|
|
||||||
|
$ consul tls ca create
|
||||||
|
==> saved consul-agent-ca.pem
|
||||||
|
==> saved consul-agent-ca-key.pem
|
||||||
|
`
|
|
@ -0,0 +1,13 @@
|
||||||
|
package create
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidateCommand_noTabs(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
if strings.ContainsRune(New(nil).Help(), '\t') {
|
||||||
|
t.Fatal("help has tabs")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package ca
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/consul/command/flags"
|
||||||
|
"github.com/mitchellh/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func New() *cmd {
|
||||||
|
return &cmd{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type cmd struct{}
|
||||||
|
|
||||||
|
func (c *cmd) Run(args []string) int {
|
||||||
|
return cli.RunResultHelp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmd) Synopsis() string {
|
||||||
|
return synopsis
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmd) Help() string {
|
||||||
|
return flags.Usage(help, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
const synopsis = `Helpers for CAs`
|
||||||
|
const help = `
|
||||||
|
Usage: consul tls ca <subcommand> [options] filename-prefix
|
||||||
|
|
||||||
|
This command has subcommands for interacting with Certificate Authorities.
|
||||||
|
|
||||||
|
Here are some simple examples, and more detailed examples are available
|
||||||
|
in the subcommands or the documentation.
|
||||||
|
|
||||||
|
Create a CA
|
||||||
|
|
||||||
|
$ consul tls ca create
|
||||||
|
==> saved consul-agent-ca.pem
|
||||||
|
==> saved consul-agent-ca-key.pem
|
||||||
|
|
||||||
|
For more examples, ask for subcommand help or view the documentation.
|
||||||
|
`
|
|
@ -0,0 +1,13 @@
|
||||||
|
package ca
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidateCommand_noTabs(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
if strings.ContainsRune(New().Help(), '\t') {
|
||||||
|
t.Fatal("help has tabs")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,216 @@
|
||||||
|
package create
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/command/flags"
|
||||||
|
"github.com/hashicorp/consul/command/tls"
|
||||||
|
"github.com/mitchellh/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func New(ui cli.Ui) *cmd {
|
||||||
|
c := &cmd{UI: ui}
|
||||||
|
c.init()
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
type cmd struct {
|
||||||
|
UI cli.Ui
|
||||||
|
flags *flag.FlagSet
|
||||||
|
ca string
|
||||||
|
key string
|
||||||
|
server bool
|
||||||
|
client bool
|
||||||
|
cli bool
|
||||||
|
dc string
|
||||||
|
days int
|
||||||
|
domain string
|
||||||
|
help string
|
||||||
|
dnsnames flags.AppendSliceValue
|
||||||
|
prefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmd) init() {
|
||||||
|
c.flags = flag.NewFlagSet("", flag.ContinueOnError)
|
||||||
|
c.flags.StringVar(&c.ca, "ca", "#DOMAINa#-agent-ca.pem", "Provide path to the ca. Defaults to #DOMAIN#-agent-ca.pem.")
|
||||||
|
c.flags.StringVar(&c.key, "key", "#DOMAIN#-agent-ca-key.pem", "Provide path to the key. Defaults to #DOMAIN#-agent-ca-key.pem.")
|
||||||
|
c.flags.BoolVar(&c.server, "server", false, "Generate server certificate.")
|
||||||
|
c.flags.BoolVar(&c.client, "client", false, "Generate client certificate.")
|
||||||
|
c.flags.BoolVar(&c.cli, "cli", false, "Generate cli certificate.")
|
||||||
|
c.flags.IntVar(&c.days, "days", 365, "Provide number of days the certificate is valid for from now on. Defaults to 1 year.")
|
||||||
|
c.flags.StringVar(&c.dc, "dc", "dc1", "Provide the datacenter. Matters only for -server certificates. Defaults to dc1.")
|
||||||
|
c.flags.StringVar(&c.domain, "domain", "consul", "Provide the domain. Matters only for -server certificates.")
|
||||||
|
c.flags.Var(&c.dnsnames, "additional-dnsname", "Provide an additional dnsname for Subject Alternative Names. "+
|
||||||
|
"127.0.0.1 and localhost are always included. This flag may be provided multiple times.")
|
||||||
|
c.help = flags.Usage(help, c.flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmd) Run(args []string) int {
|
||||||
|
if err := c.flags.Parse(args); err != nil {
|
||||||
|
if err == flag.ErrHelp {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
c.UI.Error(fmt.Sprintf("Failed to parse args: %v", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if c.ca == "" {
|
||||||
|
c.UI.Error("Please provide the ca")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if c.key == "" {
|
||||||
|
c.UI.Error("Please provide the key")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if !((c.server && !c.client && !c.cli) ||
|
||||||
|
(!c.server && c.client && !c.cli) ||
|
||||||
|
(!c.server && !c.client && c.cli)) {
|
||||||
|
c.UI.Error("Please provide either -server, -client, or -cli")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
var DNSNames []string
|
||||||
|
var IPAddresses []net.IP
|
||||||
|
var extKeyUsage []x509.ExtKeyUsage
|
||||||
|
var name, prefix string
|
||||||
|
|
||||||
|
for _, d := range c.dnsnames {
|
||||||
|
if len(d) > 0 {
|
||||||
|
DNSNames = append(DNSNames, strings.TrimSpace(d))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.server {
|
||||||
|
name = fmt.Sprintf("server.%s.%s", c.dc, c.domain)
|
||||||
|
DNSNames = append(DNSNames, []string{name, "localhost"}...)
|
||||||
|
IPAddresses = []net.IP{net.ParseIP("127.0.0.1")}
|
||||||
|
extKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}
|
||||||
|
prefix = fmt.Sprintf("%s-server-%s", c.dc, c.domain)
|
||||||
|
} else if c.client {
|
||||||
|
name = fmt.Sprintf("client.%s.%s", c.dc, c.domain)
|
||||||
|
DNSNames = append(DNSNames, []string{name, "localhost"}...)
|
||||||
|
IPAddresses = []net.IP{net.ParseIP("127.0.0.1")}
|
||||||
|
extKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}
|
||||||
|
prefix = fmt.Sprintf("%s-client-%s", c.dc, c.domain)
|
||||||
|
} else if c.cli {
|
||||||
|
name = fmt.Sprintf("cli.%s.%s", c.dc, c.domain)
|
||||||
|
DNSNames = []string{name, "localhost"}
|
||||||
|
prefix = fmt.Sprintf("%s-cli-%s", c.dc, c.domain)
|
||||||
|
} else {
|
||||||
|
c.UI.Error("Neither client, cli nor server - should not happen")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
var pkFileName, certFileName string
|
||||||
|
max := 10000
|
||||||
|
for i := 0; i <= max; i++ {
|
||||||
|
tmpCert := fmt.Sprintf("%s-%d.pem", prefix, i)
|
||||||
|
tmpPk := fmt.Sprintf("%s-%d-key.pem", prefix, i)
|
||||||
|
if tls.FileDoesNotExist(tmpCert) && tls.FileDoesNotExist(tmpPk) {
|
||||||
|
certFileName = tmpCert
|
||||||
|
pkFileName = tmpPk
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if i == max {
|
||||||
|
c.UI.Error("Could not find a filename that doesn't already exist")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
caFile := strings.Replace(c.ca, "#DOMAIN#", c.domain, 1)
|
||||||
|
keyFile := strings.Replace(c.key, "#DOMAIN#", c.domain, 1)
|
||||||
|
cert, err := ioutil.ReadFile(caFile)
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(fmt.Sprintf("Error reading CA: %s", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
key, err := ioutil.ReadFile(keyFile)
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(fmt.Sprintf("Error reading CA key: %s", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.server {
|
||||||
|
c.UI.Info(
|
||||||
|
`==> WARNING: Server Certificates grants authority to become a
|
||||||
|
server and access all state in the cluster including root keys
|
||||||
|
and all ACL tokens. Do not distribute them to production hosts
|
||||||
|
that are not server nodes. Store them as securely as CA keys.`)
|
||||||
|
}
|
||||||
|
c.UI.Info("==> Using " + caFile + " and " + keyFile)
|
||||||
|
|
||||||
|
signer, err := tls.ParseSigner(string(key))
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
sn, err := tls.GenerateSerialNumber()
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub, priv, err := tls.GenerateCert(signer, string(cert), sn, name, c.days, DNSNames, IPAddresses, extKeyUsage)
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = tls.Verify(string(cert), pub, name); err != nil {
|
||||||
|
c.UI.Error("==> " + err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
certFile, err := os.Create(certFileName)
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
certFile.WriteString(pub)
|
||||||
|
c.UI.Output("==> Saved " + certFileName)
|
||||||
|
|
||||||
|
pkFile, err := os.Create(pkFileName)
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
pkFile.WriteString(priv)
|
||||||
|
c.UI.Output("==> Saved " + pkFileName)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmd) Synopsis() string {
|
||||||
|
return synopsis
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmd) Help() string {
|
||||||
|
return c.help
|
||||||
|
}
|
||||||
|
|
||||||
|
const synopsis = "Create a new certificate"
|
||||||
|
const help = `
|
||||||
|
Usage: consul tls cert create [options]
|
||||||
|
|
||||||
|
Create a new certificate
|
||||||
|
|
||||||
|
$ consul tls cert create -server
|
||||||
|
==> WARNING: Server Certificates grants authority to become a
|
||||||
|
server and access all state in the cluster including root keys
|
||||||
|
and all ACL tokens. Do not distribute them to production hosts
|
||||||
|
that are not server nodes. Store them as securely as CA keys.
|
||||||
|
==> Using consul-agent-ca.pem and consul-agent-ca-key.pem
|
||||||
|
==> Saved consul-server-dc1-0.pem
|
||||||
|
==> Saved consul-server-dc1-0-key.pem
|
||||||
|
$ consul tls cert -client
|
||||||
|
==> Using consul-agent-ca.pem and consul-agent-ca-key.pem
|
||||||
|
==> Saved consul-client-dc1-0.pem
|
||||||
|
==> Saved consul-client-dc1-0-key.pem
|
||||||
|
`
|
|
@ -0,0 +1,13 @@
|
||||||
|
package create
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidateCommand_noTabs(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
if strings.ContainsRune(New(nil).Help(), '\t') {
|
||||||
|
t.Fatal("help has tabs")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package cert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/consul/command/flags"
|
||||||
|
"github.com/mitchellh/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func New() *cmd {
|
||||||
|
return &cmd{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type cmd struct{}
|
||||||
|
|
||||||
|
func (c *cmd) Run(args []string) int {
|
||||||
|
return cli.RunResultHelp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmd) Synopsis() string {
|
||||||
|
return synopsis
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmd) Help() string {
|
||||||
|
return flags.Usage(help, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
const synopsis = `Helpers for certificates`
|
||||||
|
const help = `
|
||||||
|
Usage: consul tls cert <subcommand> [options] [filename-prefix]
|
||||||
|
|
||||||
|
This command has subcommands for interacting with certificates
|
||||||
|
|
||||||
|
Here are some simple examples, and more detailed examples are available
|
||||||
|
in the subcommands or the documentation.
|
||||||
|
|
||||||
|
Create a certificate
|
||||||
|
|
||||||
|
$ consul tls cert create -server
|
||||||
|
==> saved consul-server-dc1.pem
|
||||||
|
==> saved consul-server-dc1-key.pem
|
||||||
|
|
||||||
|
Create a certificate with your own CA:
|
||||||
|
|
||||||
|
$ consul tls cert create -server -ca-file my-ca.pem -ca-key-file my-ca-key.pem
|
||||||
|
==> saved consul-server-dc1.pem
|
||||||
|
==> saved consul-server-dc1-key.pem
|
||||||
|
|
||||||
|
For more examples, ask for subcommand help or view the documentation.
|
||||||
|
`
|
|
@ -0,0 +1,13 @@
|
||||||
|
package cert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidateCommand_noTabs(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
if strings.ContainsRune(New().Help(), '\t') {
|
||||||
|
t.Fatal("help has tabs")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,216 @@
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GenerateSerialNumber returns random bigint generated with crypto/rand
|
||||||
|
func GenerateSerialNumber() (*big.Int, error) {
|
||||||
|
l := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||||
|
s, err := rand.Int(rand.Reader, l)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GeneratePrivateKey generates a new ecdsa private key
|
||||||
|
func GeneratePrivateKey() (crypto.Signer, string, error) {
|
||||||
|
pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("error generating private key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bs, err := x509.MarshalECPrivateKey(pk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("error generating private key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = pem.Encode(&buf, &pem.Block{Type: "EC PRIVATE KEY", Bytes: bs})
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("error encoding private key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pk, buf.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateCA generates a new CA for agent TLS (not to be confused with Connect TLS)
|
||||||
|
func GenerateCA(signer crypto.Signer, sn *big.Int, days int, constraints []string) (string, error) {
|
||||||
|
id, err := keyID(signer.Public())
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
name := fmt.Sprintf("Consul Agent CA %d", sn)
|
||||||
|
|
||||||
|
// Create the CA cert
|
||||||
|
template := x509.Certificate{
|
||||||
|
SerialNumber: sn,
|
||||||
|
Subject: pkix.Name{
|
||||||
|
Country: []string{"US"},
|
||||||
|
PostalCode: []string{"94105"},
|
||||||
|
Province: []string{"CA"},
|
||||||
|
Locality: []string{"San Francisco"},
|
||||||
|
StreetAddress: []string{"101 Second Street"},
|
||||||
|
Organization: []string{"HashiCorp Inc."},
|
||||||
|
CommonName: name,
|
||||||
|
},
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageDigitalSignature,
|
||||||
|
IsCA: true,
|
||||||
|
NotAfter: time.Now().AddDate(0, 0, days),
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
AuthorityKeyId: id,
|
||||||
|
SubjectKeyId: id,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(constraints) > 0 {
|
||||||
|
template.PermittedDNSDomainsCritical = true
|
||||||
|
template.PermittedDNSDomains = constraints
|
||||||
|
}
|
||||||
|
bs, err := x509.CreateCertificate(
|
||||||
|
rand.Reader, &template, &template, signer.Public(), signer)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error generating CA certificate: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: bs})
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error encoding private key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateCert generates a new certificate for agent TLS (not to be confused with Connect TLS)
|
||||||
|
func GenerateCert(signer crypto.Signer, ca string, sn *big.Int, name string, days int, DNSNames []string, IPAddresses []net.IP, extKeyUsage []x509.ExtKeyUsage) (string, string, error) {
|
||||||
|
parent, err := parseCert(ca)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
signee, pk, err := GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := keyID(signee.Public())
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
template := x509.Certificate{
|
||||||
|
SerialNumber: sn,
|
||||||
|
Subject: pkix.Name{CommonName: name},
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
||||||
|
ExtKeyUsage: extKeyUsage,
|
||||||
|
IsCA: false,
|
||||||
|
NotAfter: time.Now().AddDate(0, 0, days),
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
SubjectKeyId: id,
|
||||||
|
DNSNames: DNSNames,
|
||||||
|
IPAddresses: IPAddresses,
|
||||||
|
}
|
||||||
|
|
||||||
|
bs, err := x509.CreateCertificate(rand.Reader, &template, parent, signee.Public(), signer)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: bs})
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("error encoding private key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String(), pk, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyId returns a x509 KeyId from the given signing key.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCert(pemValue string) (*x509.Certificate, error) {
|
||||||
|
// The _ result below is not an error but the remaining PEM bytes.
|
||||||
|
block, _ := pem.Decode([]byte(pemValue))
|
||||||
|
if block == nil {
|
||||||
|
return nil, fmt.Errorf("no PEM-encoded data found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if block.Type != "CERTIFICATE" {
|
||||||
|
return nil, fmt.Errorf("first PEM-block should be CERTIFICATE type")
|
||||||
|
}
|
||||||
|
|
||||||
|
return x509.ParseCertificate(block.Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseSigner parses a crypto.Signer from a PEM-encoded key. The private key
|
||||||
|
// is expected to be the first block in the PEM value.
|
||||||
|
func ParseSigner(pemValue string) (crypto.Signer, error) {
|
||||||
|
// The _ result below is not an error but the remaining PEM bytes.
|
||||||
|
block, _ := pem.Decode([]byte(pemValue))
|
||||||
|
if block == nil {
|
||||||
|
return nil, fmt.Errorf("no PEM-encoded data found")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch block.Type {
|
||||||
|
case "EC PRIVATE KEY":
|
||||||
|
return x509.ParseECPrivateKey(block.Bytes)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown PEM block type for signing key: %s", block.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Verify(caString, certString, dns string) error {
|
||||||
|
roots := x509.NewCertPool()
|
||||||
|
ok := roots.AppendCertsFromPEM([]byte(caString))
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("failed to parse root certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := parseCert(certString)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := x509.VerifyOptions{
|
||||||
|
DNSName: fmt.Sprintf(dns),
|
||||||
|
Roots: roots,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = cert.Verify(opts)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSerialNumber(t *testing.T) {
|
||||||
|
n1, err := GenerateSerialNumber()
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
n2, err := GenerateSerialNumber()
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.NotEqual(t, n1, n2)
|
||||||
|
|
||||||
|
n3, err := GenerateSerialNumber()
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.NotEqual(t, n1, n3)
|
||||||
|
require.NotEqual(t, n2, n3)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGeneratePrivateKey(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
_, p, err := GeneratePrivateKey()
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.NotEmpty(t, p)
|
||||||
|
require.Contains(t, p, "BEGIN EC PRIVATE KEY")
|
||||||
|
require.Contains(t, p, "END EC PRIVATE KEY")
|
||||||
|
|
||||||
|
block, _ := pem.Decode([]byte(p))
|
||||||
|
pk, err := x509.ParseECPrivateKey(block.Bytes)
|
||||||
|
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.NotNil(t, pk)
|
||||||
|
require.Equal(t, 256, pk.Params().BitSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestSigner struct {
|
||||||
|
public interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TestSigner) Public() crypto.PublicKey {
|
||||||
|
return s.public
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TestSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
|
||||||
|
return []byte{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateCA(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
sn, err := GenerateSerialNumber()
|
||||||
|
require.Nil(t, err)
|
||||||
|
var s crypto.Signer
|
||||||
|
|
||||||
|
// test what happens without key
|
||||||
|
s = &TestSigner{}
|
||||||
|
ca, err := GenerateCA(s, sn, 0, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Empty(t, ca)
|
||||||
|
|
||||||
|
// test what happens with wrong key
|
||||||
|
s = &TestSigner{public: &rsa.PublicKey{}}
|
||||||
|
ca, err = GenerateCA(s, sn, 0, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Empty(t, ca)
|
||||||
|
|
||||||
|
// test what happens with correct key
|
||||||
|
s, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
require.Nil(t, err)
|
||||||
|
ca, err = GenerateCA(s, sn, 365, nil)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.NotEmpty(t, ca)
|
||||||
|
|
||||||
|
cert, err := parseCert(ca)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, fmt.Sprintf("Consul Agent CA %d", sn), cert.Subject.CommonName)
|
||||||
|
require.Equal(t, true, cert.IsCA)
|
||||||
|
require.Equal(t, true, cert.BasicConstraintsValid)
|
||||||
|
|
||||||
|
// format so that we don't take anything smaller than second into account.
|
||||||
|
require.Equal(t, cert.NotBefore.Format(time.ANSIC), time.Now().UTC().Format(time.ANSIC))
|
||||||
|
require.Equal(t, cert.NotAfter.Format(time.ANSIC), time.Now().AddDate(1, 0, 0).UTC().Format(time.ANSIC))
|
||||||
|
|
||||||
|
require.Equal(t, x509.KeyUsageCertSign|x509.KeyUsageCRLSign|x509.KeyUsageDigitalSignature, cert.KeyUsage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateCert(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
sn, err := GenerateSerialNumber()
|
||||||
|
require.Nil(t, err)
|
||||||
|
signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
require.Nil(t, err)
|
||||||
|
ca, err := GenerateCA(signer, sn, 365, nil)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
sn, err = GenerateSerialNumber()
|
||||||
|
require.Nil(t, err)
|
||||||
|
DNSNames := []string{"server.dc1.consul"}
|
||||||
|
IPAddresses := []net.IP{net.ParseIP("123.234.243.213")}
|
||||||
|
extKeyUsage := []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
|
||||||
|
name := "Cert Name"
|
||||||
|
certificate, pk, err := GenerateCert(signer, ca, sn, name, 365, DNSNames, IPAddresses, extKeyUsage)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.NotEmpty(t, certificate)
|
||||||
|
require.NotEmpty(t, pk)
|
||||||
|
|
||||||
|
cert, err := parseCert(certificate)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, name, cert.Subject.CommonName)
|
||||||
|
require.Equal(t, true, cert.BasicConstraintsValid)
|
||||||
|
signee, err := ParseSigner(pk)
|
||||||
|
require.Nil(t, err)
|
||||||
|
certID, err := keyID(signee.Public())
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, certID, cert.SubjectKeyId)
|
||||||
|
caID, err := keyID(signer.Public())
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, caID, cert.AuthorityKeyId)
|
||||||
|
require.Contains(t, cert.Issuer.CommonName, "Consul Agent CA")
|
||||||
|
require.Equal(t, false, cert.IsCA)
|
||||||
|
|
||||||
|
// format so that we don't take anything smaller than second into account.
|
||||||
|
require.Equal(t, cert.NotBefore.Format(time.ANSIC), time.Now().UTC().Format(time.ANSIC))
|
||||||
|
require.Equal(t, cert.NotAfter.Format(time.ANSIC), time.Now().AddDate(1, 0, 0).UTC().Format(time.ANSIC))
|
||||||
|
|
||||||
|
require.Equal(t, x509.KeyUsageDigitalSignature|x509.KeyUsageKeyEncipherment, cert.KeyUsage)
|
||||||
|
require.Equal(t, extKeyUsage, cert.ExtKeyUsage)
|
||||||
|
|
||||||
|
// https://github.com/golang/go/blob/10538a8f9e2e718a47633ac5a6e90415a2c3f5f1/src/crypto/x509/verify.go#L414
|
||||||
|
require.Equal(t, DNSNames, cert.DNSNames)
|
||||||
|
require.True(t, IPAddresses[0].Equal(cert.IPAddresses[0]))
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/command/flags"
|
||||||
|
"github.com/mitchellh/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func New() *cmd {
|
||||||
|
return &cmd{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type cmd struct{}
|
||||||
|
|
||||||
|
func (c *cmd) Run(args []string) int {
|
||||||
|
return cli.RunResultHelp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmd) Synopsis() string {
|
||||||
|
return synopsis
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmd) Help() string {
|
||||||
|
return flags.Usage(help, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FileDoesNotExist(file string) bool {
|
||||||
|
if _, err := os.Stat(file); os.IsNotExist(err) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const synopsis = `Builtin helpers for creating CAs and certificates`
|
||||||
|
const help = `
|
||||||
|
Usage: consul tls <subcommand> <subcommand> [options]
|
||||||
|
|
||||||
|
This command has subcommands for interacting with Consul TLS.
|
||||||
|
|
||||||
|
Here are some simple examples, and more detailed examples are available
|
||||||
|
in the subcommands or the documentation.
|
||||||
|
|
||||||
|
Create a CA
|
||||||
|
|
||||||
|
$ consul tls ca create
|
||||||
|
|
||||||
|
Create a server certificate
|
||||||
|
|
||||||
|
$ consul tls cert create -server
|
||||||
|
|
||||||
|
Create a client certificate
|
||||||
|
|
||||||
|
$ consul tls cert create -client
|
||||||
|
|
||||||
|
For more examples, ask for subcommand help or view the documentation.
|
||||||
|
`
|
|
@ -0,0 +1,13 @@
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidateCommand_noTabs(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
if strings.ContainsRune(New().Help(), '\t') {
|
||||||
|
t.Fatal("help has tabs")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
---
|
||||||
|
layout: "docs"
|
||||||
|
page_title: "Commands: TLS"
|
||||||
|
sidebar_current: "docs-commands-tls"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Consul TLS
|
||||||
|
|
||||||
|
Command: `consul tls`
|
||||||
|
|
||||||
|
The `tls` command is used to help with setting up a CA and certificates for Consul TLS.
|
||||||
|
|
||||||
|
## Basic Examples
|
||||||
|
|
||||||
|
Create a CA:
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ consul tls ca create
|
||||||
|
==> Saved consul-agent-ca.pem
|
||||||
|
==> Saved consul-agent-ca-key.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a client certificate:
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ consul tls cert create -client
|
||||||
|
==> Using consul-agent-ca.pem and consul-agent-ca-key.pem
|
||||||
|
==> Saved consul-client-dc1-0.pem
|
||||||
|
==> Saved consul-client-dc1-0-key.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
For more examples, ask for subcommand help or view the subcommand documentation
|
||||||
|
by clicking on one of the links in the sidebar.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Usage: `consul tls <subcommand> <subcommand> [options]`
|
||||||
|
|
||||||
|
For the exact documentation for your Consul version, run `consul tls -h` to
|
||||||
|
view the complete list of subcommands.
|
||||||
|
|
||||||
|
```text
|
||||||
|
Usage: consul tls <subcommand> <subcommand> [options]
|
||||||
|
|
||||||
|
# ...
|
||||||
|
|
||||||
|
Subcommands:
|
||||||
|
ca Helpers for CAs
|
||||||
|
cert Helpers for certificates
|
||||||
|
```
|
||||||
|
|
||||||
|
For more information, examples, and usage about a subcommand, click on the name
|
||||||
|
of the subcommand in the sidebar or one of the links below:
|
|
@ -0,0 +1,28 @@
|
||||||
|
---
|
||||||
|
layout: "docs"
|
||||||
|
page_title: "Commands: TLS CA Create"
|
||||||
|
sidebar_current: "docs-commands-tls-ca"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Consul TLS CA Create
|
||||||
|
|
||||||
|
Command: `consul tls ca create`
|
||||||
|
|
||||||
|
This command create a self signed CA to be used for Consul TLS setup.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
Create CA:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ consul tls ca create
|
||||||
|
==> Saved consul-ca.pem
|
||||||
|
==> Saved consul-ca-key.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
Usage: `consul tls ca create [filename-prefix] [options]`
|
||||||
|
|
||||||
|
#### TLS CA Create Options
|
||||||
|
|
||||||
|
- `-days=<int>` - Provide number of days the CA is valid for from now on, defaults to 5 years.
|
|
@ -0,0 +1,68 @@
|
||||||
|
---
|
||||||
|
layout: "docs"
|
||||||
|
page_title: "Commands: TLS Cert Create"
|
||||||
|
sidebar_current: "docs-commands-tls-cert"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Consul TLS Cert Create
|
||||||
|
|
||||||
|
Command: `consul tls cert create`
|
||||||
|
|
||||||
|
The `tls cert create` command is used to create certificates for your Consul TLS
|
||||||
|
setup.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Create a certificate for servers:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ consul tls cert create -server
|
||||||
|
==> WARNING: Server Certificates grants authority to become a
|
||||||
|
server and access all state in the cluster including root keys
|
||||||
|
and all ACL tokens. Do not distribute them to production hosts
|
||||||
|
that are not server nodes. Store them as securely as CA keys.
|
||||||
|
==> Using consul-ca.pem and consul-ca-key.pem
|
||||||
|
==> Saved consul-server-dc1-0.pem
|
||||||
|
==> Saved consul-server-dc1-0-key.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a certificate for clients:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ consul tls cert create -client
|
||||||
|
==> Using consul-ca.pem and consul-ca-key.pem
|
||||||
|
==> Saved consul-client-0.pem
|
||||||
|
==> Saved consul-client-0-key.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a certificate for cli:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ consul tls cert create -cli
|
||||||
|
==> Using consul-ca.pem and consul-ca-key.pem
|
||||||
|
==> Saved consul-cli-0.pem
|
||||||
|
==> Saved consul-cli-0-key.pem
|
||||||
|
```
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Usage: `consul tls cert create [filename-prefix] [options]`
|
||||||
|
|
||||||
|
#### TLS Cert Create Options
|
||||||
|
|
||||||
|
- `-additional-dnsname=<string>` - Provide additional dnsname for Subject Alternative Names.
|
||||||
|
|
||||||
|
- `-ca=<string>` - Provide path to the ca
|
||||||
|
|
||||||
|
- `-cli` - Generate cli certificate
|
||||||
|
|
||||||
|
- `-client` - Generate client certificate
|
||||||
|
|
||||||
|
- `-days=<int>` - Provide number of days the certificate is valid for from now on.
|
||||||
|
|
||||||
|
- `-dc=<string>` - Provide the datacenter. Matters only for -server certificates
|
||||||
|
|
||||||
|
- `-domain=<string>` - Provide the domain. Matters only for -server certificates
|
||||||
|
|
||||||
|
- `-key=<string>` - Provide path to the key
|
||||||
|
|
||||||
|
- `-server` - Generate server certificate
|
|
@ -1,20 +1,53 @@
|
||||||
---
|
---
|
||||||
layout: "docs"
|
layout: "docs"
|
||||||
page_title: "Creating Certificates"
|
page_title: "Creating and Configuring TLS Certificates"
|
||||||
sidebar_current: "docs-guides-creating-certificates"
|
sidebar_current: "docs-guides-creating-certificates"
|
||||||
description: |-
|
description: |-
|
||||||
Learn how to create certificates for Consul.
|
Learn how to create certificates for Consul.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Creating Certificates
|
# Creating and Configuring TLS Certificates
|
||||||
|
|
||||||
Correctly configuring TLS can be a complex process, especially given the wide
|
Setting you cluster up with TLS is an important step towards a secure
|
||||||
range of deployment methodologies. This guide will provide you with a
|
deployment. Correct TLS configuration is a prerequisite of our [Security
|
||||||
production ready TLS configuration.
|
Model](/docs/internals/security.html). Correctly configuring TLS can be a
|
||||||
|
complex process however, especially given the wide range of deployment
|
||||||
|
methodologies. This guide will provide you with a production ready TLS
|
||||||
|
configuration.
|
||||||
|
|
||||||
~> Note that while Consul's TLS configuration will be production ready, key
|
~> More advanced topics like key management and rotation are not covered by this
|
||||||
management and rotation is a complex subject not covered by this guide.
|
guide. [Vault][vault] is the suggested solution for key generation and
|
||||||
[Vault][vault] is the suggested solution for key generation and management.
|
management.
|
||||||
|
|
||||||
|
This guide has the following chapters:
|
||||||
|
|
||||||
|
1. [Creating Certificates](#creating-certificates)
|
||||||
|
1. [Configuring Agents](#configuring-agents)
|
||||||
|
1. [Configuring the Consul CLI for HTTPS](#configuring-the-consul-cli-for-https)
|
||||||
|
1. [Configuring the Consul UI for HTTPS](#configuring-the-consul-ui-for-https)
|
||||||
|
|
||||||
|
This guide is structured in way that you build knowledge with every step. It is
|
||||||
|
recommended to read the whole guide before starting with the actual work,
|
||||||
|
because you can save time if you are aware of some of the more advanced things
|
||||||
|
in Chapter [3](#configuring-the-consul-cli-for-https) and
|
||||||
|
[4](#configuring-the-consul-ui-for-https).
|
||||||
|
|
||||||
|
### Reference Material
|
||||||
|
|
||||||
|
- [Encryption](/docs/agent/encryption.html)
|
||||||
|
- [Security Model](/docs/internals/security.html)
|
||||||
|
|
||||||
|
## Creating Certificates
|
||||||
|
|
||||||
|
### Estimated Time to Complete
|
||||||
|
|
||||||
|
2 minutes
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
This guide assumes you have Consul 1.4.1 (or newer) in your PATH.
|
||||||
|
|
||||||
|
### Introduction
|
||||||
|
|
||||||
The first step to configuring TLS for Consul is generating certificates. In
|
The first step to configuring TLS for Consul is generating certificates. In
|
||||||
order to prevent unauthorized cluster access, Consul requires all certificates
|
order to prevent unauthorized cluster access, Consul requires all certificates
|
||||||
|
@ -22,162 +55,213 @@ be signed by the same Certificate Authority (CA). This should be a _private_ CA
|
||||||
and not a public one like [Let's Encrypt][letsencrypt] as any certificate
|
and not a public one like [Let's Encrypt][letsencrypt] as any certificate
|
||||||
signed by this CA will be allowed to communicate with the cluster.
|
signed by this CA will be allowed to communicate with the cluster.
|
||||||
|
|
||||||
~> Consul certificates may be signed by intermediate CAs as long as the root CA
|
### Step 1: Create a Certificate Authority
|
||||||
is the same. Append all intermediate CAs to the `cert_file`.
|
|
||||||
|
|
||||||
|
|
||||||
## Reference Material
|
|
||||||
|
|
||||||
- [Encryption](/docs/agent/encryption.html)
|
|
||||||
|
|
||||||
## Estimated Time to Complete
|
|
||||||
|
|
||||||
20 minutes
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
This guide assumes you have [cfssl][cfssl] installed (be sure to install
|
|
||||||
cfssljson as well).
|
|
||||||
|
|
||||||
## Steps
|
|
||||||
|
|
||||||
### Step 1: Create Certificate Authority
|
|
||||||
|
|
||||||
There are a variety of tools for managing your own CA, [like the PKI secret
|
There are a variety of tools for managing your own CA, [like the PKI secret
|
||||||
backend in Vault][vault-pki], but for the sake of simplicity this guide will
|
backend in Vault][vault-pki], but for the sake of simplicity this guide will
|
||||||
use [cfssl][cfssl]. You can generate a private CA certificate and key with
|
use Consul's builtin TLS helpers:
|
||||||
[cfssl][cfssl]:
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# Generate a default CSR
|
$ consul tls ca create
|
||||||
$ cfssl print-defaults csr > ca-csr.json
|
==> Saved consul-agent-ca.pem
|
||||||
|
==> Saved consul-agent-ca-key.pem
|
||||||
```
|
```
|
||||||
Change the `key` field to use RSA with a size of 2048
|
|
||||||
|
The CA certificate (`consul-agent-ca.pem`) contains the public key necessary to
|
||||||
|
validate Consul certificates and therefore must be distributed to every node
|
||||||
|
that runs a consul agent.
|
||||||
|
|
||||||
|
~> The CA key (`consul-agent-ca-key.pem`) will be used to sign certificates for Consul
|
||||||
|
nodes and must be kept private. Possession of this key allows anyone to run Consul as
|
||||||
|
a trusted server and access all Consul data including ACL tokens.
|
||||||
|
|
||||||
|
|
||||||
|
### Step 2: Create individual Server Certificates
|
||||||
|
|
||||||
|
Create a server certificate for datacenter `dc1` and domain `consul`, if your
|
||||||
|
datacenter or domain is different please use the appropriate flags:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ consul tls cert create -server
|
||||||
|
==> WARNING: Server Certificates grants authority to become a
|
||||||
|
server and access all state in the cluster including root keys
|
||||||
|
and all ACL tokens. Do not distribute them to production hosts
|
||||||
|
that are not server nodes. Store them as securely as CA keys.
|
||||||
|
==> Using consul-agent-ca.pem and consul-agent-ca-key.pem
|
||||||
|
==> Saved consul-server-dc1-0.pem
|
||||||
|
==> Saved consul-server-dc1-0-key.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
Please repeat this process until there is an *individual* certificate for each
|
||||||
|
server. The command can be called over and over again, it will automatically add
|
||||||
|
a suffix.
|
||||||
|
|
||||||
|
In order to authenticate Consul servers, servers are provided with a special
|
||||||
|
certificate - one that contains `server.dc1.consul` in the `Subject Alternative
|
||||||
|
Name`. If you enable
|
||||||
|
[`verify_server_hostname`](/docs/agent/options.html#verify_server_hostname),
|
||||||
|
only agents that provide such certificate are allowed to boot as a server.
|
||||||
|
Without `verify_server_hostname = true` an attacker could compromise a Consul
|
||||||
|
client agent and restart the agent as a server in order to get access to all the
|
||||||
|
data in your cluster! This is why server certificates are special, and only
|
||||||
|
servers should have them provisioned.
|
||||||
|
|
||||||
|
~> Server keys, like the CA key, must be kept private - they effectively allow
|
||||||
|
access to all Consul data.
|
||||||
|
|
||||||
|
### Step 3: Create Client Certificates
|
||||||
|
|
||||||
|
Create a client certificate:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ consul tls cert create -client
|
||||||
|
==> Using consul-agent-ca.pem and consul-agent-ca-key.pem
|
||||||
|
==> Saved consul-client-dc1-0.pem
|
||||||
|
==> Saved consul-client-dc1-0-key.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
Client certificates are also signed by your CA, but they do not have that
|
||||||
|
special `Subject Alternative Name` which means that if `verify_server_hostname`
|
||||||
|
is enabled, they cannot start as a server.
|
||||||
|
|
||||||
|
## Configuring Agents
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
For this section you need access to your existing or new Consul cluster and have
|
||||||
|
the certificates from the previous chapters available.
|
||||||
|
|
||||||
|
### Notes on example configurations
|
||||||
|
|
||||||
|
The example configurations from this as well as the following chapters are in
|
||||||
|
json. You can copy each one of the examples in its own file in a directory
|
||||||
|
([`-config-dir`](/docs/agent/options.html#_config_dir)) from where consul will
|
||||||
|
load all the configuration. This is just one way to do it, you can also put them
|
||||||
|
all into one file if you prefer that.
|
||||||
|
|
||||||
|
### Introduction
|
||||||
|
|
||||||
|
By now you have created the certificates you need to enable TLS in your cluster.
|
||||||
|
The next steps show how to configure TLS for a brand new cluster. If you already
|
||||||
|
have a cluster in production without TLS please see the [encryption
|
||||||
|
guide][guide] for the steps needed to introduce TLS without downtime.
|
||||||
|
|
||||||
|
### Step 1: Setup Consul servers with certificates
|
||||||
|
|
||||||
|
This step describes how to setup one of your consul servers, you want to make
|
||||||
|
sure to repeat the process for the other ones as well with their individual
|
||||||
|
certificates.
|
||||||
|
|
||||||
|
The following files need to be copied to your Consul server:
|
||||||
|
|
||||||
|
* `consul-agent-ca.pem`: CA public certificate.
|
||||||
|
* `consul-server-dc1-0.pem`: Consul server node public certificate for the `dc1` datacenter.
|
||||||
|
* `consul-server-dc1-0-key.pem`: Consul server node private key for the `dc1` datacenter.
|
||||||
|
|
||||||
|
Here is an example agent TLS configuration for Consul servers which mentions the
|
||||||
|
copied files:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"CN": "example.net",
|
"verify_incoming": true,
|
||||||
"hosts": [
|
"verify_outgoing": true,
|
||||||
"example.net",
|
"verify_server_hostname": true,
|
||||||
"www.example.net"
|
"ca_file": "consul-agent-ca.pem",
|
||||||
],
|
"cert_file": "consul-server-dc1-0.pem",
|
||||||
"key": {
|
"key_file": "consul-server-dc1-0-key.pem",
|
||||||
"algo": "rsa",
|
"ports": {
|
||||||
"size": 2048
|
"http": -1,
|
||||||
},
|
"https": 8501
|
||||||
"names": [
|
|
||||||
{
|
|
||||||
"C": "US",
|
|
||||||
"ST": "CA",
|
|
||||||
"L": "San Francisco"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```shell
|
|
||||||
# Generate the CA's private key and certificate
|
|
||||||
$ cfssl gencert -initca ca-csr.json | cfssljson -bare consul-ca
|
|
||||||
```
|
|
||||||
|
|
||||||
The CA key (`consul-ca-key.pem`) will be used to sign certificates for Consul
|
|
||||||
nodes and must be kept private. The CA certificate (`consul-ca.pem`) contains
|
|
||||||
the public key necessary to validate Consul certificates and therefore must be
|
|
||||||
distributed to every node that requires access.
|
|
||||||
|
|
||||||
### Step 2: Generate and Sign Node Certificates
|
|
||||||
|
|
||||||
Once you have a CA certificate and key you can generate and sign the
|
|
||||||
certificates Consul will use directly. TLS certificates commonly use the
|
|
||||||
fully-qualified domain name of the system being identified as the certificate's
|
|
||||||
Common Name (CN). However, hosts (and therefore hostnames and IPs) are often
|
|
||||||
ephemeral in Consul clusters. Not only would signing a new certificate per
|
|
||||||
Consul node be difficult, but using a hostname provides no security or
|
|
||||||
functional benefits to Consul. To fulfill the desired security properties
|
|
||||||
(above) Consul certificates are signed with their region and role such as:
|
|
||||||
|
|
||||||
* `client.global.consul` for a client node in the `global` region
|
|
||||||
* `server.us-west.consul` for a server node in the `us-west` region
|
|
||||||
|
|
||||||
To create certificates for the client and server in the cluster with
|
|
||||||
[cfssl][cfssl], create the following configuration file as `cfssl.json` to increase the default certificate expiration time:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"signing": {
|
|
||||||
"default": {
|
|
||||||
"expiry": "87600h",
|
|
||||||
"usages": [
|
|
||||||
"signing",
|
|
||||||
"key encipherment",
|
|
||||||
"server auth",
|
|
||||||
"client auth"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
```shell
|
This configuration disables the HTTP port to make sure there is only encryted
|
||||||
# Generate a certificate for the Consul server
|
communication. Existing clients that are not yet prepared to talk HTTPS won't be
|
||||||
$ echo '{"key":{"algo":"rsa","size":2048}}' | cfssl gencert -ca=consul-ca.pem -ca-key=consul-ca-key.pem -config=cfssl.json \
|
able to connect afterwards. This also affects builtin tooling like `consul
|
||||||
-hostname="server.global.consul,localhost,127.0.0.1" - | cfssljson -bare server
|
members` and the UI. The next chapters will demonstrate how to setup secure
|
||||||
|
access.
|
||||||
|
|
||||||
# Generate a certificate for the Consul client
|
After a Consul agent restart, your servers should be only talking TLS.
|
||||||
$ echo '{"key":{"algo":"rsa","size":2048}}' | cfssl gencert -ca=consul-ca.pem -ca-key=consul-ca-key.pem -config=cfssl.json \
|
|
||||||
-hostname="client.global.consul,localhost,127.0.0.1" - | cfssljson -bare client
|
|
||||||
|
|
||||||
# Generate a certificate for the CLI
|
### Step 2: Setup Consul clients with certificates
|
||||||
$ echo '{"key":{"algo":"rsa","size":2048}}' | cfssl gencert -ca=consul-ca.pem -ca-key=consul-ca-key.pem -profile=client \
|
|
||||||
- | cfssljson -bare cli
|
Now copy the following files to your Consul clients:
|
||||||
|
|
||||||
|
* `consul-agent-ca.pem`: CA public certificate.
|
||||||
|
* `consul-client-dc1-0.pem`: Consul client node public certificate.
|
||||||
|
* `consul-client-dc1-0-key.pem`: Consul client node private key.
|
||||||
|
|
||||||
|
Here is an example agent TLS configuration for Consul agents which mentions the
|
||||||
|
copied files:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"verify_incoming": true,
|
||||||
|
"verify_outgoing": true,
|
||||||
|
"verify_server_hostname": true,
|
||||||
|
"ca_file": "consul-agent-ca.pem",
|
||||||
|
"cert_file": "consul-client-dc1-0.pem",
|
||||||
|
"key_file": "consul-client-dc1-0-key.pem",
|
||||||
|
"ports": {
|
||||||
|
"http": -1,
|
||||||
|
"https": 8501
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Using `localhost` and `127.0.0.1` as subject alternate names (SANs) allows
|
This configuration disables the HTTP port to make sure there is only encryted
|
||||||
tools like `curl` to be able to communicate with Consul's HTTP API when run on
|
communication. Existing clients that are not yet prepared to talk HTTPS won't be
|
||||||
the same host. Other SANs may be added including a DNS resolvable hostname to
|
able to connect afterwards. This also affects builtin tooling like `consul
|
||||||
allow remote HTTP requests from third party tools.
|
members` and the UI. The next chapters will demonstrate how to setup secure
|
||||||
|
access.
|
||||||
|
|
||||||
You should now have the following files:
|
After a Consul agent restart, your agents should be only talking TLS.
|
||||||
|
|
||||||
* `cfssl.json` - cfssl configuration.
|
## Configuring the Consul CLI for HTTPS
|
||||||
* `consul-ca.csr` - CA signing request.
|
|
||||||
* `consul-ca-key.pem` - CA private key. Keep safe!
|
|
||||||
* `consul-ca.pem` - CA public certificate.
|
|
||||||
* `cli.csr` - Consul CLI certificate signing request.
|
|
||||||
* `cli-key.pem` - Consul CLI private key.
|
|
||||||
* `cli.pem` - Consul CLI certificate.
|
|
||||||
* `client.csr` - Consul client node certificate signing request for the `global` region.
|
|
||||||
* `client-key.pem` - Consul client node private key for the `global` region.
|
|
||||||
* `client.pem` - Consul client node public certificate for the `global` region.
|
|
||||||
* `server.csr` - Consul server node certificate signing request for the `global` region.
|
|
||||||
* `server-key.pem` - Consul server node private key for the `global` region.
|
|
||||||
* `server.pem` - Consul server node public certificate for the `global` region.
|
|
||||||
|
|
||||||
Each Consul node should have the appropriate key (`-key.pem`) and certificate
|
If your cluster is configured to only communicate via HTTPS, you will need to
|
||||||
(`.pem`) file for its region and role. In addition each node needs the CA's
|
create additional certificates in order to be able to continue to access the API
|
||||||
public certificate (`consul-ca.pem`).
|
and the UI:
|
||||||
|
|
||||||
Please note you will need the keys for the CLI if you choose to disable
|
|
||||||
HTTP (in which case running the command `consul members` will return an error).
|
|
||||||
This is because the Consul CLI defaults to communicating via HTTP instead of
|
|
||||||
HTTPS. We can configure the local Consul client to connect using TLS and specify
|
|
||||||
our custom keys and certificates using the command line:
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ consul members -ca-file=consul-ca.pem -client-cert=cli.pem -client-key=cli-key.pem -http-addr="https://localhost:9090"
|
$ consul tls cert create -cli
|
||||||
|
==> Using consul-agent-ca.pem and consul-agent-ca-key.pem
|
||||||
|
==> Saved consul-cli-dc1-0.pem
|
||||||
|
==> Saved consul-cli-dc1-0-key.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
If you are trying to get members of you cluster, the CLI will return an error:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ consul members
|
||||||
|
Error retrieving members:
|
||||||
|
Get http://127.0.0.1:8500/v1/agent/members?segment=_all:
|
||||||
|
dial tcp 127.0.0.1:8500: connect: connection refused
|
||||||
|
$ consul members -http-addr="https://localhost:8501"
|
||||||
|
Error retrieving members:
|
||||||
|
Get https://localhost:8501/v1/agent/members?segment=_all:
|
||||||
|
x509: certificate signed by unknown authority
|
||||||
|
```
|
||||||
|
|
||||||
|
But it will work again if you provide the certificates you provided:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ consul members -ca-file=consul-agent-ca.pem -client-cert=consul-cli-dc1-0.pem \
|
||||||
|
-client-key=consul-cli-dc1-0-key.pem -http-addr="https://localhost:8501"
|
||||||
|
Node Address Status Type Build Protocol DC Segment
|
||||||
|
...
|
||||||
```
|
```
|
||||||
(The command is assuming HTTPS is configured to use port 9090. To see how
|
|
||||||
you can change this, visit the [Configuration](/docs/agent/options.html) page)
|
|
||||||
|
|
||||||
This process can be cumbersome to type each time, so the Consul CLI also
|
This process can be cumbersome to type each time, so the Consul CLI also
|
||||||
searches environment variables for default values. Set the following
|
searches environment variables for default values. Set the following
|
||||||
environment variables in your shell:
|
environment variables in your shell:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ export CONSUL_HTTP_ADDR=https://localhost:9090
|
$ export CONSUL_HTTP_ADDR=https://localhost:8501
|
||||||
$ export CONSUL_CACERT=consul-ca.pem
|
$ export CONSUL_CACERT=consul-agent-ca.pem
|
||||||
$ export CONSUL_CLIENT_CERT=cli.pem
|
$ export CONSUL_CLIENT_CERT=consul-cli-dc1-0.pem
|
||||||
$ export CONSUL_CLIENT_KEY=cli-key.pem
|
$ export CONSUL_CLIENT_KEY=consul-cli-dc1-0-key.pem
|
||||||
```
|
```
|
||||||
|
|
||||||
* `CONSUL_HTTP_ADDR` is the URL of the Consul agent and sets the default for
|
* `CONSUL_HTTP_ADDR` is the URL of the Consul agent and sets the default for
|
||||||
|
@ -192,7 +276,159 @@ $ export CONSUL_CLIENT_KEY=cli-key.pem
|
||||||
After these environment variables are correctly configured, the CLI will
|
After these environment variables are correctly configured, the CLI will
|
||||||
respond as expected.
|
respond as expected.
|
||||||
|
|
||||||
[cfssl]: https://cfssl.org/
|
### Note on SANs for Server and Client Certificates
|
||||||
|
|
||||||
|
Using `localhost` and `127.0.0.1` as `Subject Alternative Names` in server
|
||||||
|
and client certificates allows tools like `curl` to be able to communicate with
|
||||||
|
Consul's HTTPS API when run on the same host. Other SANs may be added during
|
||||||
|
server/client certificates creation with `-additional-dnsname` to allow remote
|
||||||
|
HTTPS requests from other hosts.
|
||||||
|
|
||||||
|
## Configuring the Consul UI for HTTPS
|
||||||
|
|
||||||
|
If your servers and clients are configured now like above, you won't be able to
|
||||||
|
access the builtin UI anymore. We recommend that you pick one (or two for
|
||||||
|
availability) Consul agent you want to run the UI on and follow the instructions
|
||||||
|
to get the UI up and running again.
|
||||||
|
|
||||||
|
### Step 1: Which interface to bind to?
|
||||||
|
|
||||||
|
Depending on your setup you might need to change to which interface you are
|
||||||
|
binding because thats `127.0.0.1` by default for the UI. Either via the
|
||||||
|
[`addresses.https`](/docs/agent/options.html#https) or
|
||||||
|
[client_addr](/docs/agent/options.html#client_addr) option which also impacts
|
||||||
|
the DNS server. The Consul UI is unproteced which means you need to put some
|
||||||
|
auth in front of it if you want to make it publicly available!
|
||||||
|
|
||||||
|
Binding to `0.0.0.0` should work:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ui": true,
|
||||||
|
"client_addr": "0.0.0.0",
|
||||||
|
"enable_script_checks": false,
|
||||||
|
"disable_remote_exec": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
~> Since your Consul agent is now available to the network, please make sure
|
||||||
|
that [`enable_script_checks`](/docs/agent/options.html#_enable_script_checks) is
|
||||||
|
set to `false` and
|
||||||
|
[`disable_remote_exec`](https://www.consul.io/docs/agent/options.html#disable_remote_exec)
|
||||||
|
is set to `true`.
|
||||||
|
|
||||||
|
### Step 2: verify_incoming_rpc
|
||||||
|
|
||||||
|
Your Consul agent will deny the connection straight away because
|
||||||
|
`verify_incoming` is enabled.
|
||||||
|
|
||||||
|
> If set to true, Consul requires that all incoming connections make use of TLS
|
||||||
|
> and that the client provides a certificate signed by a Certificate Authority
|
||||||
|
> from the ca_file or ca_path. This applies to both server RPC and to the HTTPS
|
||||||
|
> API.
|
||||||
|
|
||||||
|
Since the browser doesn't present a certificate signed by our CA, you cannot
|
||||||
|
access the UI. If you `curl` your HTTPS UI the following happens:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ curl https://localhost:8501/ui/ -k -I
|
||||||
|
curl: (35) error:14094412:SSL routines:SSL3_READ_BYTES:sslv3 alert bad certificate
|
||||||
|
```
|
||||||
|
|
||||||
|
This is the Consul HTTPS server denying your connection because you are not
|
||||||
|
presenting a client certificate signed by your Consul CA. There is a combination
|
||||||
|
of options however that allows us to keep using `verify_incoming` for RPC, but
|
||||||
|
not for HTTPS:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"verify_incoming": false,
|
||||||
|
"verify_incoming_rpc": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
~> This is the only time we are changing the value of the existing option
|
||||||
|
`verify_incoming` to false. Make sure to only change it on the agent running the
|
||||||
|
UI!
|
||||||
|
|
||||||
|
With the new configuration, it should work:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ curl https://localhost:8501/ui/ -k -I
|
||||||
|
HTTP/2 200
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Subject Alternative Name
|
||||||
|
|
||||||
|
This step will take care of setting up the domain you want to use to access the
|
||||||
|
Consul UI. Unless you only need to access the UI over localhost or 127.0.0.1 you
|
||||||
|
will need to go complete this step.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ curl https://consul.example.com:8501/ui/ \
|
||||||
|
--resolve 'consul.example.com:8501:127.0.0.1' \
|
||||||
|
--cacert consul-agent-ca.pem
|
||||||
|
curl: (51) SSL: no alternative certificate subject name matches target host name 'consul.example.com'
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
The above command simulates a request a browser is making when you are trying to
|
||||||
|
use the domain `consul.example.com` to access your UI. The problem this time is
|
||||||
|
that your domain is not in `Subject Alternative Name` of the Certificate. We can
|
||||||
|
fix that by creating a certificate that has our domain:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ consul tls cert create -server -additional-dnsname consul.example.com
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
And if you put your new cert into the configuration of the agent you picked to
|
||||||
|
serve the UI and restart Consul, it works now:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ curl https://consul.example.com:8501/ui/ \
|
||||||
|
--resolve 'consul.example.com:8501:127.0.0.1' \
|
||||||
|
--cacert consul-agent-ca.pem -I
|
||||||
|
HTTP/2 200
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Trust the Consul CA
|
||||||
|
|
||||||
|
So far we have provided curl with our CA so that it can verify the connection,
|
||||||
|
but if we stop doing that it will complain and so will our browser if you visit
|
||||||
|
your UI on https://consul.example.com:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ curl https://consul.example.com:8501/ui/ \
|
||||||
|
--resolve 'consul.example.com:8501:127.0.0.1'
|
||||||
|
curl: (60) SSL certificate problem: unable to get local issuer certificate
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
You can fix that by trusting your Consul CA (`consul-agent-ca.pem`) on your machine,
|
||||||
|
please use Google to find out how to do that on your OS.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ curl https://consul.example.com:8501/ui/ \
|
||||||
|
--resolve 'consul.example.com:8501:127.0.0.1' -I
|
||||||
|
HTTP/2 200
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
When you have completed this guide, your Consul cluster will have TLS enabled
|
||||||
|
and will encrypt all RPC and HTTP traffic (assuming you disabled the HTTP port).
|
||||||
|
The other pre-requisites for a secure Consul deployment are:
|
||||||
|
|
||||||
|
* [Enable gossip encryption](/docs/agent/encryption.html#gossip-encryption)
|
||||||
|
* [Configure ACLs][acl] with default deny
|
||||||
|
|
||||||
[letsencrypt]: https://letsencrypt.org/
|
[letsencrypt]: https://letsencrypt.org/
|
||||||
[vault]: https://www.vaultproject.io/
|
[vault]: https://www.vaultproject.io/
|
||||||
[vault-pki]: https://www.vaultproject.io/docs/secrets/pki/index.html
|
[vault-pki]: https://www.vaultproject.io/docs/secrets/pki/index.html
|
||||||
|
[guide]: /docs/agent/encryption.html#configuring-tls-on-an-existing-cluster
|
||||||
|
[acl]: /docs/guides/acl.html
|
||||||
|
|
||||||
|
|
|
@ -241,6 +241,18 @@
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-commands-tls") %>>
|
||||||
|
<a href="/docs/commands/tls.html">tls</a>
|
||||||
|
<ul class="nav">
|
||||||
|
<li<%= sidebar_current("docs-commands-tls-ca") %>>
|
||||||
|
<a href="/docs/commands/tls/ca.html">ca</a>
|
||||||
|
</li>
|
||||||
|
<li<%= sidebar_current("docs-commands-tls-cert") %>>
|
||||||
|
<a href="/docs/commands/tls/cert.html">cert</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-commands-validate") %>>
|
<li<%= sidebar_current("docs-commands-validate") %>>
|
||||||
<a href="/docs/commands/validate.html">validate</a>
|
<a href="/docs/commands/validate.html">validate</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -409,7 +421,7 @@
|
||||||
<a href="/docs/guides/consul-aws.html">Consul-AWS</a>
|
<a href="/docs/guides/consul-aws.html">Consul-AWS</a>
|
||||||
</li>
|
</li>
|
||||||
<li<%= sidebar_current("docs-guides-creating-certificates") %>>
|
<li<%= sidebar_current("docs-guides-creating-certificates") %>>
|
||||||
<a href="/docs/guides/creating-certificates.html">Creating Certificates</a>
|
<a href="/docs/guides/creating-certificates.html">Creating TLS Certificates</a>
|
||||||
</li>
|
</li>
|
||||||
<li<%= sidebar_current("docs-guides-deployment-guide") %>>
|
<li<%= sidebar_current("docs-guides-deployment-guide") %>>
|
||||||
<a href="/docs/guides/deployment-guide.html">Deployment Guide</a>
|
<a href="/docs/guides/deployment-guide.html">Deployment Guide</a>
|
||||||
|
|
Loading…
Reference in New Issue