Skip to content

Commit

Permalink
Add support for setting TLS min version number in tls block
Browse files Browse the repository at this point in the history
For example, to set minimum version to TLS v1.3:

  tls { min_version = "1.3" }

Signed-off-by: Waldemar Quevedo <wally@nats.io>
  • Loading branch information
wallyqs committed Sep 19, 2024
1 parent 83c77b4 commit 77db0c0
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 2 deletions.
32 changes: 32 additions & 0 deletions server/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,7 @@ type TLSConfigOpts struct {
CaCertsMatch []string
OCSPPeerConfig *certidp.OCSPPeerConfig
Certificates []*TLSCertPairOpt
MinVersion uint16
}

// TLSCertPairOpt are the paths to a certificate and private key.
Expand Down Expand Up @@ -4560,6 +4561,24 @@ func parseCurvePreferences(curveName string) (tls.CurveID, error) {
return curve, nil
}

func parseTLSVersion(v any) (uint16, error) {
var tlsVersionNumber uint16
switch v := v.(type) {
case string:
n, err := tlsVersionFromString(v)
if err != nil {
return 0, err
}
tlsVersionNumber = n
default:
return 0, fmt.Errorf("'min_version' wrong type: %v", v)
}
if tlsVersionNumber < tls.VersionTLS12 {
return 0, fmt.Errorf("unsupported TLS version: %s", tls.VersionName(tlsVersionNumber))
}
return tlsVersionNumber, nil
}

// Helper function to parse TLS configs.
func parseTLS(v any, isClientCtx bool) (t *TLSConfigOpts, retErr error) {
var (
Expand Down Expand Up @@ -4825,6 +4844,12 @@ func parseTLS(v any, isClientCtx bool) (t *TLSConfigOpts, retErr error) {
}
tc.Certificates[i] = certPair
}
case "min_version":
minVersion, err := parseTLSVersion(mv)
if err != nil {
return nil, &configErr{tk, fmt.Sprintf("error parsing tls config: %v", err)}
}
tc.MinVersion = minVersion
default:
return nil, &configErr{tk, fmt.Sprintf("error parsing tls config, unknown field %q", mk)}
}
Expand Down Expand Up @@ -5199,6 +5224,13 @@ func GenTLSConfig(tc *TLSConfigOpts) (*tls.Config, error) {
}
config.ClientCAs = pool
}
// Allow setting TLS minimum version.
if tc.MinVersion > 0 {
if tc.MinVersion < tls.VersionTLS12 {
return nil, fmt.Errorf("unsupported minimum TLS version: %s", tls.VersionName(tc.MinVersion))
}
config.MinVersion = tc.MinVersion
}

