TLS configuration

- handle the same AlPN list
- use dev build tag for debugging purposes
This commit is contained in:
Zoltán Papp
2024-11-27 18:14:17 +01:00
parent 3a447c066b
commit bca8944e7a
9 changed files with 174 additions and 92 deletions

View File

@ -2,7 +2,7 @@ package quic
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"strings"
@ -11,27 +11,16 @@ import (
"github.com/quic-go/quic-go"
log "github.com/sirupsen/logrus"
quictls "github.com/netbirdio/netbird/relay/tls"
nbnet "github.com/netbirdio/netbird/util/net"
)
const (
dialTimeout = 30 * time.Second
)
func Dial(address string) (net.Conn, error) {
func Dial(ctx context.Context, address string) (net.Conn, error) {
quicURL, err := prepareURL(address)
if err != nil {
return nil, err
}
ctx, cancel := context.WithTimeout(context.Background(), dialTimeout)
defer cancel()
tlsConf := &tls.Config{
InsecureSkipVerify: true, // Set to true only for testing
NextProtos: []string{"netbird-relay"}, // Ensure this matches the server's ALPN
}
quicConfig := &quic.Config{
KeepAlivePeriod: 15 * time.Second,
MaxIdleTimeout: 60 * time.Second,
@ -50,8 +39,12 @@ func Dial(address string) (net.Conn, error) {
return nil, err
}
session, err := quic.Dial(ctx, udpConn, udpAddr, tlsConf, quicConfig)
session, err := quic.Dial(ctx, udpConn, udpAddr, quictls.ClientQUICTLSConfig(), quicConfig)
if err != nil {
if errors.Is(ctx.Err(), context.Canceled) {
log.Infof("QUIC connection aborted to: %s", quicURL)
return nil, err
}
log.Errorf("failed to dial to Relay server via QUIC '%s': %s", quicURL, err)
return nil, err
}

View File

@ -2,7 +2,7 @@ package ws
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"net/http"
@ -16,7 +16,7 @@ import (
nbnet "github.com/netbirdio/netbird/util/net"
)
func Dial(address string) (net.Conn, error) {
func Dial(ctx context.Context, address string) (net.Conn, error) {
wsURL, err := prepareURL(address)
if err != nil {
return nil, err
@ -32,8 +32,12 @@ func Dial(address string) (net.Conn, error) {
}
parsedURL.Path = ws.URLPath
wsConn, resp, err := websocket.Dial(context.Background(), parsedURL.String(), opts)
wsConn, resp, err := websocket.Dial(ctx, parsedURL.String(), opts)
if err != nil {
if errors.Is(ctx.Err(), context.Canceled) {
log.Infof("WebSocket connection aborted to: %s", wsURL)
return nil, err
}
log.Errorf("failed to dial to Relay server '%s': %s", wsURL, err)
return nil, err
}
@ -60,10 +64,6 @@ func httpClientNbDialer() *http.Client {
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return customDialer.DialContext(ctx, network, addr)
},
// Set up a TLS configuration that skips certificate verification
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, // This accepts invalid TLS certificates
},
}
return &http.Client{

View File

@ -2,19 +2,10 @@ package server
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"net"
"sync"
"time"
"github.com/hashicorp/go-multierror"
log "github.com/sirupsen/logrus"
"go.opentelemetry.io/otel/metric"
nberrors "github.com/netbirdio/netbird/client/errors"
@ -22,6 +13,7 @@ import (
"github.com/netbirdio/netbird/relay/server/listener"
"github.com/netbirdio/netbird/relay/server/listener/quic"
"github.com/netbirdio/netbird/relay/server/listener/ws"
quictls "github.com/netbirdio/netbird/relay/tls"
)
// ListenerConfig is the configuration for the listener.
@ -64,19 +56,16 @@ func (r *Server) Listen(cfg ListenerConfig) error {
}
r.listeners = append(r.listeners, wSListener)
quicListener := &quic.Listener{
Address: cfg.Address,
tlsConfigQUIC, err := quictls.ServerQUICTLSConfig(cfg.TLSConfig)
if err != nil {
return err
}
if cfg.TLSConfig != nil {
quicListener.TLSConfig = cfg.TLSConfig
} else {
tlsConfig, err := generateTestTLSConfig()
if err != nil {
return err
}
quicListener.TLSConfig = tlsConfig
quicListener := &quic.Listener{
Address: cfg.Address,
TLSConfig: tlsConfigQUIC,
}
r.listeners = append(r.listeners, quicListener)
errChan := make(chan error, len(r.listeners))
@ -117,54 +106,3 @@ func (r *Server) Shutdown(ctx context.Context) error {
func (r *Server) InstanceURL() string {
return r.relay.instanceURL
}
// GenerateTestTLSConfig creates a self-signed certificate for testing
func generateTestTLSConfig() (*tls.Config, error) {
log.Infof("generating test TLS config")
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, err
}
template := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
Organization: []string{"Test Organization"},
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24 * 180), // Valid for 180 days
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth,
},
BasicConstraintsValid: true,
DNSNames: []string{"localhost"},
IPAddresses: []net.IP{net.ParseIP("192.168.0.10")},
}
// Create certificate
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
if err != nil {
return nil, err
}
certPEM := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: certDER,
})
privateKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
})
tlsCert, err := tls.X509KeyPair(certPEM, privateKeyPEM)
if err != nil {
return nil, err
}
return &tls.Config{
Certificates: []tls.Certificate{tlsCert},
NextProtos: []string{"netbird-relay"},
}, nil
}

