Builtin tls helper (#5078)

* command: add tls subcommand
* website: update docs and guide
This commit is contained in:
Hans Hasselberg 2018-12-19 09:22:49 +01:00 committed by GitHub
parent 564288adcc
commit acc458d7a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1448 additions and 138 deletions

View File

@ -70,6 +70,11 @@ import (
snapinspect "github.com/hashicorp/consul/command/snapshot/inspect"
snaprestore "github.com/hashicorp/consul/command/snapshot/restore"
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/version"
"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 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("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("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 })

View File

@ -5,7 +5,7 @@ import (
"testing"
)
func TestCatalogCommand_noTabs(t *testing.T) {
func TestConnectCommand_noTabs(t *testing.T) {
t.Parallel()
if strings.ContainsRune(New().Help(), '\t') {
t.Fatal("help has tabs")

View File

@ -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
`

View File

@ -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")
}
}

42
command/tls/ca/tls_ca.go Normal file
View File

@ -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.
`

View File

@ -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")
}
}

View File

@ -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
`

View File

@ -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")
}
}

View File

@ -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.
`

View File

@ -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")
}
}

216
command/tls/generate.go Normal file
View File

@ -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
}

View File

@ -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]))
}

57
command/tls/tls.go Normal file
View File

@ -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.
`

13
command/tls/tls_test.go Normal file
View File

@ -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")
}
}

View File

@ -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:

View File

@ -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.

View File

@ -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

View File

@ -1,20 +1,53 @@
---
layout: "docs"
page_title: "Creating Certificates"
page_title: "Creating and Configuring TLS Certificates"
sidebar_current: "docs-guides-creating-certificates"
description: |-
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
range of deployment methodologies. This guide will provide you with a
production ready TLS configuration.
Setting you cluster up with TLS is an important step towards a secure
deployment. Correct TLS configuration is a prerequisite of our [Security
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
management and rotation is a complex subject not covered by this guide.
[Vault][vault] is the suggested solution for key generation and management.
~> More advanced topics like key management and rotation are not covered by this
guide. [Vault][vault] is the suggested solution for key generation and
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
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
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
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
### Step 1: Create a Certificate Authority
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
use [cfssl][cfssl]. You can generate a private CA certificate and key with
[cfssl][cfssl]:
use Consul's builtin TLS helpers:
```shell
# Generate a default CSR
$ cfssl print-defaults csr > ca-csr.json
$ consul tls ca create
==> 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
{
"CN": "example.net",
"hosts": [
"example.net",
"www.example.net"
],
"key": {
"algo": "rsa",
"size": 2048
},
"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"
]
}
"verify_incoming": true,
"verify_outgoing": true,
"verify_server_hostname": true,
"ca_file": "consul-agent-ca.pem",
"cert_file": "consul-server-dc1-0.pem",
"key_file": "consul-server-dc1-0-key.pem",
"ports": {
"http": -1,
"https": 8501
}
}
```
```shell
# Generate a certificate for the Consul server
$ echo '{"key":{"algo":"rsa","size":2048}}' | cfssl gencert -ca=consul-ca.pem -ca-key=consul-ca-key.pem -config=cfssl.json \
-hostname="server.global.consul,localhost,127.0.0.1" - | cfssljson -bare server
This configuration disables the HTTP port to make sure there is only encryted
communication. Existing clients that are not yet prepared to talk HTTPS won't be
able to connect afterwards. This also affects builtin tooling like `consul
members` and the UI. The next chapters will demonstrate how to setup secure
access.
# Generate a certificate for the Consul client
$ 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
After a Consul agent restart, your servers should be only talking TLS.
# Generate a certificate for the CLI
$ echo '{"key":{"algo":"rsa","size":2048}}' | cfssl gencert -ca=consul-ca.pem -ca-key=consul-ca-key.pem -profile=client \
- | cfssljson -bare cli
### Step 2: Setup Consul clients with certificates
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
tools like `curl` to be able to communicate with Consul's HTTP API when run on
the same host. Other SANs may be added including a DNS resolvable hostname to
allow remote HTTP requests from third party tools.
This configuration disables the HTTP port to make sure there is only encryted
communication. Existing clients that are not yet prepared to talk HTTPS won't be
able to connect afterwards. This also affects builtin tooling like `consul
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.
* `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.
## Configuring the Consul CLI for HTTPS
Each Consul node should have the appropriate key (`-key.pem`) and certificate
(`.pem`) file for its region and role. In addition each node needs the CA's
public certificate (`consul-ca.pem`).
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:
If your cluster is configured to only communicate via HTTPS, you will need to
create additional certificates in order to be able to continue to access the API
and the UI:
```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
searches environment variables for default values. Set the following
environment variables in your shell:
```shell
$ export CONSUL_HTTP_ADDR=https://localhost:9090
$ export CONSUL_CACERT=consul-ca.pem
$ export CONSUL_CLIENT_CERT=cli.pem
$ export CONSUL_CLIENT_KEY=cli-key.pem
$ export CONSUL_HTTP_ADDR=https://localhost:8501
$ export CONSUL_CACERT=consul-agent-ca.pem
$ export CONSUL_CLIENT_CERT=consul-cli-dc1-0.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
@ -192,7 +276,159 @@ $ export CONSUL_CLIENT_KEY=cli-key.pem
After these environment variables are correctly configured, the CLI will
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/
[vault]: https://www.vaultproject.io/
[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

View File

@ -241,6 +241,18 @@
</ul>
</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") %>>
<a href="/docs/commands/validate.html">validate</a>
</li>
@ -409,7 +421,7 @@
<a href="/docs/guides/consul-aws.html">Consul-AWS</a>
</li>
<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<%= sidebar_current("docs-guides-deployment-guide") %>>
<a href="/docs/guides/deployment-guide.html">Deployment Guide</a>