Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add flags to support CA generation for Connect #9585

Merged
merged 7 commits into from
Jan 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/9585.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
cli: Add new `-cluster-id` and `common-name` to `consul tls ca create` to support creating a CA for Consul Connect.
```
16 changes: 4 additions & 12 deletions agent/consul/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ package consul

import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"fmt"
"net"
Expand Down Expand Up @@ -46,22 +43,17 @@ const (
// testTLSCertificates Generates a TLS CA and server key/cert and returns them
// in PEM encoded form.
func testTLSCertificates(serverName string) (cert string, key string, cacert string, err error) {
// generate CA
serial, err := tlsutil.GenerateSerialNumber()
if err != nil {
return "", "", "", err
}
signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
signer, _, err := tlsutil.GeneratePrivateKey()
if err != nil {
return "", "", "", err
}
ca, err := tlsutil.GenerateCA(signer, serial, 365, nil)

ca, _, err := tlsutil.GenerateCA(tlsutil.CAOpts{Signer: signer})
if err != nil {
return "", "", "", err
}

// generate leaf
serial, err = tlsutil.GenerateSerialNumber()
serial, err := tlsutil.GenerateSerialNumber()
if err != nil {
return "", "", "", err
}
Expand Down
15 changes: 4 additions & 11 deletions agent/pool/peek_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package pool

import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"errors"
Expand Down Expand Up @@ -194,22 +191,18 @@ func deadlineNetPipe(deadline time.Time) (net.Conn, net.Conn, error) {
}

func generateTestCert(serverName string) (cert tls.Certificate, caPEM []byte, err error) {
// generate CA
serial, err := tlsutil.GenerateSerialNumber()
if err != nil {
return tls.Certificate{}, nil, err
}
signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
signer, _, err := tlsutil.GeneratePrivateKey()
if err != nil {
return tls.Certificate{}, nil, err
}
ca, err := tlsutil.GenerateCA(signer, serial, 365, nil)

ca, _, err := tlsutil.GenerateCA(tlsutil.CAOpts{Signer: signer})
if err != nil {
return tls.Certificate{}, nil, err
}

// generate leaf
serial, err = tlsutil.GenerateSerialNumber()
serial, err := tlsutil.GenerateSerialNumber()
if err != nil {
return tls.Certificate{}, nil, err
}
Expand Down
17 changes: 5 additions & 12 deletions agent/routine-leak-checker/leak_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package leakcheck

import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"io/ioutil"
"path/filepath"
Expand All @@ -18,22 +15,18 @@ import (
)

func testTLSCertificates(serverName string) (cert string, key string, cacert string, err error) {
// generate CA
serial, err := tlsutil.GenerateSerialNumber()
if err != nil {
return "", "", "", err
}
signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
ca, _, err := tlsutil.GenerateCA(tlsutil.CAOpts{})
if err != nil {
return "", "", "", err
}
ca, err := tlsutil.GenerateCA(signer, serial, 365, nil)

// generate leaf
serial, err := tlsutil.GenerateSerialNumber()
if err != nil {
return "", "", "", err
}

// generate leaf
serial, err = tlsutil.GenerateSerialNumber()
signer, _, err := tlsutil.GeneratePrivateKey()
if err != nil {
return "", "", "", err
}
Expand Down
15 changes: 4 additions & 11 deletions agent/testagent.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package agent
import (
"bytes"
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/x509"
"fmt"
"io"
Expand Down Expand Up @@ -569,22 +567,17 @@ func TestACLConfigWithParams(params *TestACLConfigParams) string {
// testTLSCertificates Generates a TLS CA and server key/cert and returns them
// in PEM encoded form.
func testTLSCertificates(serverName string) (cert string, key string, cacert string, err error) {
// generate CA
serial, err := tlsutil.GenerateSerialNumber()
if err != nil {
return "", "", "", err
}
signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.New(rand.NewSource(99)))
signer, _, err := tlsutil.GeneratePrivateKey()
if err != nil {
return "", "", "", err
}
ca, err := tlsutil.GenerateCA(signer, serial, 365, nil)

ca, _, err := tlsutil.GenerateCA(tlsutil.CAOpts{Signer: signer})
if err != nil {
return "", "", "", err
}

// generate leaf
serial, err = tlsutil.GenerateSerialNumber()
serial, err := tlsutil.GenerateSerialNumber()
if err != nil {
return "", "", "", err
}
Expand Down
17 changes: 5 additions & 12 deletions command/tls/ca/create/tls_ca_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ type cmd struct {
help string
days int
domain string
clusterID string
constraint bool
commonName string
additionalConstraints flags.AppendSliceValue
}

Expand All @@ -36,6 +38,8 @@ func (c *cmd) init() {
"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.StringVar(&c.clusterID, "cluster-id", "", "ClusterID of the consul cluster, requires -domain to be set as well. When used will set URIs with spiffeid.")
c.flags.StringVar(&c.commonName, "common-name", "", "Common Name of CA. Defaults to Consul Agent CA.")
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)
Expand All @@ -62,23 +66,12 @@ func (c *cmd) Run(args []string) int {
return 1
}

sn, err := tlsutil.GenerateSerialNumber()
if err != nil {
c.UI.Error(err.Error())
return 1
}
s, pk, err := tlsutil.GeneratePrivateKey()
if err != nil {
c.UI.Error(err.Error())
return 1
}

constraints := []string{}
if c.constraint {
constraints = append(c.additionalConstraints, []string{c.domain, "localhost"}...)
}

ca, err := tlsutil.GenerateCA(s, sn, c.days, constraints)
ca, pk, err := tlsutil.GenerateCA(tlsutil.CAOpts{Name: c.commonName, Days: c.days, Domain: c.domain, PermittedDNSDomains: constraints, ClusterID: c.clusterID})
if err != nil {
c.UI.Error(err.Error())
return 1
Expand Down
35 changes: 33 additions & 2 deletions command/tls/ca/create/tls_ca_create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,22 +62,53 @@ func TestCACreateCommand(t *testing.T) {
require.ElementsMatch(t, cert.PermittedDNSDomains, []string{"foo", "localhost", "bar"})
},
},
{"with cluster-id",
[]string{
"-domain=foo",
"-cluster-id=uuid",
},
"foo-agent-ca.pem",
"foo-agent-ca-key.pem",
func(t *testing.T, cert *x509.Certificate) {
require.Len(t, cert.URIs, 1)
require.Equal(t, cert.URIs[0].String(), "spiffe://uuid.foo")
},
},
{"with common-name",
[]string{
"-common-name=foo",
},
"consul-agent-ca.pem",
"consul-agent-ca-key.pem",
func(t *testing.T, cert *x509.Certificate) {
require.Equal(t, cert.Subject.CommonName, "foo")
},
},
{"without common-name",
[]string{},
"consul-agent-ca.pem",
"consul-agent-ca-key.pem",
func(t *testing.T, cert *x509.Certificate) {
require.True(t, strings.HasPrefix(cert.Subject.CommonName, "Consul Agent CA"))
},
},
}
for _, tc := range cases {
tc := tc
require.True(t, t.Run(tc.name, func(t *testing.T) {
ui := cli.NewMockUi()
cmd := New(ui)
require.Equal(t, 0, cmd.Run(tc.args))
require.Equal(t, 0, cmd.Run(tc.args), ui.ErrorWriter.String())
require.Equal(t, "", ui.ErrorWriter.String())

cert, _ := expectFiles(t, tc.caPath, tc.keyPath)
require.Contains(t, cert.Subject.CommonName, "Consul Agent CA")
require.True(t, cert.BasicConstraintsValid)
require.Equal(t, x509.KeyUsageCertSign|x509.KeyUsageCRLSign|x509.KeyUsageDigitalSignature, cert.KeyUsage)
require.True(t, cert.IsCA)
require.Equal(t, cert.AuthorityKeyId, cert.SubjectKeyId)
tc.extraCheck(t, cert)
require.NoError(t, os.Remove(tc.caPath))
require.NoError(t, os.Remove(tc.keyPath))
}))
}

Expand Down
60 changes: 52 additions & 8 deletions tlsutil/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"time"

"github.com/hashicorp/consul/agent/connect"
"net/url"
)

// GenerateSerialNumber returns random bigint generated with crypto/rand
Expand All @@ -32,18 +33,61 @@ func GeneratePrivateKey() (crypto.Signer, string, error) {
return connect.GeneratePrivateKey()
}

type CAOpts struct {
Signer crypto.Signer
Serial *big.Int
ClusterID string
Days int
PermittedDNSDomains []string
Domain string
Name string
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New struct to make it less confusing when passing options to GenerateCA.


// 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) {
func GenerateCA(opts CAOpts) (string, string, error) {
signer := opts.Signer
var pk string
if signer == nil {
var err error
signer, pk, err = GeneratePrivateKey()
if err != nil {
return "", "", err
}
}

id, err := keyID(signer.Public())
if err != nil {
return "", err
return "", "", err
}

name := fmt.Sprintf("Consul Agent CA %d", sn)
sn := opts.Serial
if sn == nil {
var err error
sn, err = GenerateSerialNumber()
if err != nil {
return "", "", err
}
}
name := opts.Name
if name == "" {
name = fmt.Sprintf("Consul Agent CA %d", sn)
}

days := opts.Days
if opts.Days == 0 {
days = 365
}

var uris []*url.URL
if opts.ClusterID != "" {
spiffeID := connect.SpiffeIDSigning{ClusterID: opts.ClusterID, Domain: opts.Domain}
uris = []*url.URL{spiffeID.URI()}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bunch of defaults, that are being used instead of the null values of CAOpts


// Create the CA cert
template := x509.Certificate{
SerialNumber: sn,
URIs: uris,
Subject: pkix.Name{
Country: []string{"US"},
PostalCode: []string{"94105"},
Expand All @@ -62,23 +106,23 @@ func GenerateCA(signer crypto.Signer, sn *big.Int, days int, constraints []strin
SubjectKeyId: id,
}

if len(constraints) > 0 {
if len(opts.PermittedDNSDomains) > 0 {
template.PermittedDNSDomainsCritical = true
template.PermittedDNSDomains = constraints
template.PermittedDNSDomains = opts.PermittedDNSDomains
}
bs, err := x509.CreateCertificate(
rand.Reader, &template, &template, signer.Public(), signer)
if err != nil {
return "", fmt.Errorf("error generating CA certificate: %s", err)
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 "", "", fmt.Errorf("error encoding private key: %s", err)
}

return buf.String(), nil
return buf.String(), pk, nil
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also return the encoded private key, since it no longer mandatory to pass in.

}

// GenerateCert generates a new certificate for agent TLS (not to be confused with Connect TLS)
Expand Down
Loading