Files
coredns/core/dnsserver/tlsconfig.go
Yong Tang 8d66643935 core: Reject conflicting TLS policies on shared listeners. (#8565)
* core: Reject conflicting TLS policies on shared listeners.

This PR tries to fix the issue when multiple zones share a DoT listener, CoreDNS can apply one sibling block’s tls.Config to all zones, allowing weaker TLS or client-auth settings to override a stricter zone.

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>

* Fix ACME

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>

---------

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
2026-09-21 23:56:27 -07:00

133 lines
3.7 KiB
Go

package dnsserver
import (
"bytes"
"crypto/tls"
"crypto/x509"
"fmt"
"slices"
)
// sharedTLSConfig returns the TLS configuration for a listener shared by group.
// TLS is negotiated before CoreDNS knows which zone will handle the DNS request,
// so listener-wide identity and client-authentication settings must agree.
func sharedTLSConfig(addr string, group []*Config) (*tls.Config, error) {
if len(group) == 0 {
return nil, nil
}
first := group[0]
if first == nil {
return nil, fmt.Errorf("nil config for shared listener %s", addr)
}
for _, conf := range group[1:] {
if conf == nil {
return nil, fmt.Errorf("nil config for shared listener %s", addr)
}
if err := compatibleTLSConfig(first, conf); err != nil {
return nil, fmt.Errorf("conflicting TLS configuration for shared listener %s between zones %q and %q: %w", addr, first.Zone, conf.Zone, err)
}
}
if first.TLSConfig == nil {
return nil, nil
}
return first.TLSConfig.Clone(), nil
}
func sameServerBlock(a, b *Config) bool {
aFirst := a.firstConfigInBlock
if aFirst == nil {
aFirst = a
}
bFirst := b.firstConfigInBlock
if bFirst == nil {
bFirst = b
}
return aFirst == bFirst
}
func compatibleTLSConfig(aConfig, bConfig *Config) error {
a := aConfig.TLSConfig
b := bConfig.TLSConfig
if a == nil || b == nil {
if a == b {
return nil
}
return fmt.Errorf("TLS is configured for only one server block")
}
if a.ClientAuth != b.ClientAuth {
return fmt.Errorf("client authentication policies differ")
}
if !certPoolsEqual(a.ClientCAs, b.ClientCAs) {
return fmt.Errorf("client CA pools differ")
}
if !certificatesEqual(a.Certificates, b.Certificates) {
return fmt.Errorf("server certificates differ")
}
// Dynamic callbacks cannot be compared directly. Allow them only when the
// configs came from the same server block, are the same config object, or a
// plugin supplied a trusted identity proving the policies are equivalent.
trustedDynamicPolicy := sameServerBlock(aConfig, bConfig) || a == b ||
(aConfig.tlsConfigIdentity != nil && aConfig.tlsConfigIdentity == bConfig.tlsConfigIdentity)
if !trustedDynamicPolicy && (a.GetCertificate != nil || b.GetCertificate != nil ||
a.GetConfigForClient != nil || b.GetConfigForClient != nil ||
a.VerifyPeerCertificate != nil || b.VerifyPeerCertificate != nil ||
a.VerifyConnection != nil || b.VerifyConnection != nil) {
return fmt.Errorf("dynamic TLS callbacks differ")
}
if a.MinVersion != b.MinVersion || a.MaxVersion != b.MaxVersion {
return fmt.Errorf("TLS version policies differ")
}
if !slices.Equal(a.CipherSuites, b.CipherSuites) {
return fmt.Errorf("cipher suite policies differ")
}
if !slices.Equal(a.CurvePreferences, b.CurvePreferences) {
return fmt.Errorf("curve preference policies differ")
}
if a.SessionTicketsDisabled != b.SessionTicketsDisabled {
return fmt.Errorf("session ticket policies differ")
}
return nil
}
func certPoolsEqual(a, b *x509.CertPool) bool {
if a == nil || b == nil {
return a == b
}
return a.Equal(b)
}
func certificatesEqual(a, b []tls.Certificate) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if len(a[i].Certificate) != len(b[i].Certificate) {
return false
}
for j := range a[i].Certificate {
if !bytes.Equal(a[i].Certificate[j], b[i].Certificate[j]) {
return false
}
}
if !bytes.Equal(a[i].OCSPStaple, b[i].OCSPStaple) {
return false
}
if len(a[i].SignedCertificateTimestamps) != len(b[i].SignedCertificateTimestamps) {
return false
}
for j := range a[i].SignedCertificateTimestamps {
if !bytes.Equal(a[i].SignedCertificateTimestamps[j], b[i].SignedCertificateTimestamps[j]) {
return false
}
}
}
return true
}