diff --git a/cmd/hvpn-node/.gitignore b/cmd/hvpn-node/.gitignore index 7c07f9f..6db9f6f 100644 --- a/cmd/hvpn-node/.gitignore +++ b/cmd/hvpn-node/.gitignore @@ -1,2 +1,7 @@ hvpn-node - +tmp/ +.air.toml +client_cert.pem +client_cert.key +cert.key +cert.pem diff --git a/cmd/hvpn-node/hvpn-node.go b/cmd/hvpn-node/hvpn-node.go index 4cb52b9..16dd3d9 100644 --- a/cmd/hvpn-node/hvpn-node.go +++ b/cmd/hvpn-node/hvpn-node.go @@ -2,6 +2,8 @@ package main import ( "bufio" + "crypto/tls" + "crypto/x509" "errors" "fmt" "log/slog" @@ -32,6 +34,8 @@ var WgPort int var wgLink *hvpnnode3.WGLink var httpPort int +var TLS_ENABLED bool +var tlsConfig *tls.Config func main() { @@ -76,10 +80,17 @@ func run(ctx *cli.Context) { IdleTimeout: 120 * time.Second, Handler: handler, Addr: hostPort, + TLSConfig: tlsConfig, } defer wgLink.Close() - slog.Warn(srv.ListenAndServe().Error()) + if TLS_ENABLED { + slog.Debug("Running with TLS") + slog.Info("Running TLS or mTLS with a reverse proxy is recommended") + slog.Warn(srv.ListenAndServeTLS(ctx.Path("cert"), ctx.Path("cert-private-key")).Error()) + } else { + slog.Warn(srv.ListenAndServe().Error()) + } } func createCliApp() *cli.App { @@ -175,6 +186,48 @@ func createCliApp() *cli.App { app.Flags = append(app.Flags, &httpPort) + /* TLS Flags */ + + TLS := cli.BoolFlag{ + Name: "enable-tls", + Aliases: []string{ "tls" }, + Value: false, + Category: "\rTLS:", + Destination: &TLS_ENABLED, + Action: func(ctx *cli.Context, b bool) error { + tlsConf, err := setupTLS(ctx) + if err != nil { + return err + } + tlsConfig = tlsConf + return nil + }, + } + app.Flags = append(app.Flags, &TLS) + + mTLSClientCerts := cli.PathFlag{ + Name: "client-certs", + Aliases: []string{"ca"}, + Usage: "A path to PEM file with client certificates; Enables TLS", + Category: "\rTLS:", + } + app.Flags = append(app.Flags, &mTLSClientCerts) + + TLSCert := cli.PathFlag{ + Name: "cert", + Usage: "Server x509 certificate file", + Category: "\rTLS:", + } + app.Flags = append(app.Flags, &TLSCert) + + TLSCertKey := cli.PathFlag{ + Name: "cert-private-key", + Usage: "Server x509 certificate private key file", + Category: "\rTLS:", + } + app.Flags = append(app.Flags, &TLSCertKey) + + app.Action = func(ctx *cli.Context) error { err := setup(ctx) if err != nil { @@ -367,3 +420,46 @@ func handleStdin(c chan struct{}) { } } } + +func createMTLS(clinetCert, serverCert, serverKey string) (*x509.CertPool, error) { + clientCert, err := os.ReadFile(clinetCert) + if err != nil { + return nil, err + } + certPool := x509.NewCertPool() + ok := certPool.AppendCertsFromPEM(clientCert) + if !ok { + return nil, errors.New("mTLS: Could read and parase a single cert from client cert") + } + return certPool, nil +} + +func setupTLS(ctx *cli.Context) (*tls.Config, error) { + ca := ctx.Path("client-certs") + if ca == "" { + return nil, errors.New("client-certs flag is not set to enable TLS") + } + + serverCert := ctx.Path("ca") + if serverCert == "" { + return nil, errors.New("cert flag is not set to enable TLS") + } + + serverKey := ctx.Path("cert-private-key") + if serverKey == "" { + return nil, errors.New("cert-private-key is not set to enable TLS") + } + + certPool, err := createMTLS(ca, serverCert, serverKey) + if err != nil { + return nil, err + } + + conf := tls.Config{ + ClientAuth: tls.RequireAndVerifyClientCert, + ClientCAs: certPool, + } + + return &conf, nil +} + diff --git a/cmd/hvpn-node/init-certs.sh b/cmd/hvpn-node/init-certs.sh new file mode 100755 index 0000000..fb513e3 --- /dev/null +++ b/cmd/hvpn-node/init-certs.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +openssl req \ + -x509 \ + -new \ + -noenc \ + -sha256 \ + -newkey rsa:4096 \ + -outform PEM \ + -keyout client_cert.key \ + -out client_cert.pem \ + -days 3600 \ + -new \ + -subj "/C=US/ST=Ohio/L=Columbus/O=HVPN/OU=HVPN Client" + +openssl req \ + -x509 \ + -new \ + -noenc \ + -sha256 \ + -newkey rsa:4096 \ + -outform PEM \ + -keyout cert.key \ + -out cert.pem \ + -days 3600 \ + -new \ + -subj "/C=US/ST=Ohio/L=Columbus/O=HVPN/OU=HVPN Server" +