From 5b76dd19166826333169a091f450a76af94a0166 Mon Sep 17 00:00:00 2001 From: Hans Hasselberg Date: Fri, 13 Dec 2019 09:00:41 +0100 Subject: [PATCH 1/6] Add CreateCSRWithSAN --- agent/connect/csr.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/agent/connect/csr.go b/agent/connect/csr.go index cfe37b78ae63..e3d6b8e08a50 100644 --- a/agent/connect/csr.go +++ b/agent/connect/csr.go @@ -9,6 +9,7 @@ import ( "crypto/x509/pkix" "encoding/asn1" "encoding/pem" + "net" "net/url" ) @@ -41,15 +42,17 @@ func SigAlgoForKeyType(keyType string) x509.SignatureAlgorithm { } } -// CreateCSR returns a CSR to sign the given service along with the PEM-encoded -// private key for this certificate. -func CreateCSR(uri CertURI, commonName string, privateKey crypto.Signer, - extensions ...pkix.Extension) (string, error) { +// CreateCSRWithSAN returns a CSR to sign the given service with SAN entries +// along with the PEM-encoded private key for this certificate. +func CreateCSRWithSAN(uri CertURI, commonName string, privateKey crypto.Signer, + dnsNames []string, ipAddresses []net.IP, extensions ...pkix.Extension) (string, error) { template := &x509.CertificateRequest{ URIs: []*url.URL{uri.URI()}, SignatureAlgorithm: SigAlgoForKey(privateKey), ExtraExtensions: extensions, Subject: pkix.Name{CommonName: commonName}, + DNSNames: dnsNames, + IPAddresses: ipAddresses, } // Create the CSR itself @@ -67,6 +70,13 @@ func CreateCSR(uri CertURI, commonName string, privateKey crypto.Signer, return csrBuf.String(), nil } +// CreateCSR returns a CSR to sign the given service along with the PEM-encoded +// private key for this certificate. +func CreateCSR(uri CertURI, commonName string, privateKey crypto.Signer, + extensions ...pkix.Extension) (string, error) { + return CreateCSRWithSAN(uri, commonName, privateKey, nil, nil, extensions...) +} + // CreateCSR returns a CA CSR to sign the given service along with the PEM-encoded // private key for this certificate. func CreateCACSR(uri CertURI, commonName string, privateKey crypto.Signer) (string, error) { From 98e1af95d0e565942f07206bab17fd040b6d08ed Mon Sep 17 00:00:00 2001 From: Hans Hasselberg Date: Fri, 13 Dec 2019 09:21:06 +0100 Subject: [PATCH 2/6] Use CreateCSRWithSAN in auto_encrypt and cache --- agent/cache-types/connect_ca_leaf.go | 7 ++++++- agent/consul/auto_encrypt.go | 5 ++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/agent/cache-types/connect_ca_leaf.go b/agent/cache-types/connect_ca_leaf.go index 7c402ee2e191..e2394c8609b0 100644 --- a/agent/cache-types/connect_ca_leaf.go +++ b/agent/cache-types/connect_ca_leaf.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "net" "sync" "sync/atomic" "time" @@ -508,6 +509,8 @@ func (c *ConnectCALeaf) generateNewLeaf(req *ConnectCALeafRequest, // Build the cert uri var id connect.CertURI var commonName string + var dnsNames []string + var ipAddresses []net.IP if req.Service != "" { id = &connect.SpiffeIDService{ Host: roots.TrustDomain, @@ -523,6 +526,8 @@ func (c *ConnectCALeaf) generateNewLeaf(req *ConnectCALeafRequest, Agent: req.Agent, } commonName = connect.ServiceCN(req.Agent, roots.TrustDomain) + dnsNames = []string{"localhost"} + ipAddresses = []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::")} } else { return result, errors.New("URI must be either service or agent") } @@ -545,7 +550,7 @@ func (c *ConnectCALeaf) generateNewLeaf(req *ConnectCALeafRequest, } // Create a CSR. - csr, err := connect.CreateCSR(id, commonName, pk) + csr, err := connect.CreateCSRWithSAN(id, commonName, pk, dnsNames, ipAddresses) if err != nil { return result, err } diff --git a/agent/consul/auto_encrypt.go b/agent/consul/auto_encrypt.go index 183aed5ddcbe..a462f9411e94 100644 --- a/agent/consul/auto_encrypt.go +++ b/agent/consul/auto_encrypt.go @@ -64,12 +64,15 @@ func (c *Client) RequestAutoEncryptCerts(servers []string, port int, token strin return errFn(err) } + dnsNames := []string{"localhost"} + ipAddresses := []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::")} + // Create a CSR. // // The Common Name includes the dummy trust domain for now but Server will // override this when it is signed anyway so it's OK. cn := connect.AgentCN(string(c.config.NodeName), dummyTrustDomain) - csr, err := connect.CreateCSR(id, cn, pk) + csr, err := connect.CreateCSRWithSAN(id, cn, pk, dnsNames, ipAddresses) if err != nil { return errFn(err) } From 25fa8b90e1e509908438ea6129122480b033733c Mon Sep 17 00:00:00 2001 From: Hans Hasselberg Date: Fri, 13 Dec 2019 09:28:49 +0100 Subject: [PATCH 3/6] Copy DNSNames and IPAddresses to cert --- agent/connect/ca/provider_consul.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/agent/connect/ca/provider_consul.go b/agent/connect/ca/provider_consul.go index 89879238658a..d95ff6819d01 100644 --- a/agent/connect/ca/provider_consul.go +++ b/agent/connect/ca/provider_consul.go @@ -403,6 +403,8 @@ func (c *ConsulProvider) Sign(csr *x509.CertificateRequest) (string, error) { NotBefore: effectiveNow, AuthorityKeyId: keyId, SubjectKeyId: keyId, + DNSNames: csr.DNSNames, + IPAddresses: csr.IPAddresses, } // Create the certificate, PEM encode it and return that value. From f14e0cff65486e825d66b6779ee48fc742f3970f Mon Sep 17 00:00:00 2001 From: Hans Hasselberg Date: Fri, 13 Dec 2019 10:09:18 +0100 Subject: [PATCH 4/6] Verify auto_encrypt.sign returns cert with SAN --- agent/consul/auto_encrypt_endpoint_test.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/agent/consul/auto_encrypt_endpoint_test.go b/agent/consul/auto_encrypt_endpoint_test.go index 93bf8f2ec2ed..69fb2e3f3fbb 100644 --- a/agent/consul/auto_encrypt_endpoint_test.go +++ b/agent/consul/auto_encrypt_endpoint_test.go @@ -3,6 +3,7 @@ package consul import ( "crypto/x509" "fmt" + "net" "os" "strings" "testing" @@ -77,7 +78,9 @@ func TestAutoEncryptSign(t *testing.T) { // Create a CSR. cn, err := connect.CNForCertURI(id) require.NoError(t, err) - csr, err := connect.CreateCSR(id, cn, pk) + dnsNames := []string{"localhost"} + ipAddresses := []net.IP{net.ParseIP("127.0.0.1")} + csr, err := connect.CreateCSRWithSAN(id, cn, pk, dnsNames, ipAddresses) require.NoError(t, err, info) require.NotEmpty(t, csr, info) args := &structs.CASignRequest{ @@ -119,6 +122,11 @@ func TestAutoEncryptSign(t *testing.T) { }) require.NoError(t, err, info) + // Verify SANs + require.Equal(t, dnsNames, leaf.DNSNames) + require.Len(t, leaf.IPAddresses, 1) + require.True(t, ipAddresses[0].Equal(leaf.IPAddresses[0])) + // Verify other fields require.Equal(t, "uuid", reply.IssuedCert.Agent, info) require.Len(t, reply.ManualCARoots, 1, info) From 6e2f4f99417599c0b98399a0d98c96d3e58f7f39 Mon Sep 17 00:00:00 2001 From: Hans Hasselberg Date: Thu, 16 Jan 2020 11:44:13 +0100 Subject: [PATCH 5/6] provide configuration options for auto_encrypt dnssan and ipsan --- agent/agent.go | 2 ++ agent/cache-types/connect_ca_leaf.go | 6 ++++-- agent/config/builder.go | 16 ++++++++++++++++ agent/config/config.go | 6 ++++++ agent/config/runtime.go | 8 ++++++++ agent/config/runtime_test.go | 8 ++++++++ website/source/docs/agent/options.html.md | 4 ++++ 7 files changed, 48 insertions(+), 2 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 2a1acaa1d0eb..8360d6b8ccc0 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -592,6 +592,8 @@ func (a *Agent) setupClientAutoEncryptCache(reply *structs.SignedResponse) (*str Datacenter: a.config.Datacenter, Token: a.tokens.AgentToken(), Agent: a.config.NodeName, + DNSSAN: a.config.AutoEncryptDNSSAN, + IPSAN: a.config.AutoEncryptIPSAN, } // prepolutate leaf cache diff --git a/agent/cache-types/connect_ca_leaf.go b/agent/cache-types/connect_ca_leaf.go index e2394c8609b0..454d302e7f91 100644 --- a/agent/cache-types/connect_ca_leaf.go +++ b/agent/cache-types/connect_ca_leaf.go @@ -526,8 +526,8 @@ func (c *ConnectCALeaf) generateNewLeaf(req *ConnectCALeafRequest, Agent: req.Agent, } commonName = connect.ServiceCN(req.Agent, roots.TrustDomain) - dnsNames = []string{"localhost"} - ipAddresses = []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::")} + dnsNames = append([]string{"localhost"}, req.DNSSAN...) + ipAddresses = append([]net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::")}, req.IPSAN...) } else { return result, errors.New("URI must be either service or agent") } @@ -641,6 +641,8 @@ type ConnectCALeafRequest struct { Datacenter string Service string // Service name, not ID Agent string // Agent name, not ID + DNSSAN []string + IPSAN []net.IP MinQueryIndex uint64 MaxQueryTime time.Duration } diff --git a/agent/config/builder.go b/agent/config/builder.go index a674d8b0134c..270a9d60fca6 100644 --- a/agent/config/builder.go +++ b/agent/config/builder.go @@ -604,6 +604,20 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) { } autoEncryptTLS := b.boolVal(c.AutoEncrypt.TLS) + autoEncryptDNSSAN := []string{} + for _, d := range c.AutoEncrypt.DNSSAN { + autoEncryptDNSSAN = append(autoEncryptDNSSAN, d) + } + autoEncryptIPSAN := []net.IP{} + for _, i := range c.AutoEncrypt.IPSAN { + ip := net.ParseIP(i) + if ip == nil { + b.warn(fmt.Sprintf("Cannot parse ip %q from AutoEncrypt.IPSAN", i)) + continue + } + autoEncryptIPSAN = append(autoEncryptIPSAN, ip) + + } autoEncryptAllowTLS := b.boolVal(c.AutoEncrypt.AllowTLS) if autoEncryptAllowTLS { @@ -809,6 +823,8 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) { ClientAddrs: clientAddrs, ConfigEntryBootstrap: configEntries, AutoEncryptTLS: autoEncryptTLS, + AutoEncryptDNSSAN: autoEncryptDNSSAN, + AutoEncryptIPSAN: autoEncryptIPSAN, AutoEncryptAllowTLS: autoEncryptAllowTLS, ConnectEnabled: connectEnabled, ConnectCAProvider: connectCAProvider, diff --git a/agent/config/config.go b/agent/config/config.go index 6af8439e0580..ea935f8cfe61 100644 --- a/agent/config/config.go +++ b/agent/config/config.go @@ -562,6 +562,12 @@ type AutoEncrypt struct { // TLS enables receiving certificates for clients from servers TLS *bool `json:"tls,omitempty" hcl:"tls" mapstructure:"tls"` + // Additional DNS SAN entries that clients request for their certificates. + DNSSAN []string `json:"dns_san,omitempty" hcl:"dns_san" mapstructure:"dns_san"` + + // Additional IP SAN entries that clients request for their certificates. + IPSAN []string `json:"ip_san,omitempty" hcl:"ip_san" mapstructure:"ip_san"` + // AllowTLS enables the RPC endpoint on the server to answer // AutoEncrypt.Sign requests. AllowTLS *bool `json:"allow_tls,omitempty" hcl:"allow_tls" mapstructure:"allow_tls"` diff --git a/agent/config/runtime.go b/agent/config/runtime.go index 02429ab5829d..3eeb3d774b77 100644 --- a/agent/config/runtime.go +++ b/agent/config/runtime.go @@ -529,6 +529,14 @@ type RuntimeConfig struct { // servers. AutoEncryptTLS bool + // Additional DNS SAN entries that clients request during auto_encrypt + // flow for their certificates. + AutoEncryptDNSSAN []string + + // Additional IP SAN entries that clients request during auto_encrypt + // flow for their certificates. + AutoEncryptIPSAN []net.IP + // AutoEncryptAllowTLS enables the server to respond to // AutoEncrypt.Sign requests. AutoEncryptAllowTLS bool diff --git a/agent/config/runtime_test.go b/agent/config/runtime_test.go index 3d23076705f9..e33c9a1ecd3e 100644 --- a/agent/config/runtime_test.go +++ b/agent/config/runtime_test.go @@ -3727,6 +3727,8 @@ func TestFullConfig(t *testing.T) { }, "auto_encrypt": { "tls": true, + "dns_san": ["a.com", "b.com"], + "ip_san": ["192.168.4.139", "192.168.4.140"], "allow_tls": true }, "connect": { @@ -4324,6 +4326,8 @@ func TestFullConfig(t *testing.T) { } auto_encrypt = { tls = true + dns_san = ["a.com", "b.com"] + ip_san = ["192.168.4.139", "192.168.4.140"] allow_tls = true } connect { @@ -5030,6 +5034,8 @@ func TestFullConfig(t *testing.T) { }, }, AutoEncryptTLS: true, + AutoEncryptDNSSAN: []string{"a.com", "b.com"}, + AutoEncryptIPSAN: []net.IP{net.ParseIP("192.168.4.139"), net.ParseIP("192.168.4.140")}, AutoEncryptAllowTLS: true, ConnectEnabled: true, ConnectSidecarMinPort: 8888, @@ -5868,6 +5874,8 @@ func TestSanitize(t *testing.T) { "ClientAddrs": [], "ConfigEntryBootstrap": [], "AutoEncryptTLS": false, + "AutoEncryptDNSSAN": [], + "AutoEncryptIPSAN": [], "AutoEncryptAllowTLS": false, "ConnectCAConfig": {}, "ConnectCAProvider": "", diff --git a/website/source/docs/agent/options.html.md b/website/source/docs/agent/options.html.md index 064f946135c5..063a588b3cb9 100644 --- a/website/source/docs/agent/options.html.md +++ b/website/source/docs/agent/options.html.md @@ -826,6 +826,10 @@ default will automatically work with some tooling. * `tls` (Defaults to `false`) Allows the client to request the Connect CA and certificates from the servers, for encrypting RPC communication. The client will make the request to any servers listed in the `-join` or `-retry-join` option. This requires that every server to have `auto_encrypt.allow_tls` enabled. When both `auto_encrypt` options are used, it allows clients to receive certificates that are generated on the servers. If the `-server-port` is not the default one, it has to be provided to the client as well. Usually this is discovered through LAN gossip, but `auto_encrypt` provision happens before the information can be distributed through gossip. The most secure `auto_encrypt` setup is when the client is provided with the built-in CA, `verify_server_hostname` is turned on, and when an ACL token with `node.write` permissions is setup. It is also possible to use `auto_encrypt` with a CA and ACL, but without `verify_server_hostname`, or only with a ACL enabled, or only with CA and `verify_server_hostname`, or only with a CA, or finally without a CA and without ACL enabled. In any case, the communication to the `auto_encrypt` endpoint is always TLS encrypted. + * `dns_san` (Defaults to `[]`) When this option is being used, the certificates requested by `auto_encrypt` from the server have these `dns_san` set as DNS SAN. + + * `ip_san` (Defaults to `[]`) When this option is being used, the certificates requested by `auto_encrypt` from the server have these `ip_san` set as IP SAN. + * `bootstrap` Equivalent to the [`-bootstrap` command-line flag](#_bootstrap). From ffda63db8db4c565db44fd7ac1fda2a29e1f7902 Mon Sep 17 00:00:00 2001 From: Hans Hasselberg Date: Fri, 17 Jan 2020 22:12:16 +0100 Subject: [PATCH 6/6] rename CreateCSRWithSAN to CreateCSR --- agent/cache-types/connect_ca_leaf.go | 2 +- agent/connect/csr.go | 13 +++---------- agent/consul/auto_encrypt.go | 2 +- agent/consul/auto_encrypt_endpoint_test.go | 2 +- 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/agent/cache-types/connect_ca_leaf.go b/agent/cache-types/connect_ca_leaf.go index 454d302e7f91..e5fb8c891e75 100644 --- a/agent/cache-types/connect_ca_leaf.go +++ b/agent/cache-types/connect_ca_leaf.go @@ -550,7 +550,7 @@ func (c *ConnectCALeaf) generateNewLeaf(req *ConnectCALeafRequest, } // Create a CSR. - csr, err := connect.CreateCSRWithSAN(id, commonName, pk, dnsNames, ipAddresses) + csr, err := connect.CreateCSR(id, commonName, pk, dnsNames, ipAddresses) if err != nil { return result, err } diff --git a/agent/connect/csr.go b/agent/connect/csr.go index e3d6b8e08a50..4ba0f1a9fc7a 100644 --- a/agent/connect/csr.go +++ b/agent/connect/csr.go @@ -42,9 +42,9 @@ func SigAlgoForKeyType(keyType string) x509.SignatureAlgorithm { } } -// CreateCSRWithSAN returns a CSR to sign the given service with SAN entries +// CreateCSR returns a CSR to sign the given service with SAN entries // along with the PEM-encoded private key for this certificate. -func CreateCSRWithSAN(uri CertURI, commonName string, privateKey crypto.Signer, +func CreateCSR(uri CertURI, commonName string, privateKey crypto.Signer, dnsNames []string, ipAddresses []net.IP, extensions ...pkix.Extension) (string, error) { template := &x509.CertificateRequest{ URIs: []*url.URL{uri.URI()}, @@ -70,13 +70,6 @@ func CreateCSRWithSAN(uri CertURI, commonName string, privateKey crypto.Signer, return csrBuf.String(), nil } -// CreateCSR returns a CSR to sign the given service along with the PEM-encoded -// private key for this certificate. -func CreateCSR(uri CertURI, commonName string, privateKey crypto.Signer, - extensions ...pkix.Extension) (string, error) { - return CreateCSRWithSAN(uri, commonName, privateKey, nil, nil, extensions...) -} - // CreateCSR returns a CA CSR to sign the given service along with the PEM-encoded // private key for this certificate. func CreateCACSR(uri CertURI, commonName string, privateKey crypto.Signer) (string, error) { @@ -85,7 +78,7 @@ func CreateCACSR(uri CertURI, commonName string, privateKey crypto.Signer) (stri return "", err } - return CreateCSR(uri, commonName, privateKey, ext) + return CreateCSR(uri, commonName, privateKey, nil, nil, ext) } // CreateCAExtension creates a pkix.Extension for the x509 Basic Constraints diff --git a/agent/consul/auto_encrypt.go b/agent/consul/auto_encrypt.go index a462f9411e94..fd0a3611f246 100644 --- a/agent/consul/auto_encrypt.go +++ b/agent/consul/auto_encrypt.go @@ -72,7 +72,7 @@ func (c *Client) RequestAutoEncryptCerts(servers []string, port int, token strin // The Common Name includes the dummy trust domain for now but Server will // override this when it is signed anyway so it's OK. cn := connect.AgentCN(string(c.config.NodeName), dummyTrustDomain) - csr, err := connect.CreateCSRWithSAN(id, cn, pk, dnsNames, ipAddresses) + csr, err := connect.CreateCSR(id, cn, pk, dnsNames, ipAddresses) if err != nil { return errFn(err) } diff --git a/agent/consul/auto_encrypt_endpoint_test.go b/agent/consul/auto_encrypt_endpoint_test.go index 69fb2e3f3fbb..b9098b21c5ff 100644 --- a/agent/consul/auto_encrypt_endpoint_test.go +++ b/agent/consul/auto_encrypt_endpoint_test.go @@ -80,7 +80,7 @@ func TestAutoEncryptSign(t *testing.T) { require.NoError(t, err) dnsNames := []string{"localhost"} ipAddresses := []net.IP{net.ParseIP("127.0.0.1")} - csr, err := connect.CreateCSRWithSAN(id, cn, pk, dnsNames, ipAddresses) + csr, err := connect.CreateCSR(id, cn, pk, dnsNames, ipAddresses) require.NoError(t, err, info) require.NotEmpty(t, csr, info) args := &structs.CASignRequest{