This commit is contained in:
Willem Grobler 2022-04-12 17:22:46 +02:00 committed by GitHub
commit 2ae343ff68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 257 additions and 111 deletions

View File

@ -4,7 +4,6 @@ import (
"bufio"
"context"
"crypto/tls"
"flag"
"fmt"
"io"
"log"
@ -42,29 +41,11 @@ type Server struct {
httpListener *PassthroughListener
}
func Listen() {
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)
}
func Listen(config *ServerConfig) {
log.Println("Starting up")
db, err := NewDatabase(*dbDir)
db, err := NewDatabase(config.dbDir)
if err != nil {
log.Fatal(err)
}
@ -73,8 +54,8 @@ func Listen() {
var ip string
if *publicIp != "" {
ip = *publicIp
if config.publicIp != "" {
ip = config.publicIp
} else {
ip, err = namedropClient.GetPublicIp()
if err != nil {
@ -82,45 +63,49 @@ func Listen() {
}
}
err = namedrop.CheckPublicAddress(ip, *httpPort)
err = namedrop.CheckPublicAddress(ip, config.httpPort)
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 {
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
if *httpPort != 80 || *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)
var autoCerts bool
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", config.httpPort, config.httpsPort)
autoCerts = false
} else {
autoCerts = config.myCertConfig.autoCerts
}
if *certDir != "" {
certmagic.Default.Storage = &certmagic.FileStorage{*certDir}
}
//certmagic.DefaultACME.DisableHTTPChallenge = true
//certmagic.DefaultACME.DisableTLSALPNChallenge = true
if *acmeEmail != "" {
certmagic.DefaultACME.Email = *acmeEmail
if config.myCertConfig.certDir != "" {
certmagic.Default.Storage = &certmagic.FileStorage{config.myCertConfig.certDir}
}
if *acceptCATerms {
if config.myCertConfig.acmeEmail != "" {
certmagic.DefaultACME.Email = config.myCertConfig.acmeEmail
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
default:
certmagic.DefaultACME.CA = config.myCertConfig.defaultCA
}
certConfig := certmagic.NewDefault()
if *newAdminDomain != "" {
db.SetAdminDomain(*newAdminDomain)
if config.adminDomain != "" {
db.SetAdminDomain(config.adminDomain)
}
adminDomain := db.GetAdminDomain()
@ -152,29 +137,29 @@ func Listen() {
}
if *printLogin {
if config.printLogin {
for token, tokenData := range db.GetTokens() {
if tokenData.Owner == "admin" && tokenData.Client == "" {
printLoginInfo(token, db.GetAdminDomain(), *httpsPort)
printLoginInfo(token, db.GetAdminDomain(), config.httpsPort)
break
}
}
}
config := &Config{
SshServerPort: *sshServerPort,
TunnelManagerConfig := &Config{
SshServerPort: config.sshServerPort,
PublicIp: ip,
namedropClient: namedropClient,
autoCerts: autoCerts,
}
tunMan := NewTunnelManager(config, db, certConfig)
tunMan := NewTunnelManager(TunnelManagerConfig, db, certConfig)
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{
// Don't follow redirects
@ -232,7 +217,7 @@ func Listen() {
host := namedropTokenData.Scopes[0].Host
recordType := "AAAA"
if IsIPv4(config.PublicIp) {
if IsIPv4(TunnelManagerConfig.PublicIp) {
recordType = "A"
}
@ -240,7 +225,7 @@ func Listen() {
Domain: domain,
Host: host,
Type: recordType,
Value: config.PublicIp,
Value: TunnelManagerConfig.PublicIp,
TTL: 300,
}
@ -299,23 +284,23 @@ func Listen() {
return
}
proxyRequest(w, r, tunnel, httpClient, "localhost", tunnel.TunnelPort, *behindProxy)
proxyRequest(w, r, tunnel, httpClient, "localhost", tunnel.TunnelPort, config.behindProxy)
}
})
go func() {
if *allowHttp {
if err := http.ListenAndServe(fmt.Sprintf(":%d", *httpPort), nil); err != nil {
if config.allowHttp {
if err := http.ListenAndServe(fmt.Sprintf(":%d", config.httpPort), nil); err != nil {
log.Fatalf("ListenAndServe error: %v", err)
}
} else {
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)
}
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)
}
}
@ -324,7 +309,7 @@ func Listen() {
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 {
log.Fatal(err)
}

View File

@ -31,18 +31,6 @@ type Client struct {
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) {
if config.DnsServer != "" {
@ -72,16 +60,25 @@ func NewClient(config *ClientConfig) (*Client, error) {
certmagic.DefaultACME.DisableHTTPChallenge = true
if config.CertDir != "" {
certmagic.Default.Storage = &certmagic.FileStorage{config.CertDir}
if config.myCertConfig.certDir != "" {
certmagic.Default.Storage = &certmagic.FileStorage{config.myCertConfig.certDir}
}
if config.AcmeEmail != "" {
certmagic.DefaultACME.Email = config.AcmeEmail
if config.myCertConfig.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
default:
certmagic.DefaultACME.CA = config.myCertConfig.defaultCA
}
certConfig := certmagic.NewDefault()

View File

@ -6,11 +6,13 @@ import (
"flag"
"fmt"
"io"
"log"
"net"
"os"
"sync"
"github.com/boringproxy/boringproxy"
"github.com/joho/godotenv"
)
const usage = `Usage: %s [command] [flags]
@ -30,12 +32,39 @@ func fail(msg string) {
fmt.Fprintln(os.Stderr, msg)
os.Exit(1)
}
func loadEnvFile(filePath string) {
// loads values from .env into the system
var err error
if filePath != "" {
err = godotenv.Load(filePath)
log.Println(fmt.Sprintf("Loading .env file from '%s'", filePath))
} else {
err = godotenv.Load()
log.Println("Loading .env file from working directory")
}
if err != nil {
log.Println("No .env file found")
}
}
func main() {
var env_file string
var flags []string
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, os.Args[0]+": Need a command")
fmt.Printf(usage, os.Args[0])
os.Exit(1)
} else {
flags = os.Args[2:]
if len(os.Args) > 2 {
if os.Args[2] == "config" {
env_file = os.Args[3]
flags = os.Args[4:]
}
}
}
loadEnvFile(env_file)
command := os.Args[1]
@ -83,43 +112,10 @@ func main() {
}
}
case "server":
boringproxy.Listen()
config := boringproxy.SetServerConfig(flags)
boringproxy.Listen(config)
case "client":
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")
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,
}
config := boringproxy.SetClientConfig(flags)
ctx := context.Background()

165
config.go Normal file
View File

@ -0,0 +1,165 @@
package boringproxy
import (
"flag"
"fmt"
"os"
"strconv"
)
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)
}
// Simple helper function to read an environment or return a default value
func getEnv(key string, defaultVal string) string {
if value, exists := os.LookupEnv(key); exists {
return value
}
return defaultVal
}
// Simple helper function to read an environment variable into integer or return a default value
func getEnvAsInt(name string, defaultVal int) int {
valueStr := getEnv(name, "")
if value, err := strconv.Atoi(valueStr); err == nil {
return value
}
return defaultVal
}
// Helper to read an environment variable into a bool or return default value
func getEnvAsBool(name string, defaultVal bool) bool {
valStr := getEnv(name, "")
if val, err := strconv.ParseBool(valStr); err == nil {
return val
}
return defaultVal
}
func SetServerConfig(flags []string) *ServerConfig {
flagSet := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
adminDomain := flagSet.String("admin-domain", getEnv("BP_ADMIN_DOMAIN", ""), "Admin Domain")
sshServerPort := flagSet.Int("ssh-server-port", getEnvAsInt("BP_SSH_SERVER_PORT", 22), "SSH Server Port")
dbDir := flagSet.String("db-dir", getEnv("BP_DB_DIR", ""), "Database file directory")
printLogin := flagSet.Bool("print-login", getEnvAsBool("BP_PRINT_LOGIN", false), "Prints admin login information")
httpPort := flagSet.Int("http-port", getEnvAsInt("BP_HTTP_PORT", 80), "HTTP (insecure) port")
httpsPort := flagSet.Int("https-port", getEnvAsInt("BP_HTTPS_PORT", 443), "HTTPS (secure) port")
allowHttp := flagSet.Bool("allow-http", getEnvAsBool("BP_ALLOW_HTTP", false), "Allow unencrypted (HTTP) requests")
publicIp := flagSet.String("public-ip", getEnv("BP_PUBLIC_IP", ""), "Public IP")
behindProxy := flagSet.Bool("behind-proxy", getEnvAsBool("BP_BEHIND_PROXY", false), "Whether we're running behind another reverse proxy")
certDir := flagSet.String("cert-dir", getEnv("BP_CERT_DIR", ""), "TLS cert directory")
acmeEmail := flagSet.String("acme-email", getEnv("BP_ACME_EMAIL", ""), "Email for ACME (ie Let's Encrypt)")
defaultCA := flagSet.String("ca", getEnv("BP_CA", "production"), "Default ACME CA")
autoCerts := flagSet.Bool("autocert", getEnvAsBool("BP_AUTO_CERTS", false), "Enable/Disable auto certs")
err := flagSet.Parse(flags)
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(flags []string) *ClientConfig {
flagSet := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
server := flagSet.String("server", getEnv("BP_SERVER", ""), "boringproxy server")
token := flagSet.String("token", getEnv("BP_TOKEN", ""), "Access token")
name := flagSet.String("client-name", getEnv("BP_CLIENT_NAME", ""), "Client name")
user := flagSet.String("user", getEnv("BP_USER", ""), "user")
dnsServer := flagSet.String("dns-server", getEnv("BP_DNS_SERVER", ""), "Custom DNS server")
behindProxy := flagSet.Bool("behind-proxy", getEnvAsBool("BP_BEHIND_PROXY", false), "Whether we're running behind another reverse proxy")
certDir := flagSet.String("cert-dir", getEnv("BP_CERT_DIR", ""), "TLS cert directory")
acmeEmail := flagSet.String("acme-email", getEnv("BP_ACME_EMAIL", ""), "Email for ACME (ie Let's Encrypt)")
defaultCA := flagSet.String("ca", getEnv("BP_CA", "production"), "Default ACME CA")
autoCerts := flagSet.Bool("autocert", getEnvAsBool("BP_AUTO_CERTS", false), "Enable/Disable auto certs")
err := flagSet.Parse(flags)
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 (
github.com/caddyserver/certmagic v0.15.2
github.com/joho/godotenv v1.4.0
github.com/mdp/qrterminal/v3 v3.0.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
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.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/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.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=