mirror of https://github.com/status-im/consul.git
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
44280103ee
commit
87f32c8ba6
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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"`
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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": "",
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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).
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue