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:
parent
3de3259833
commit
b6c83e06d5
|
@ -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
|
||||
|
|
|
@ -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 = 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")
|
||||
}
|
||||
|
@ -545,7 +550,7 @@ func (c *ConnectCALeaf) generateNewLeaf(req *ConnectCALeafRequest,
|
|||
}
|
||||
|
||||
// Create a CSR.
|
||||
csr, err := connect.CreateCSR(id, commonName, pk)
|
||||
csr, err := connect.CreateCSR(id, commonName, pk, dnsNames, ipAddresses)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
@ -636,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
|
||||
}
|
||||
|
|
|
@ -651,6 +651,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 {
|
||||
|
@ -856,6 +870,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,
|
||||
|
|
|
@ -568,6 +568,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"`
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -3758,6 +3758,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": {
|
||||
|
@ -4357,6 +4359,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 {
|
||||
|
@ -5065,6 +5069,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,
|
||||
|
@ -5907,6 +5913,8 @@ func TestSanitize(t *testing.T) {
|
|||
"ClientAddrs": [],
|
||||
"ConfigEntryBootstrap": [],
|
||||
"AutoEncryptTLS": false,
|
||||
"AutoEncryptDNSSAN": [],
|
||||
"AutoEncryptIPSAN": [],
|
||||
"AutoEncryptAllowTLS": false,
|
||||
"ConnectCAConfig": {},
|
||||
"ConnectCAProvider": "",
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
// CreateCSR returns a CSR to sign the given service with SAN entries
|
||||
// along with the PEM-encoded private key for this certificate.
|
||||
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{
|
||||
URIs: []*url.URL{uri.URI()},
|
||||
SignatureAlgorithm: SigAlgoForKey(privateKey),
|
||||
ExtraExtensions: extensions,
|
||||
Subject: pkix.Name{CommonName: commonName},
|
||||
DNSNames: dnsNames,
|
||||
IPAddresses: ipAddresses,
|
||||
}
|
||||
|
||||
// Create the CSR itself
|
||||
|
@ -75,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
|
||||
|
|
|
@ -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.CreateCSR(id, cn, pk, dnsNames, ipAddresses)
|
||||
if err != nil {
|
||||
return errFn(err)
|
||||
}
|
||||
|
|
|
@ -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.CreateCSR(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)
|
||||
|
|
|
@ -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="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
|
||||
[`-bootstrap` command-line flag](#_bootstrap).
|
||||
|
||||
|
|
Loading…
Reference in New Issue