Created separate config with shared structures for server & client flags

Standardize server & client flag names
Auto accept terms if ‘acmeEmail’ is provided
Added ‘defaultCA’ flag
Added ‘autoCerts’ flag
This commit is contained in:
Willem@105.pve1.lan 2022-03-02 09:11:44 +02:00
parent 2e0ff06c33
commit f7b1df0505
6 changed files with 198 additions and 111 deletions

View File

@ -4,7 +4,6 @@ import (
"bufio" "bufio"
"context" "context"
"crypto/tls" "crypto/tls"
"flag"
"fmt" "fmt"
"io" "io"
"log" "log"
@ -42,29 +41,11 @@ type Server struct {
httpListener *PassthroughListener httpListener *PassthroughListener
} }
func Listen() { func Listen(config *ServerConfig) {
flagSet := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
newAdminDomain := flagSet.String("admin-domain", "", "Admin Domain")
sshServerPort := flagSet.Int("ssh-server-port", 22, "SSH Server Port")
dbDir := flagSet.String("db-dir", "", "Database file directory")
certDir := flagSet.String("cert-dir", "", "TLS cert directory")
printLogin := flagSet.Bool("print-login", false, "Prints admin login information")
httpPort := flagSet.Int("http-port", 80, "HTTP (insecure) port")
httpsPort := flagSet.Int("https-port", 443, "HTTPS (secure) port")
allowHttp := flagSet.Bool("allow-http", false, "Allow unencrypted (HTTP) requests")
publicIp := flagSet.String("public-ip", "", "Public IP")
behindProxy := flagSet.Bool("behind-proxy", false, "Whether we're running behind another reverse proxy")
acmeEmail := flagSet.String("acme-email", "", "Email for ACME (ie Let's Encrypt)")
acmeUseStaging := flagSet.Bool("acme-use-staging", false, "Use ACME (ie Let's Encrypt) staging servers")
acceptCATerms := flagSet.Bool("accept-ca-terms", false, "Automatically accept CA terms")
err := flagSet.Parse(os.Args[2:])
if err != nil {
fmt.Fprintf(os.Stderr, "%s: parsing flags: %s\n", os.Args[0], err)
}
log.Println("Starting up") log.Println("Starting up")
db, err := NewDatabase(*dbDir) db, err := NewDatabase(config.dbDir)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -73,8 +54,8 @@ func Listen() {
var ip string var ip string
if *publicIp != "" { if config.publicIp != "" {
ip = *publicIp ip = config.publicIp
} else { } else {
ip, err = namedropClient.GetPublicIp() ip, err = namedropClient.GetPublicIp()
if err != nil { if err != nil {
@ -82,45 +63,49 @@ func Listen() {
} }
} }
err = namedrop.CheckPublicAddress(ip, *httpPort) err = namedrop.CheckPublicAddress(ip, config.httpPort)
if err != nil { if err != nil {
fmt.Printf("WARNING: Failed to access %s:%d from the internet\n", ip, *httpPort) fmt.Printf("WARNING: Failed to access %s:%d from the internet\n", ip, config.httpPort)
} }
err = namedrop.CheckPublicAddress(ip, *httpsPort) err = namedrop.CheckPublicAddress(ip, config.httpsPort)
if err != nil { if err != nil {
fmt.Printf("WARNING: Failed to access %s:%d from the internet\n", ip, *httpsPort) fmt.Printf("WARNING: Failed to access %s:%d from the internet\n", ip, config.httpsPort)
} }
autoCerts := true var autoCerts bool
if *httpPort != 80 || *httpsPort != 443 { if config.httpPort != 80 || config.httpsPort != 443 {
fmt.Printf("WARNING: LetsEncrypt only supports HTTP/HTTPS ports 80/443. You are using %d/%d. Disabling automatic certificate management\n", *httpPort, *httpsPort) fmt.Printf("WARNING: LetsEncrypt only supports HTTP/HTTPS ports 80/443. You are using %d/%d. Disabling automatic certificate management\n", config.httpPort, config.httpsPort)
autoCerts = false autoCerts = false
} else {
autoCerts = config.myCertConfig.autoCerts
} }
if *certDir != "" { if config.myCertConfig.certDir != "" {
certmagic.Default.Storage = &certmagic.FileStorage{*certDir} certmagic.Default.Storage = &certmagic.FileStorage{config.myCertConfig.certDir}
}
//certmagic.DefaultACME.DisableHTTPChallenge = true
//certmagic.DefaultACME.DisableTLSALPNChallenge = true
if *acmeEmail != "" {
certmagic.DefaultACME.Email = *acmeEmail
} }
if *acceptCATerms { if config.myCertConfig.acmeEmail != "" {
certmagic.DefaultACME.Email = config.myCertConfig.acmeEmail
certmagic.DefaultACME.Agreed = true certmagic.DefaultACME.Agreed = true
log.Print(fmt.Sprintf("Automatic agreement to CA terms with email (%s)", *acmeEmail)) log.Print(fmt.Sprintf("Automatic agreement to CA terms with email (%s)", config.myCertConfig.acmeEmail))
} }
if *acmeUseStaging { switch config.myCertConfig.defaultCA {
case "production":
certmagic.DefaultACME.CA = certmagic.LetsEncryptProductionCA
case "staging":
certmagic.DefaultACME.CA = certmagic.LetsEncryptStagingCA certmagic.DefaultACME.CA = certmagic.LetsEncryptStagingCA
default:
certmagic.DefaultACME.CA = config.myCertConfig.defaultCA
} }
certConfig := certmagic.NewDefault() certConfig := certmagic.NewDefault()
if *newAdminDomain != "" { if config.adminDomain != "" {
db.SetAdminDomain(*newAdminDomain) db.SetAdminDomain(config.adminDomain)
} }
adminDomain := db.GetAdminDomain() adminDomain := db.GetAdminDomain()
@ -152,29 +137,29 @@ func Listen() {
} }
if *printLogin { if config.printLogin {
for token, tokenData := range db.GetTokens() { for token, tokenData := range db.GetTokens() {
if tokenData.Owner == "admin" && tokenData.Client == "" { if tokenData.Owner == "admin" && tokenData.Client == "" {
printLoginInfo(token, db.GetAdminDomain(), *httpsPort) printLoginInfo(token, db.GetAdminDomain(), config.httpsPort)
break break
} }
} }
} }
config := &Config{ TunnelManagerConfig := &Config{
SshServerPort: *sshServerPort, SshServerPort: config.sshServerPort,
PublicIp: ip, PublicIp: ip,
namedropClient: namedropClient, namedropClient: namedropClient,
autoCerts: autoCerts, autoCerts: autoCerts,
} }
tunMan := NewTunnelManager(config, db, certConfig) tunMan := NewTunnelManager(TunnelManagerConfig, db, certConfig)
auth := NewAuth(db) auth := NewAuth(db)
api := NewApi(config, db, auth, tunMan) api := NewApi(TunnelManagerConfig, db, auth, tunMan)
webUiHandler := NewWebUiHandler(config, db, api, auth) webUiHandler := NewWebUiHandler(TunnelManagerConfig, db, api, auth)
httpClient := &http.Client{ httpClient := &http.Client{
// Don't follow redirects // Don't follow redirects
@ -232,7 +217,7 @@ func Listen() {
host := namedropTokenData.Scopes[0].Host host := namedropTokenData.Scopes[0].Host
recordType := "AAAA" recordType := "AAAA"
if IsIPv4(config.PublicIp) { if IsIPv4(TunnelManagerConfig.PublicIp) {
recordType = "A" recordType = "A"
} }
@ -240,7 +225,7 @@ func Listen() {
Domain: domain, Domain: domain,
Host: host, Host: host,
Type: recordType, Type: recordType,
Value: config.PublicIp, Value: TunnelManagerConfig.PublicIp,
TTL: 300, TTL: 300,
} }
@ -299,23 +284,23 @@ func Listen() {
return return
} }
proxyRequest(w, r, tunnel, httpClient, "localhost", tunnel.TunnelPort, *behindProxy) proxyRequest(w, r, tunnel, httpClient, "localhost", tunnel.TunnelPort, config.behindProxy)
} }
}) })
go func() { go func() {
if *allowHttp { if config.allowHttp {
if err := http.ListenAndServe(fmt.Sprintf(":%d", *httpPort), nil); err != nil { if err := http.ListenAndServe(fmt.Sprintf(":%d", config.httpPort), nil); err != nil {
log.Fatalf("ListenAndServe error: %v", err) log.Fatalf("ListenAndServe error: %v", err)
} }
} else { } else {
redirectTLS := func(w http.ResponseWriter, r *http.Request) { redirectTLS := func(w http.ResponseWriter, r *http.Request) {
url := fmt.Sprintf("https://%s:%d%s", r.Host, *httpsPort, r.RequestURI) url := fmt.Sprintf("https://%s:%d%s", r.Host, config.httpsPort, r.RequestURI)
http.Redirect(w, r, url, http.StatusMovedPermanently) http.Redirect(w, r, url, http.StatusMovedPermanently)
} }
if err := http.ListenAndServe(fmt.Sprintf(":%d", *httpPort), http.HandlerFunc(redirectTLS)); err != nil { if err := http.ListenAndServe(fmt.Sprintf(":%d", config.httpPort), http.HandlerFunc(redirectTLS)); err != nil {
log.Fatalf("ListenAndServe error: %v", err) log.Fatalf("ListenAndServe error: %v", err)
} }
} }
@ -324,7 +309,7 @@ func Listen() {
go http.Serve(tlsListener, nil) go http.Serve(tlsListener, nil)
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", *httpsPort)) listener, err := net.Listen("tcp", fmt.Sprintf(":%d", config.httpsPort))
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -31,18 +31,6 @@ type Client struct {
behindProxy bool behindProxy bool
} }
type ClientConfig struct {
ServerAddr string `json:"serverAddr,omitempty"`
Token string `json:"token,omitempty"`
ClientName string `json:"clientName,omitempty"`
User string `json:"user,omitempty"`
CertDir string `json:"certDir,omitempty"`
AcmeEmail string `json:"acmeEmail,omitempty"`
AcmeUseStaging bool `json:"acmeUseStaging,omitempty"`
DnsServer string `json:"dnsServer,omitempty"`
BehindProxy bool `json:"behindProxy,omitempty"`
}
func NewClient(config *ClientConfig) (*Client, error) { func NewClient(config *ClientConfig) (*Client, error) {
if config.DnsServer != "" { if config.DnsServer != "" {
@ -72,16 +60,25 @@ func NewClient(config *ClientConfig) (*Client, error) {
certmagic.DefaultACME.DisableHTTPChallenge = true certmagic.DefaultACME.DisableHTTPChallenge = true
if config.CertDir != "" { if config.myCertConfig.certDir != "" {
certmagic.Default.Storage = &certmagic.FileStorage{config.CertDir} certmagic.Default.Storage = &certmagic.FileStorage{config.myCertConfig.certDir}
} }
if config.AcmeEmail != "" { if config.myCertConfig.acmeEmail != "" {
certmagic.DefaultACME.Email = config.AcmeEmail certmagic.DefaultACME.Email = config.myCertConfig.acmeEmail
certmagic.DefaultACME.Agreed = true
log.Print(fmt.Sprintf("Automatic agreement to CA terms with email (%s)", config.myCertConfig.acmeEmail))
} }
if config.AcmeUseStaging { switch config.myCertConfig.defaultCA {
case "production":
certmagic.DefaultACME.CA = certmagic.LetsEncryptProductionCA
case "staging":
certmagic.DefaultACME.CA = certmagic.LetsEncryptStagingCA certmagic.DefaultACME.CA = certmagic.LetsEncryptStagingCA
default:
certmagic.DefaultACME.CA = config.myCertConfig.defaultCA
} }
certConfig := certmagic.NewDefault() certConfig := certmagic.NewDefault()

View File

@ -83,43 +83,10 @@ func main() {
} }
} }
case "server": case "server":
boringproxy.Listen() config := boringproxy.SetServerConfig()
boringproxy.Listen(config)
case "client": case "client":
flagSet := flag.NewFlagSet(os.Args[0], flag.ExitOnError) config := boringproxy.SetClientConfig()
server := flagSet.String("server", "", "boringproxy server")
token := flagSet.String("token", "", "Access token")
name := flagSet.String("client-name", "", "Client name")
user := flagSet.String("user", "", "user")
certDir := flagSet.String("cert-dir", "", "TLS cert directory")
acmeEmail := flagSet.String("acme-email", "", "Email for ACME (ie Let's Encrypt)")
acmeUseStaging := flagSet.Bool("acme-use-staging", false, "Use ACME (ie Let's Encrypt) staging servers")
dnsServer := flagSet.String("dns-server", "", "Custom DNS server")
behindProxy := flagSet.Bool("behind-proxy", false, "Whether we're running behind another reverse proxy")
err := flagSet.Parse(os.Args[2:])
if err != nil {
fmt.Fprintf(os.Stderr, "%s: parsing flags: %s\n", os.Args[0], err)
}
if *server == "" {
fail("-server is required")
}
if *token == "" {
fail("-token is required")
}
config := &boringproxy.ClientConfig{
ServerAddr: *server,
Token: *token,
ClientName: *name,
User: *user,
CertDir: *certDir,
AcmeEmail: *acmeEmail,
AcmeUseStaging: *acmeUseStaging,
DnsServer: *dnsServer,
BehindProxy: *behindProxy,
}
ctx := context.Background() ctx := context.Background()

135
config.go Normal file
View File

@ -0,0 +1,135 @@
package boringproxy
import (
"flag"
"fmt"
"os"
)
type myCertConfig struct {
certDir string
acmeEmail string
defaultCA string
autoCerts bool
}
type ServerConfig struct {
adminDomain string
sshServerPort int
dbDir string
printLogin bool
httpPort int
httpsPort int
allowHttp bool
publicIp string
behindProxy bool
myCertConfig myCertConfig
}
type ClientConfig struct {
ServerAddr string `json:"serverAddr,omitempty"`
Token string `json:"token,omitempty"`
ClientName string `json:"clientName,omitempty"`
User string `json:"user,omitempty"`
CertDir string `json:"certDir,omitempty"`
AcmeEmail string `json:"acmeEmail,omitempty"`
AcmeUseStaging bool `json:"acmeUseStaging,omitempty"`
DnsServer string `json:"dnsServer,omitempty"`
BehindProxy bool `json:"behindProxy,omitempty"`
myCertConfig myCertConfig
}
func fail(msg string) {
fmt.Fprintln(os.Stderr, msg)
os.Exit(1)
}
func SetServerConfig() *ServerConfig {
flagSet := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
adminDomain := flagSet.String("admin-domain", "BP_ADMIN_DOMAIN", "Admin Domain")
sshServerPort := flagSet.Int("ssh-server-port", 22, "SSH Server Port")
dbDir := flagSet.String("db-dir", "", "Database file directory")
printLogin := flagSet.Bool("print-login", false, "Prints admin login information")
httpPort := flagSet.Int("http-port", 80, "HTTP (insecure) port")
httpsPort := flagSet.Int("https-port", 443, "HTTPS (secure) port")
allowHttp := flagSet.Bool("allow-http", false, "Allow unencrypted (HTTP) requests")
publicIp := flagSet.String("public-ip", "", "Public IP")
behindProxy := flagSet.Bool("behind-proxy", false, "Whether we're running behind another reverse proxy")
certDir := flagSet.String("cert-dir", "", "TLS cert directory")
acmeEmail := flagSet.String("acme-email", "", "Email for ACME (ie Let's Encrypt)")
defaultCA := flagSet.String("ca", "production", "Default ACME CA")
autoCerts := flagSet.Bool("autocert", true, "Enable/Disable auto certs")
err := flagSet.Parse(os.Args[2:])
if err != nil {
fmt.Fprintf(os.Stderr, "%s: parsing flags: %s\n", os.Args[0], err)
}
var myCertConfig = &myCertConfig{
certDir: *certDir,
acmeEmail: *acmeEmail,
defaultCA: *defaultCA,
autoCerts: *autoCerts,
}
var config = &ServerConfig{
adminDomain: *adminDomain,
sshServerPort: *sshServerPort,
dbDir: *dbDir,
printLogin: *printLogin,
httpPort: *httpPort,
httpsPort: *httpsPort,
allowHttp: *allowHttp,
publicIp: *publicIp,
behindProxy: *behindProxy,
myCertConfig: *myCertConfig,
}
return config
}
func SetClientConfig() *ClientConfig {
flagSet := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
server := flagSet.String("server", "", "boringproxy server")
token := flagSet.String("token", "", "Access token")
name := flagSet.String("client-name", "", "Client name")
user := flagSet.String("user", "", "user")
dnsServer := flagSet.String("dns-server", "", "Custom DNS server")
behindProxy := flagSet.Bool("behind-proxy", false, "Whether we're running behind another reverse proxy")
certDir := flagSet.String("cert-dir", "", "TLS cert directory")
acmeEmail := flagSet.String("acme-email", "", "Email for ACME (ie Let's Encrypt)")
defaultCA := flagSet.String("ca", "production", "Default ACME CA")
autoCerts := flagSet.Bool("autocert", true, "Enable/Disable auto certs")
err := flagSet.Parse(os.Args[2:])
if err != nil {
fmt.Fprintf(os.Stderr, "%s: parsing flags: %s\n", os.Args[0], err)
}
if *server == "" {
fail("-server is required")
}
if *token == "" {
fail("-token is required")
}
var myCertConfig = &myCertConfig{
certDir: *certDir,
acmeEmail: *acmeEmail,
defaultCA: *defaultCA,
autoCerts: *autoCerts,
}
var config = &ClientConfig{
ServerAddr: *server,
Token: *token,
ClientName: *name,
User: *user,
DnsServer: *dnsServer,
BehindProxy: *behindProxy,
myCertConfig: *myCertConfig,
}
return config
}

1
go.mod
View File

@ -6,6 +6,7 @@ go 1.17
require ( require (
github.com/caddyserver/certmagic v0.15.2 github.com/caddyserver/certmagic v0.15.2
github.com/joho/godotenv v1.4.0
github.com/mdp/qrterminal/v3 v3.0.0 github.com/mdp/qrterminal/v3 v3.0.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/takingnames/namedrop-go v0.7.0 github.com/takingnames/namedrop-go v0.7.0

2
go.sum
View File

@ -101,6 +101,8 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=