make sure client CA and auth type are set if CA is explicitly specified. (#2825)

* make sure client CA and auth type are set if CA is explicitly specified.

added some simple tests to confirm the effect.

* test certificates (forgot to add them in the previous commit)

* made client auth policy configurable with new client_auth option.

README has been updated accordingly.

* fix editorial in README
This commit is contained in:
JINMEI Tatuya 2019-05-31 09:30:15 -07:00 committed by John Belamaric
parent 5565ca1c03
commit a6d9adbf4a
6 changed files with 160 additions and 1 deletions

View file

@ -24,6 +24,16 @@ tls CERT KEY [CA]
Parameter CA is optional. If not set, system CAs can be used to verify the client certificate Parameter CA is optional. If not set, system CAs can be used to verify the client certificate
~~~ txt
tls CERT KEY [CA] {
client_auth nocert|request|require|verify_if_given|require_and_verify
}
~~~
If client_auth option is specified, it controls the client authentication policy.
The option value corresponds to the [ClientAuthType values of the Go tls package](https://golang.org/pkg/crypto/tls/#ClientAuthType): NoClientCert, RequestClientCert, RequireAnyClientCert, VerifyClientCertIfGiven, and RequireAndVerifyClientCert, respectively.
The default is "nocert". Note that it makes no sense to specify parameter CA unless this option is set to verify_if_given or require_and_verify.
## Examples ## Examples
Start a DNS-over-TLS server that picks up incoming DNS-over-TLS queries on port 5553 and uses the Start a DNS-over-TLS server that picks up incoming DNS-over-TLS queries on port 5553 and uses the

20
plugin/tls/test_ca.pem Normal file
View file

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDPzCCAiegAwIBAgIJAPjCWTu1wGapMA0GCSqGSIb3DQEBCwUAMDUxCzAJBgNV
BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMREwDwYDVQQKDAhJbmZvYmxveDAg
Fw0xOTA1MTEwMDI3NDRaGA8yMTE5MDQxNzAwMjc0NFowNTELMAkGA1UEBhMCVVMx
EzARBgNVBAgMCkNhbGlmb3JuaWExETAPBgNVBAoMCEluZm9ibG94MIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArAYiw1UjlYj+nITRUlj5hA7j8U2qWcyN
YcDfqQnt173Z8yR7NJokqt3Bd3PlrBZS2XtYSNohxRr4qeJu/g7UBre/fSEU/ZOM
Gl7NjBGKQEymJ0d8rBg52iiGNwU+ERI9pcQRA6DCEjVbOmjDiUd5yzuVotG/Sxep
GUJ2puJ0p0gWCMEL9sdqY6HHd/hdj6B6+u2xD9NUCkX9pLC7CPFJHnP0vLO4WIWL
z5C7yzpeLO9r7Nfnu+2HcRLmuFZVPNxkMq7UymqR1w5ZYJQ5p9E7pyxDVXxHnTqQ
yLaAS2/9umrOwVnD1NaN3OdAhDedXbH0cF08GcIQD9rnlkLMW4CKtwIDAQABo1Aw
TjAdBgNVHQ4EFgQUHcxJPBmHF0nSv+FJJI/kwrSThf8wHwYDVR0jBBgwFoAUHcxJ
PBmHF0nSv+FJJI/kwrSThf8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC
AQEAByItgyhlXDv2wnnMVXHHlUCbsKCOtBJZ8EumvKjeOx5G4gqJpQIQPNeBv1Od
QT7d15HfT7RQqHSL0uAoGuNuyGjZGWWbLMkVt8T0tXY2v9Dd8eWC/lFaaA0vkqTG
GpADSmH+SoFAdPPcYN/sXmEHvZcIQ0wUxuF48ZMwOh7ZOcrZggxlA9+BKHU4fO03
o7krzpQZQmEDXNN8bt1R0DIhVADw/G2oJAzK0LGhh4eu6hj6k/cAWS6ujRBGqN0Z
fURCrMEyjzbNybhkU1KqSr7eSJOWkl4UJ5Ns/dt9/yw2BBrKH3Mijch7UA8mlbEE
29M28u2W7GMXLSSwmtCqDBRNhg==
-----END CERTIFICATE-----

20
plugin/tls/test_cert.pem Normal file
View file

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDPzCCAiegAwIBAgIJAPezzzshGRiTMA0GCSqGSIb3DQEBCwUAMDUxCzAJBgNV
BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMREwDwYDVQQKDAhJbmZvYmxveDAg
Fw0xOTA1MTEwMDI2MjNaGA8yMTE5MDQxNzAwMjYyM1owNTELMAkGA1UEBhMCVVMx
EzARBgNVBAgMCkNhbGlmb3JuaWExETAPBgNVBAoMCEluZm9ibG94MIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArAYiw1UjlYj+nITRUlj5hA7j8U2qWcyN
YcDfqQnt173Z8yR7NJokqt3Bd3PlrBZS2XtYSNohxRr4qeJu/g7UBre/fSEU/ZOM
Gl7NjBGKQEymJ0d8rBg52iiGNwU+ERI9pcQRA6DCEjVbOmjDiUd5yzuVotG/Sxep
GUJ2puJ0p0gWCMEL9sdqY6HHd/hdj6B6+u2xD9NUCkX9pLC7CPFJHnP0vLO4WIWL
z5C7yzpeLO9r7Nfnu+2HcRLmuFZVPNxkMq7UymqR1w5ZYJQ5p9E7pyxDVXxHnTqQ
yLaAS2/9umrOwVnD1NaN3OdAhDedXbH0cF08GcIQD9rnlkLMW4CKtwIDAQABo1Aw
TjAdBgNVHQ4EFgQUHcxJPBmHF0nSv+FJJI/kwrSThf8wHwYDVR0jBBgwFoAUHcxJ
PBmHF0nSv+FJJI/kwrSThf8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC
AQEAQyN9nLImdtufuSjXcrCJ3alt/vffHJIzlPgDsNw8+tjI7aRX7CzuurOOEQUC
fJ9A6O+dat5k5yqVb9hDcD42HXtOjRQDYpQ6dOGirLFThIFSMC/7RiqHk0YtxojM
ZNBbgXo4o1d+P9b25oc/+pRDzlOvqNL7IzW/LDHnJ4j6tBNguujCB5QFUF5dOa1z
UR5rupMvv2KpEgRcfW/d3kwcAxH9nI0SHKJenhtweyajUgInK88TC+aT4909c2XA
EADYyWxj1DMz3/sMpvGegHsfTPegNoDgz2yEKdu53dr4BUpF6E+eoCX9Hv78SWH3
/rAlkbffzCL5d+I8y0jzEpLEqA==
-----END CERTIFICATE-----

28
plugin/tls/test_key.pem Normal file
View file

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCsBiLDVSOViP6c
hNFSWPmEDuPxTapZzI1hwN+pCe3XvdnzJHs0miSq3cF3c+WsFlLZe1hI2iHFGvip
4m7+DtQGt799IRT9k4waXs2MEYpATKYnR3ysGDnaKIY3BT4REj2lxBEDoMISNVs6
aMOJR3nLO5Wi0b9LF6kZQnam4nSnSBYIwQv2x2pjocd3+F2PoHr67bEP01QKRf2k
sLsI8Ukec/S8s7hYhYvPkLvLOl4s72vs1+e77YdxEua4VlU83GQyrtTKapHXDllg
lDmn0TunLENVfEedOpDItoBLb/26as7BWcPU1o3c50CEN51dsfRwXTwZwhAP2ueW
QsxbgIq3AgMBAAECggEAF3FCnYHltoQTxnqnF+S+JAvvbjvaQiCJB9BD6oJK4kKi
B+tpytJSuuI7ci7eFqR4J+ESN+NaBMVXK7eKzp5wsHWr575xYNkRl6phsnvVbkvD
vMiWKdGnWJ57I9ZYDfWBZyyf8PGgYODajMwoEXYnF9YH30dcHTydM68GAloL8Zu9
CtGCmlu4TER0BvG+rK2OD5lt8ORK56eMwzTTqMy0hCkP5VEq8j9RmekEzrgtWKm8
OI3i8VnpOA0RCVhJ0q5a5jt/xbKRjFNsUNmy9HBRYg7Iw3SCEHmDtz1R9A9rvaJC
WXqwKbGZPY8W69h8BhKcJ5RrKt2PZyJxw+LB610XSQKBgQDR/LIGXdJR/90epiGC
p68W9Vc3eWxJlAtLDQCSULphLi6j7D+jesmhD3z2woBPjxkd4TaZa2t94Q1MzSeC
ON/Aux1huto9ddxvijUQJN3Ep4zPkHdNzHfRwIZsgGH8u77VY/5I4V7IgxKjWlJ6
Ii8ez8xpWj1rnQ0azSaYIcVl7QKBgQDRt+J+iRjKxHWuXoBFfv8oMfl+iYaMdJxu
PELWb3RLsZ92hobSAmNR/gC3T7p8NFJlQVCoxZr8zt/Rvqh4aK3aSOuKeUvYAjs1
/YbPcdSn6uTTIOi6CcHaJ8ZUXNvY5FuoT0+Q9Eb8fw5NGzxsgsfhScELLgbFKb5E
Tkw43ZqeswKBgQCxXBgZnIEaVVw0mOlQ68TNRWfnKR23f92SBGdpLdpeXp1yQwb1
U66d5PENkvbBPAJg5GozZzGhXsbXCajHKraCmQiWFTZkFvqbE0cCXcEaatJaNpEu
GvdRKKXhWwZoa0MiBZUvhXuDLII/iviCxAC8q5LhoSCjlkENVB22/T83eQKBgQC4
c3wRALG+fWZns5QsC5ONnc6rXXfqhxGi3vuGMMbfYF05WP6xLQp/7eBhWg1R+o7R
oc24cvxrB+TRTFhOdvsZtvL7es2bMfMz/EUapSp9edpCW3p1Temi30LPplByhf6b
nQ4FFuRsZa+FX8QYSDpWypCwLY4k0R8YYqklhrrcgwKBgFiM/GnRc230nj0GGWf1
+Ve2M/TQCgS6ufr2F0vU7QkEWfeiN9iunhmhsggqWxOEOU77FhCkQRtztm93hG0K
eKoHNh/1HvHGBWsR0TaMDw3n8t7Yg5NmQb617nBELZbxxpd358muLiHDoix86W9Q
xM6hB159G1gOEJsi8exm5AlZ
-----END PRIVATE KEY-----

View file

@ -1,6 +1,8 @@
package tls package tls
import ( import (
ctls "crypto/tls"
"github.com/coredns/coredns/core/dnsserver" "github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/plugin" "github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/pkg/tls" "github.com/coredns/coredns/plugin/pkg/tls"
@ -16,6 +18,14 @@ func init() {
} }
func setup(c *caddy.Controller) error { func setup(c *caddy.Controller) error {
err := parseTLS(c)
if err != nil {
return plugin.Error("tls", err)
}
return nil
}
func parseTLS(c *caddy.Controller) error {
config := dnsserver.GetConfig(c) config := dnsserver.GetConfig(c)
if config.TLSConfig != nil { if config.TLSConfig != nil {
@ -27,10 +37,39 @@ func setup(c *caddy.Controller) error {
if len(args) < 2 || len(args) > 3 { if len(args) < 2 || len(args) > 3 {
return plugin.Error("tls", c.ArgErr()) return plugin.Error("tls", c.ArgErr())
} }
clientAuth := ctls.NoClientCert
for c.NextBlock() {
switch c.Val() {
case "client_auth":
authTypeArgs := c.RemainingArgs()
if len(authTypeArgs) != 1 {
return c.ArgErr()
}
switch authTypeArgs[0] {
case "nocert":
clientAuth = ctls.NoClientCert
case "request":
clientAuth = ctls.RequestClientCert
case "require":
clientAuth = ctls.RequireAnyClientCert
case "verify_if_given":
clientAuth = ctls.VerifyClientCertIfGiven
case "require_and_verify":
clientAuth = ctls.RequireAndVerifyClientCert
default:
return c.Errf("unknown authentication type '%s'", authTypeArgs[0])
}
default:
return c.Errf("unknown option '%s'", c.Val())
}
}
tls, err := tls.NewTLSConfigFromArgs(args...) tls, err := tls.NewTLSConfigFromArgs(args...)
if err != nil { if err != nil {
return plugin.Error("tls", err) return err
} }
tls.ClientAuth = clientAuth
// NewTLSConfigFromArgs only sets RootCAs, so we need to let ClientCAs refer to it.
tls.ClientCAs = tls.RootCAs
config.TLSConfig = tls config.TLSConfig = tls
} }
return nil return nil

View file

@ -1,9 +1,12 @@
package tls package tls
import ( import (
"crypto/tls"
"strings" "strings"
"testing" "testing"
"github.com/coredns/coredns/core/dnsserver"
"github.com/mholt/caddy" "github.com/mholt/caddy"
) )
@ -16,6 +19,11 @@ func TestTLS(t *testing.T) {
}{ }{
// positive // positive
// negative // negative
{"tls test_cert.pem test_key.pem test_ca.pem {\nunknown\n}", true, "", "unknown option"},
// client_auth takes exactly one parameter, which must be one of known keywords.
{"tls test_cert.pem test_key.pem test_ca.pem {\nclient_auth\n}", true, "", "Wrong argument"},
{"tls test_cert.pem test_key.pem test_ca.pem {\nclient_auth none bogus\n}", true, "", "Wrong argument"},
{"tls test_cert.pem test_key.pem test_ca.pem {\nclient_auth bogus\n}", true, "", "unknown authentication type"},
} }
for i, test := range tests { for i, test := range tests {
@ -38,3 +46,37 @@ func TestTLS(t *testing.T) {
} }
} }
} }
func TestTLSClientAuthentication(t *testing.T) {
// Invalid configurations are tested in the general test case. In this test we only look into specific details of valid client_auth options.
tests := []struct {
option string // tls plugin option(s)
expectedType tls.ClientAuthType // expected authentication type.
}{
// By default, or if 'nocert' is specified, no cert should be requested.
// Other cases should be a straightforward mapping from the keyword to the type value.
{"", tls.NoClientCert},
{"{\nclient_auth nocert\n}", tls.NoClientCert},
{"{\nclient_auth request\n}", tls.RequestClientCert},
{"{\nclient_auth require\n}", tls.RequireAnyClientCert},
{"{\nclient_auth verify_if_given\n}", tls.VerifyClientCertIfGiven},
{"{\nclient_auth require_and_verify\n}", tls.RequireAndVerifyClientCert},
}
for i, test := range tests {
input := "tls test_cert.pem test_key.pem test_ca.pem " + test.option
c := caddy.NewTestController("dns", input)
err := setup(c)
if err != nil {
t.Errorf("Test %d: TLS config is unexpectedly rejected: %v", i, err)
continue // there's no point in the rest of the tests.
}
cfg := dnsserver.GetConfig(c)
if cfg.TLSConfig.ClientCAs == nil {
t.Errorf("Test %d: Client CA is not configured", i)
}
if cfg.TLSConfig.ClientAuth != test.expectedType {
t.Errorf("Test %d: Unexpected client auth type: %d", i, cfg.TLSConfig.ClientAuth)
}
}
}