return &config, nil
}
Expand Down
2 changes: 1 addition & 1 deletion server/reload.go
Original file line number Diff line number Diff line change
Expand Up @@ -1151,7 +1151,7 @@ func imposeOrder(value any) error {
slices.SortFunc(value.Gateways, func(i, j *RemoteGatewayOpts) int { return cmp.Compare(i.Name, j.Name) })
case WebsocketOpts:
slices.Sort(value.AllowedOrigins)
case string, bool, uint8, int, int32, int64, time.Duration, float64, nil, LeafNodeOpts, ClusterOpts, *tls.Config, PinnedCertSet,
case string, bool, uint8, uint16, int, int32, int64, time.Duration, float64, nil, LeafNodeOpts, ClusterOpts, *tls.Config, PinnedCertSet,
*URLAccResolver, *MemAccResolver, *DirAccResolver, *CacheDirAccResolver, Authentication, MQTTOpts, jwt.TagList,
*OCSPConfig, map[string]string, JSLimitOpts, StoreCipher, *OCSPResponseCacheConfig:
// explicitly skipped types
Expand Down
14 changes: 14 additions & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -3506,6 +3506,20 @@ func tlsVersion(ver uint16) string {
return fmt.Sprintf("Unknown [0x%x]", ver)
}

func tlsVersionFromString(ver string) (uint16, error) {
switch ver {
case "1.0":
return tls.VersionTLS10, nil
case "1.1":
return tls.VersionTLS11, nil
case "1.2":
return tls.VersionTLS12, nil
case "1.3":
return tls.VersionTLS13, nil
}
return 0, fmt.Errorf("Unknown version: %v", ver)
}

// We use hex here so we don't need multiple versions
func tlsCipher(cs uint16) string {
name, present := cipherMapByID[cs]
Expand Down
97 changes: 96 additions & 1 deletion server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,102 @@ func TestTLSVersions(t *testing.T) {
}
}

func TestTlsCipher(t *testing.T) {
func TestTLSMinVersionConfig(t *testing.T) {
tmpl := `
listen: "127.0.0.1:-1"
tls {
cert_file: "../test/configs/certs/server-cert.pem"
key_file: "../test/configs/certs/server-key.pem"
timeout: 1
min_version: %s
}
`
conf := createConfFile(t, []byte(fmt.Sprintf(tmpl, `"1.3"`)))
s, o := RunServerWithConfig(conf)
defer s.Shutdown()

connect := func(t *testing.T, tlsConf *tls.Config, expectedErr error) {
t.Helper()
opts := []nats.Option{}
if tlsConf != nil {
opts = append(opts, nats.Secure(tlsConf))
}
opts = append(opts, nats.RootCAs("../test/configs/certs/ca.pem"))
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", o.Port), opts...)
if expectedErr == nil {
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
} else if err == nil || err.Error() != expectedErr.Error() {
nc.Close()
t.Fatalf("Expected error %v, got: %v", expectedErr, err)
}
}

// Cannot connect with client requiring a lower minimum TLS Version.
connect(t, &tls.Config{
MaxVersion: tls.VersionTLS12,
}, errors.New(`remote error: tls: protocol version not supported`))

// Should connect since matching minimum TLS version.
connect(t, &tls.Config{
MinVersion: tls.VersionTLS13,
}, nil)

// Reloading with invalid values should fail.
if err := os.WriteFile(conf, []byte(fmt.Sprintf(tmpl, `"1.0"`)), 0666); err != nil {
t.Fatalf("Error creating config file: %v", err)
}
if err := s.Reload(); err == nil {
t.Fatalf("Expected reload to fail: %v", err)
}

// Reloading with original values and no changes should be ok.
if err := os.WriteFile(conf, []byte(fmt.Sprintf(tmpl, `"1.3"`)), 0666); err != nil {
t.Fatalf("Error creating config file: %v", err)
}
if err := s.Reload(); err != nil {
t.Fatalf("Unexpected error reloading TLS version: %v", err)
}

// Reloading with a new minimum lower version.
if err := os.WriteFile(conf, []byte(fmt.Sprintf(tmpl, `"1.2"`)), 0666); err != nil {
t.Fatalf("Error creating config file: %v", err)
}
if err := s.Reload(); err != nil {
t.Fatalf("Unexpected error reloading: %v", err)
}

// Should connect since now matching minimum TLS version.
connect(t, &tls.Config{
MaxVersion: tls.VersionTLS12,
}, nil)
connect(t, &tls.Config{
MinVersion: tls.VersionTLS13,
}, nil)

// Setting unsupported TLS versions
if err := os.WriteFile(conf, []byte(fmt.Sprintf(tmpl, `"1.4"`)), 0666); err != nil {
t.Fatalf("Error creating config file: %v", err)
}
if err := s.Reload(); err == nil || !strings.Contains(err.Error(), `Unknown version: 1.4`) {
t.Fatalf("Unexpected error reloading: %v", err)
}

tc := &TLSConfigOpts{
CertFile: "../test/configs/certs/server-cert.pem",
KeyFile: "../test/configs/certs/server-key.pem",
CaFile: "../test/configs/certs/ca.pem",
Timeout: 4.0,
MinVersion: tls.VersionTLS11,
}
_, err := GenTLSConfig(tc)
if err == nil || err.Error() != `unsupported minimum TLS version: TLS 1.1` {
t.Fatalf("Expected error generating TLS config: %v", err)
}
}

func TestTLSCipher(t *testing.T) {
if strings.Compare(tlsCipher(0x0005), "TLS_RSA_WITH_RC4_128_SHA") != 0 {
t.Fatalf("Invalid tls cipher")
}
Expand Down

0 comments on commit 77db0c0

Please sign in to comment.