3
relay/tls/alpn.go Normal file
View File

@ -0,0 +1,3 @@
package tls
const nbalpn = "nb-quic"

12
relay/tls/client_dev.go Normal file
View File

@ -0,0 +1,12 @@
//go:build dev
package tls
import "crypto/tls"
func ClientQUICTLSConfig() *tls.Config {
return &tls.Config{
InsecureSkipVerify: true, // Debug mode allows insecure connections
NextProtos: []string{nbalpn}, // Ensure this matches the server's ALPN
}
}

11
relay/tls/client_prod.go Normal file
View File

@ -0,0 +1,11 @@
//go:build !dev
package tls
import "crypto/tls"
func ClientQUICTLSConfig() *tls.Config {
return &tls.Config{
NextProtos: []string{nbalpn},
}
}

37
relay/tls/doc.go Normal file
View File

@ -0,0 +1,37 @@
// Package tls provides utilities for configuring and managing
// Transport Layer Security (TLS) in server environments, with
// a focus on QUIC protocol support and testing configurations.
//
// The package includes functions for cloning and customizing
// TLS configurations as well as generating self-signed
// certificates for development and testing purposes.
//
// Key Features:
//
// - `ServerQUICTLSConfig`: Creates a server-side TLS configuration
// tailored for QUIC protocol with specified or default settings.
// QUIC requires a specific TLS configuration with proper ALPN
// (Application-Layer Protocol Negotiation) support, making the
// TLS settings crucial for establishing secure connections.
//
// - `ClientQUICTLSConfig`: Provides a client-side TLS configuration
// suitable for QUIC protocol. The configuration differs between
// development (insecure testing) and production (strict verification).
//
// - `generateTestTLSConfig`: Generates a self-signed TLS configuration
// for use in local development and testing scenarios.
//
// Usage:
//
// This package provides separate implementations for development
// and production environments. The development implementation
// (guarded by `//go:build dev`) supports testing configurations
// with self-signed certificates and insecure client connections.
// The production implementation (guarded by `//go:build !dev`)
// ensures that valid and secure TLS configurations are supplied
// and used.
//
// The QUIC protocol is highly reliant on properly configured TLS
// settings, and this package ensures that configurations meet the
// requirements for secure and efficient QUIC communication.
package tls

71
relay/tls/server_dev.go Normal file
View File

@ -0,0 +1,71 @@
//go:build dev
package tls
import (
"crypto/tls"
log "github.com/sirupsen/logrus"
)
func ServerQUICTLSConfig(originTLSCfg *tls.Config) (*tls.Config, error) {
if originTLSCfg == nil {
log.Warnf("QUIC server will use self signed certificate for testing!")
return generateTestTLSConfig()
}
cfg := originTLSCfg.Clone()
cfg.NextProtos = []string{"nb"}
return cfg, nil
}
// GenerateTestTLSConfig creates a self-signed certificate for testing
func generateTestTLSConfig() (*tls.Config, error) {
log.Infof("generating test TLS config")
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, err
}
template := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
Organization: []string{"Test Organization"},
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24 * 180), // Valid for 180 days
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth,
},
BasicConstraintsValid: true,
DNSNames: []string{"localhost"},
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
}
// Create certificate
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
if err != nil {
return nil, err
}
certPEM := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: certDER,
})
privateKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
})
tlsCert, err := tls.X509KeyPair(certPEM, privateKeyPEM)
if err != nil {
return nil, err
}
return &tls.Config{
Certificates: []tls.Certificate{tlsCert},
NextProtos: []string{nbalpn},
}, nil
}

17
relay/tls/server_prod.go Normal file
View File

@ -0,0 +1,17 @@
//go:build !dev
package tls
import (
"crypto/tls"
"fmt"
)
func ServerQUICTLSConfig(originTLSCfg *tls.Config) (*tls.Config, error) {
if originTLSCfg == nil {
return nil, fmt.Errorf("valid TLS config is required for QUIC listener")
}
cfg := originTLSCfg.Clone()
cfg.NextProtos = []string{nbalpn}
return cfg, nil
}