auto_encrypt: set dns and ip san for k8s and provide configuration (#6944)

* Add CreateCSRWithSAN
* Use CreateCSRWithSAN in auto_encrypt and cache
* Copy DNSNames and IPAddresses to cert
* Verify auto_encrypt.sign returns cert with SAN
* provide configuration options for auto_encrypt dnssan and ipsan
* rename CreateCSRWithSAN to CreateCSR
This commit is contained in:
Hans Hasselberg 2020-01-17 23:25:26 +01:00 committed by GitHub
parent 3de3259833
commit b6c83e06d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 74 additions and 7 deletions

View File

@ -592,6 +592,8 @@ func (a *Agent) setupClientAutoEncryptCache(reply *structs.SignedResponse) (*str
Datacenter: a.config.Datacenter, Datacenter: a.config.Datacenter,
Token: a.tokens.AgentToken(), Token: a.tokens.AgentToken(),
Agent: a.config.NodeName, Agent: a.config.NodeName,
DNSSAN: a.config.AutoEncryptDNSSAN,
IPSAN: a.config.AutoEncryptIPSAN,
} }
// prepolutate leaf cache // prepolutate leaf cache

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"net"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@ -508,6 +509,8 @@ func (c *ConnectCALeaf) generateNewLeaf(req *ConnectCALeafRequest,
// Build the cert uri // Build the cert uri
var id connect.CertURI var id connect.CertURI
var commonName string var commonName string
var dnsNames []string
var ipAddresses []net.IP
if req.Service != "" { if req.Service != "" {
id = &connect.SpiffeIDService{ id = &connect.SpiffeIDService{
Host: roots.TrustDomain, Host: roots.TrustDomain,
@ -523,6 +526,8 @@ func (c *ConnectCALeaf) generateNewLeaf(req *ConnectCALeafRequest,
Agent: req.Agent, Agent: req.Agent,
} }
commonName = connect.ServiceCN(req.Agent, roots.TrustDomain) commonName = connect.ServiceCN(req.Agent, roots.TrustDomain)
dnsNames = append([]string{"localhost"}, req.DNSSAN...)
ipAddresses = append([]net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::")}, req.IPSAN...)
} else { } else {
return result, errors.New("URI must be either service or agent") return result, errors.New("URI must be either service or agent")
} }
@ -545,7 +550,7 @@ func (c *ConnectCALeaf) generateNewLeaf(req *ConnectCALeafRequest,
} }
// Create a CSR. // Create a CSR.
csr, err := connect.CreateCSR(id, commonName, pk) csr, err := connect.CreateCSR(id, commonName, pk, dnsNames, ipAddresses)
if err != nil { if err != nil {
return result, err return result, err
} }
@ -636,6 +641,8 @@ type ConnectCALeafRequest struct {
Datacenter string Datacenter string
Service string // Service name, not ID Service string // Service name, not ID
Agent string // Agent name, not ID Agent string // Agent name, not ID
DNSSAN []string
IPSAN []net.IP
MinQueryIndex uint64 MinQueryIndex uint64
MaxQueryTime time.Duration MaxQueryTime time.Duration
} }

View File

@ -651,6 +651,20 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) {
} }
autoEncryptTLS := b.boolVal(c.AutoEncrypt.TLS) 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) autoEncryptAllowTLS := b.boolVal(c.AutoEncrypt.AllowTLS)
if autoEncryptAllowTLS { if autoEncryptAllowTLS {
@ -856,6 +870,8 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) {
ClientAddrs: clientAddrs, ClientAddrs: clientAddrs,
ConfigEntryBootstrap: configEntries, ConfigEntryBootstrap: configEntries,
AutoEncryptTLS: autoEncryptTLS, AutoEncryptTLS: autoEncryptTLS,
AutoEncryptDNSSAN: autoEncryptDNSSAN,
AutoEncryptIPSAN: autoEncryptIPSAN,
AutoEncryptAllowTLS: autoEncryptAllowTLS, AutoEncryptAllowTLS: autoEncryptAllowTLS,
ConnectEnabled: connectEnabled, ConnectEnabled: connectEnabled,
ConnectCAProvider: connectCAProvider, ConnectCAProvider: connectCAProvider,

View File

@ -568,6 +568,12 @@ type AutoEncrypt struct {
// TLS enables receiving certificates for clients from servers // TLS enables receiving certificates for clients from servers
TLS *bool `json:"tls,omitempty" hcl:"tls" mapstructure:"tls"` 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 // AllowTLS enables the RPC endpoint on the server to answer
// AutoEncrypt.Sign requests. // AutoEncrypt.Sign requests.
AllowTLS *bool `json:"allow_tls,omitempty" hcl:"allow_tls" mapstructure:"allow_tls"` AllowTLS *bool `json:"allow_tls,omitempty" hcl:"allow_tls" mapstructure:"allow_tls"`

View File

@ -529,6 +529,14 @@ type RuntimeConfig struct {
// servers. // servers.
AutoEncryptTLS bool 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 // AutoEncryptAllowTLS enables the server to respond to
// AutoEncrypt.Sign requests. // AutoEncrypt.Sign requests.
AutoEncryptAllowTLS bool AutoEncryptAllowTLS bool

View File

@ -3758,6 +3758,8 @@ func TestFullConfig(t *testing.T) {
}, },
"auto_encrypt": { "auto_encrypt": {
"tls": true, "tls": true,
"dns_san": ["a.com", "b.com"],
"ip_san": ["192.168.4.139", "192.168.4.140"],
"allow_tls": true "allow_tls": true
}, },
"connect": { "connect": {
@ -4357,6 +4359,8 @@ func TestFullConfig(t *testing.T) {
} }
auto_encrypt = { auto_encrypt = {
tls = true tls = true
dns_san = ["a.com", "b.com"]
ip_san = ["192.168.4.139", "192.168.4.140"]
allow_tls = true allow_tls = true
} }
connect { connect {
@ -5065,6 +5069,8 @@ func TestFullConfig(t *testing.T) {
}, },
}, },
AutoEncryptTLS: true, 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, AutoEncryptAllowTLS: true,
ConnectEnabled: true, ConnectEnabled: true,
ConnectSidecarMinPort: 8888, ConnectSidecarMinPort: 8888,
@ -5907,6 +5913,8 @@ func TestSanitize(t *testing.T) {
"ClientAddrs": [], "ClientAddrs": [],
"ConfigEntryBootstrap": [], "ConfigEntryBootstrap": [],
"AutoEncryptTLS": false, "AutoEncryptTLS": false,
"AutoEncryptDNSSAN": [],
"AutoEncryptIPSAN": [],
"AutoEncryptAllowTLS": false, "AutoEncryptAllowTLS": false,
"ConnectCAConfig": {}, "ConnectCAConfig": {},
"ConnectCAProvider": "", "ConnectCAProvider": "",

View File

@ -403,6 +403,8 @@ func (c *ConsulProvider) Sign(csr *x509.CertificateRequest) (string, error) {
NotBefore: effectiveNow, NotBefore: effectiveNow,
AuthorityKeyId: keyId, AuthorityKeyId: keyId,
SubjectKeyId: keyId, SubjectKeyId: keyId,
DNSNames: csr.DNSNames,
IPAddresses: csr.IPAddresses,
} }
// Create the certificate, PEM encode it and return that value. // Create the certificate, PEM encode it and return that value.

View File

@ -9,6 +9,7 @@ import (
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/asn1" "encoding/asn1"
"encoding/pem" "encoding/pem"
"net"
"net/url" "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 // CreateCSR returns a CSR to sign the given service with SAN entries
// private key for this certificate. // along with the PEM-encoded private key for this certificate.
func CreateCSR(uri CertURI, commonName string, privateKey crypto.Signer, func CreateCSR(uri CertURI, commonName string, privateKey crypto.Signer,
extensions ...pkix.Extension) (string, error) { dnsNames []string, ipAddresses []net.IP, extensions ...pkix.Extension) (string, error) {
template := &x509.CertificateRequest{ template := &x509.CertificateRequest{
URIs: []*url.URL{uri.URI()}, URIs: []*url.URL{uri.URI()},
SignatureAlgorithm: SigAlgoForKey(privateKey), SignatureAlgorithm: SigAlgoForKey(privateKey),
ExtraExtensions: extensions, ExtraExtensions: extensions,
Subject: pkix.Name{CommonName: commonName}, Subject: pkix.Name{CommonName: commonName},
DNSNames: dnsNames,
IPAddresses: ipAddresses,
} }
// Create the CSR itself // Create the CSR itself
@ -75,7 +78,7 @@ func CreateCACSR(uri CertURI, commonName string, privateKey crypto.Signer) (stri
return "", err 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 // CreateCAExtension creates a pkix.Extension for the x509 Basic Constraints

View File

@ -64,12 +64,15 @@ func (c *Client) RequestAutoEncryptCerts(servers []string, port int, token strin
return errFn(err) return errFn(err)
} }
dnsNames := []string{"localhost"}
ipAddresses := []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::")}
// Create a CSR. // Create a CSR.
// //
// The Common Name includes the dummy trust domain for now but Server will // The Common Name includes the dummy trust domain for now but Server will
// override this when it is signed anyway so it's OK. // override this when it is signed anyway so it's OK.
cn := connect.AgentCN(string(c.config.NodeName), dummyTrustDomain) cn := connect.AgentCN(string(c.config.NodeName), dummyTrustDomain)
csr, err := connect.CreateCSR(id, cn, pk) csr, err := connect.CreateCSR(id, cn, pk, dnsNames, ipAddresses)
if err != nil { if err != nil {
return errFn(err) return errFn(err)
} }

View File

@ -3,6 +3,7 @@ package consul
import ( import (
"crypto/x509" "crypto/x509"
"fmt" "fmt"
"net"
"os" "os"
"strings" "strings"
"testing" "testing"
@ -77,7 +78,9 @@ func TestAutoEncryptSign(t *testing.T) {
// Create a CSR. // Create a CSR.
cn, err := connect.CNForCertURI(id) cn, err := connect.CNForCertURI(id)
require.NoError(t, err) 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.CreateCSR(id, cn, pk, dnsNames, ipAddresses)
require.NoError(t, err, info) require.NoError(t, err, info)
require.NotEmpty(t, csr, info) require.NotEmpty(t, csr, info)
args := &structs.CASignRequest{ args := &structs.CASignRequest{
@ -119,6 +122,11 @@ func TestAutoEncryptSign(t *testing.T) {
}) })
require.NoError(t, err, info) 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 // Verify other fields
require.Equal(t, "uuid", reply.IssuedCert.Agent, info) require.Equal(t, "uuid", reply.IssuedCert.Agent, info)
require.Len(t, reply.ManualCARoots, 1, info) require.Len(t, reply.ManualCARoots, 1, info)

View File

@ -836,6 +836,10 @@ default will automatically work with some tooling.
* <a name="tls"></a><a href="#tls">`tls`</a> (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. * <a name="tls"></a><a href="#tls">`tls`</a> (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.
* <a name="dns_san"></a><a href="#dns_san">`dns_san`</a> (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.
* <a name="ip_san"></a><a href="#ip_san">`ip_san`</a> (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.
* <a name="bootstrap"></a><a href="#bootstrap">`bootstrap`</a> Equivalent to the * <a name="bootstrap"></a><a href="#bootstrap">`bootstrap`</a> Equivalent to the
[`-bootstrap` command-line flag](#_bootstrap). [`-bootstrap` command-line flag](#_bootstrap).