2020-12-07 22:41:45 -06:00
package boringproxy
2020-09-26 16:41:47 -05:00
import (
2020-10-27 16:47:17 -05:00
"bufio"
2021-12-30 02:00:52 -06:00
"context"
2020-09-29 21:12:54 -05:00
"crypto/tls"
2020-10-27 16:47:17 -05:00
"flag"
2020-09-29 21:12:54 -05:00
"fmt"
"io"
2020-09-26 16:41:47 -05:00
"log"
2020-11-26 23:37:51 -06:00
"net"
2020-09-26 16:41:47 -05:00
"net/http"
2020-10-27 16:47:17 -05:00
"os"
2020-10-18 20:29:57 -05:00
"strings"
2020-11-26 23:37:51 -06:00
"sync"
2020-10-18 20:29:57 -05:00
"time"
2021-01-05 08:04:08 -06:00
"github.com/caddyserver/certmagic"
2021-12-20 01:46:26 -06:00
"github.com/mdp/qrterminal/v3"
2021-12-30 15:52:59 -06:00
"github.com/takingnames/namedrop-go"
2020-09-26 16:41:47 -05:00
)
2020-12-07 22:50:33 -06:00
type Config struct {
2021-12-30 23:05:29 -06:00
SshServerPort int ` json:"ssh_server_port" `
PublicIp string ` json:"public_ip" `
namedropClient * namedrop . Client
2022-01-01 17:32:51 -06:00
autoCerts bool
2020-09-28 15:07:54 -05:00
}
type SmtpConfig struct {
2020-09-29 21:12:54 -05:00
Server string
Port int
Username string
Password string
2020-09-28 15:07:54 -05:00
}
2020-12-07 22:50:33 -06:00
type Server struct {
2020-11-26 23:37:51 -06:00
db * Database
tunMan * TunnelManager
httpClient * http . Client
httpListener * PassthroughListener
2020-09-26 16:41:47 -05:00
}
2020-10-05 19:12:31 -05:00
func Listen ( ) {
2020-10-27 16:47:17 -05:00
flagSet := flag . NewFlagSet ( os . Args [ 0 ] , flag . ExitOnError )
2021-12-17 23:18:55 -06:00
newAdminDomain := flagSet . String ( "admin-domain" , "" , "Admin Domain" )
2020-11-26 21:13:50 -06:00
sshServerPort := flagSet . Int ( "ssh-server-port" , 22 , "SSH Server Port" )
2022-02-21 12:07:07 -06:00
dbDir := flagSet . String ( "db-dir" , "" , "Database file directory" )
2020-11-27 22:20:38 -06:00
certDir := flagSet . String ( "cert-dir" , "" , "TLS cert directory" )
2021-12-20 01:46:26 -06:00
printLogin := flagSet . Bool ( "print-login" , false , "Prints admin login information" )
2021-12-20 13:56:50 -06:00
httpPort := flagSet . Int ( "http-port" , 80 , "HTTP (insecure) port" )
httpsPort := flagSet . Int ( "https-port" , 443 , "HTTPS (secure) port" )
2021-12-20 23:37:50 -06:00
allowHttp := flagSet . Bool ( "allow-http" , false , "Allow unencrypted (HTTP) requests" )
2022-01-05 23:49:42 -06:00
publicIp := flagSet . String ( "public-ip" , "" , "Public IP" )
2022-01-07 13:33:43 -06:00
behindProxy := flagSet . Bool ( "behind-proxy" , false , "Whether we're running behind another reverse proxy" )
2022-02-20 22:32:42 -06:00
acmeEmail := flagSet . String ( "acme-email" , "" , "Email for ACME (ie Let's Encrypt)" )
2022-02-17 15:08:32 -06:00
acmeUseStaging := flagSet . Bool ( "acme-use-staging" , false , "Use ACME (ie Let's Encrypt) staging servers" )
2022-02-23 04:33:36 -06:00
acceptCATerms := flagSet . Bool ( "accept-ca-terms" , false , "Automatically accept CA terms" )
2022-05-11 12:41:42 -05:00
acmeCa := flagSet . String ( "acme-certificate-authority" , "" , "URI for ACME Certificate Authority" )
2021-01-05 08:04:08 -06:00
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" )
2020-09-26 16:41:47 -05:00
2022-02-21 12:07:07 -06:00
db , err := NewDatabase ( * dbDir )
2021-12-15 15:33:01 -06:00
if err != nil {
log . Fatal ( err )
}
2021-12-30 23:05:29 -06:00
namedropClient := namedrop . NewClient ( db , db . GetAdminDomain ( ) , "takingnames.io/namedrop" )
2022-01-05 23:49:42 -06:00
var ip string
if * publicIp != "" {
ip = * publicIp
2022-01-05 23:42:11 -06:00
} else {
2022-01-05 23:49:42 -06:00
ip , err = namedropClient . GetPublicIp ( )
2022-01-05 23:42:11 -06:00
if err != nil {
2022-01-05 23:49:42 -06:00
fmt . Printf ( "WARNING: Failed to determine public IP: %s\n" , err . Error ( ) )
2022-01-05 23:42:11 -06:00
}
2022-01-05 23:49:42 -06:00
}
2021-12-15 15:33:01 -06:00
2022-01-05 23:49:42 -06:00
err = namedrop . CheckPublicAddress ( ip , * httpPort )
if err != nil {
fmt . Printf ( "WARNING: Failed to access %s:%d from the internet\n" , ip , * httpPort )
}
err = namedrop . CheckPublicAddress ( ip , * httpsPort )
if err != nil {
fmt . Printf ( "WARNING: Failed to access %s:%d from the internet\n" , ip , * httpsPort )
2022-01-01 17:32:51 -06:00
}
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 )
autoCerts = false
2020-11-26 21:13:50 -06:00
}
2020-10-27 16:47:17 -05:00
2020-11-27 22:20:38 -06:00
if * certDir != "" {
certmagic . Default . Storage = & certmagic . FileStorage { * certDir }
}
2021-12-17 21:26:20 -06:00
//certmagic.DefaultACME.DisableHTTPChallenge = true
2020-10-27 16:47:17 -05:00
//certmagic.DefaultACME.DisableTLSALPNChallenge = true
2022-02-17 15:08:32 -06:00
2022-02-20 22:32:42 -06:00
if * acmeEmail != "" {
certmagic . DefaultACME . Email = * acmeEmail
}
2022-02-23 04:33:36 -06:00
if * acceptCATerms {
2022-02-23 18:30:02 -06:00
certmagic . DefaultACME . Agreed = true
2022-02-23 04:33:36 -06:00
log . Print ( fmt . Sprintf ( "Automatic agreement to CA terms with email (%s)" , * acmeEmail ) )
}
2022-02-17 15:08:32 -06:00
if * acmeUseStaging {
certmagic . DefaultACME . CA = certmagic . LetsEncryptStagingCA
}
2022-05-11 12:41:42 -05:00
if * acmeCa != "" {
certmagic . DefaultACME . CA = * acmeCa
}
2020-10-27 16:47:17 -05:00
certConfig := certmagic . NewDefault ( )
2021-12-17 23:18:55 -06:00
if * newAdminDomain != "" {
db . SetAdminDomain ( * newAdminDomain )
2020-09-29 21:12:54 -05:00
}
2020-09-28 15:07:54 -05:00
2021-12-17 23:18:55 -06:00
adminDomain := db . GetAdminDomain ( )
2020-10-27 16:47:17 -05:00
2021-12-17 23:18:55 -06:00
if adminDomain == "" {
2022-01-01 17:32:51 -06:00
err = setAdminDomain ( certConfig , db , namedropClient , autoCerts )
2021-12-19 22:24:52 -06:00
if err != nil {
log . Fatal ( err )
}
2021-12-17 23:18:55 -06:00
} else {
2022-01-01 17:32:51 -06:00
if autoCerts {
err = certConfig . ManageSync ( context . Background ( ) , [ ] string { adminDomain } )
if err != nil {
log . Fatal ( err )
}
log . Print ( fmt . Sprintf ( "Successfully acquired certificate for admin domain (%s)" , adminDomain ) )
2021-12-17 23:18:55 -06:00
}
2020-10-06 00:25:36 -05:00
}
2021-12-20 01:46:26 -06:00
// Add admin user if it doesn't already exist
2020-10-13 10:48:03 -05:00
users := db . GetUsers ( )
if len ( users ) == 0 {
db . AddUser ( "admin" , true )
2022-02-19 09:45:38 -06:00
_ , err := db . AddToken ( "admin" , "" )
2020-10-13 10:48:03 -05:00
if err != nil {
log . Fatal ( "Failed to initialize admin user" )
}
2021-12-20 01:46:26 -06:00
}
if * printLogin {
2022-02-24 10:57:14 -06:00
for token , tokenData := range db . GetTokens ( ) {
if tokenData . Owner == "admin" && tokenData . Client == "" {
2022-02-25 00:34:04 -06:00
printLoginInfo ( token , db . GetAdminDomain ( ) , * httpsPort )
2021-12-20 01:46:26 -06:00
break
}
}
2021-12-17 23:18:55 -06:00
}
config := & Config {
2021-12-30 23:05:29 -06:00
SshServerPort : * sshServerPort ,
PublicIp : ip ,
namedropClient : namedropClient ,
2022-01-01 17:32:51 -06:00
autoCerts : autoCerts ,
2020-10-13 10:48:03 -05:00
}
2020-10-08 13:51:52 -05:00
tunMan := NewTunnelManager ( config , db , certConfig )
2020-09-26 16:41:47 -05:00
2020-10-27 16:47:17 -05:00
auth := NewAuth ( db )
2020-09-27 23:24:03 -05:00
2020-10-14 10:17:04 -05:00
api := NewApi ( config , db , auth , tunMan )
2020-09-27 23:24:03 -05:00
2021-12-21 15:05:14 -06:00
webUiHandler := NewWebUiHandler ( config , db , api , auth )
2020-10-11 14:45:46 -05:00
2020-10-29 14:03:35 -05:00
httpClient := & http . Client {
// Don't follow redirects
CheckRedirect : func ( req * http . Request , via [ ] * http . Request ) error {
return http . ErrUseLastResponse
} ,
}
2020-10-06 11:22:03 -05:00
2020-11-26 23:37:51 -06:00
httpListener := NewPassthroughListener ( )
2020-12-07 22:50:33 -06:00
p := & Server { db , tunMan , httpClient , httpListener }
2020-10-02 17:57:09 -05:00
2020-10-06 00:25:36 -05:00
tlsConfig := & tls . Config {
2020-10-05 19:12:31 -05:00
GetCertificate : certConfig . GetCertificate ,
2020-10-07 11:44:37 -05:00
NextProtos : [ ] string { "h2" , "acme-tls/1" } ,
2020-10-06 00:25:36 -05:00
}
2020-11-26 23:37:51 -06:00
tlsListener := tls . NewListener ( httpListener , tlsConfig )
2020-09-29 21:12:54 -05:00
2020-10-05 19:12:31 -05:00
http . HandleFunc ( "/" , func ( w http . ResponseWriter , r * http . Request ) {
2020-10-18 20:29:57 -05:00
timestamp := time . Now ( ) . Format ( time . RFC3339 )
2021-12-20 13:56:50 -06:00
2022-02-11 13:27:36 -06:00
remoteIp , _ , err := net . SplitHostPort ( r . RemoteAddr )
if err != nil {
w . WriteHeader ( 500 )
io . WriteString ( w , err . Error ( ) )
return
}
fmt . Println ( fmt . Sprintf ( "%s %s %s %s %s" , timestamp , remoteIp , r . Method , r . Host , r . URL . Path ) )
2021-12-20 13:56:50 -06:00
hostParts := strings . Split ( r . Host , ":" )
hostDomain := hostParts [ 0 ]
2022-01-04 22:01:33 -06:00
if r . URL . Path == "/namedrop/callback" {
2021-12-18 18:40:59 -06:00
r . ParseForm ( )
2022-01-04 22:01:33 -06:00
errorParam := r . Form . Get ( "error" )
2021-12-30 02:00:52 -06:00
requestId := r . Form . Get ( "state" )
code := r . Form . Get ( "code" )
2021-12-18 18:40:59 -06:00
2022-01-04 22:01:33 -06:00
if errorParam != "" {
db . DeleteDNSRequest ( requestId )
http . Redirect ( w , r , "/alert?message=Domain request failed" , 303 )
return
}
2021-12-30 23:05:29 -06:00
namedropTokenData , err := namedropClient . GetToken ( requestId , code )
2021-12-30 02:00:52 -06:00
if err != nil {
w . WriteHeader ( 500 )
io . WriteString ( w , err . Error ( ) )
return
}
2021-12-30 17:43:13 -06:00
domain := namedropTokenData . Scopes [ 0 ] . Domain
host := namedropTokenData . Scopes [ 0 ] . Host
2022-02-16 13:45:55 -06:00
recordType := "AAAA"
if IsIPv4 ( config . PublicIp ) {
recordType = "A"
}
2021-12-30 17:43:13 -06:00
createRecordReq := namedrop . Record {
Domain : domain ,
Host : host ,
2022-02-16 13:45:55 -06:00
Type : recordType ,
2021-12-30 17:43:13 -06:00
Value : config . PublicIp ,
TTL : 300 ,
}
2021-12-30 23:05:29 -06:00
err = namedropClient . CreateRecord ( createRecordReq )
2021-12-30 17:43:13 -06:00
if err != nil {
w . WriteHeader ( 500 )
io . WriteString ( w , err . Error ( ) )
return
}
fqdn := host + "." + domain
2021-12-19 22:24:52 -06:00
2021-12-30 23:05:29 -06:00
if db . GetAdminDomain ( ) == "" {
2021-12-30 17:43:13 -06:00
db . SetAdminDomain ( fqdn )
2021-12-30 23:05:29 -06:00
namedropClient . SetDomain ( fqdn )
2021-12-30 18:20:55 -06:00
2022-01-01 17:32:51 -06:00
if autoCerts {
// TODO: Might want to get all certs here, not just the admin domain
err := certConfig . ManageSync ( r . Context ( ) , [ ] string { fqdn } )
if err != nil {
log . Fatal ( err )
}
2021-12-19 22:24:52 -06:00
}
2022-01-02 01:20:41 -06:00
url := fmt . Sprintf ( "https://%s" , fqdn )
// Automatically log using the first found admin token. This is safe to do here
2022-01-02 01:22:28 -06:00
// because we know that retrieving the admin domain was initiated from the CLI.
2022-01-02 01:20:41 -06:00
tokens := db . GetTokens ( )
for token , tokenData := range tokens {
if tokenData . Owner == "admin" {
url = url + "/login?access_token=" + token
break
}
}
http . Redirect ( w , r , url , 303 )
2021-12-19 22:24:52 -06:00
} else {
adminDomain := db . GetAdminDomain ( )
2021-12-30 17:43:13 -06:00
http . Redirect ( w , r , fmt . Sprintf ( "https://%s/edit-tunnel?domain=%s" , adminDomain , fqdn ) , 303 )
2021-12-19 22:24:52 -06:00
}
2021-12-20 13:56:50 -06:00
} else if hostDomain == db . GetAdminDomain ( ) {
2020-11-26 20:51:50 -06:00
if strings . HasPrefix ( r . URL . Path , "/api/" ) {
http . StripPrefix ( "/api" , api ) . ServeHTTP ( w , r )
} else {
webUiHandler . handleWebUiRequest ( w , r )
}
2020-10-06 00:25:36 -05:00
} else {
2020-11-27 16:36:07 -06:00
2021-12-20 13:56:50 -06:00
tunnel , exists := db . GetTunnel ( hostDomain )
2020-11-27 16:36:07 -06:00
if ! exists {
2021-12-20 13:56:50 -06:00
errMessage := fmt . Sprintf ( "No tunnel attached to %s" , hostDomain )
2020-11-27 16:36:07 -06:00
w . WriteHeader ( 500 )
io . WriteString ( w , errMessage )
return
}
2022-02-14 09:38:52 -06:00
proxyRequest ( w , r , tunnel , httpClient , "localhost" , tunnel . TunnelPort , * behindProxy )
2020-10-06 00:25:36 -05:00
}
2020-10-05 19:12:31 -05:00
} )
2020-09-26 16:41:47 -05:00
2020-10-09 11:05:31 -05:00
go func ( ) {
2021-12-20 23:37:50 -06:00
if * allowHttp {
if err := http . ListenAndServe ( fmt . Sprintf ( ":%d" , * 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 )
http . Redirect ( w , r , url , http . StatusMovedPermanently )
}
if err := http . ListenAndServe ( fmt . Sprintf ( ":%d" , * httpPort ) , http . HandlerFunc ( redirectTLS ) ) ; err != nil {
log . Fatalf ( "ListenAndServe error: %v" , err )
}
2020-10-09 11:05:31 -05:00
}
2021-12-20 23:37:50 -06:00
2020-10-09 11:05:31 -05:00
} ( )
2020-10-07 11:44:37 -05:00
2020-11-26 23:37:51 -06:00
go http . Serve ( tlsListener , nil )
2021-12-20 13:56:50 -06:00
listener , err := net . Listen ( "tcp" , fmt . Sprintf ( ":%d" , * httpsPort ) )
2020-11-26 23:37:51 -06:00
if err != nil {
log . Fatal ( err )
}
2021-12-15 15:33:01 -06:00
log . Println ( "Ready" )
2020-11-26 23:37:51 -06:00
for {
conn , err := listener . Accept ( )
if err != nil {
log . Print ( err )
continue
}
2022-02-24 15:12:09 -06:00
go p . handleConnection ( conn , certConfig )
2020-11-26 23:37:51 -06:00
}
}
2022-02-24 15:12:09 -06:00
func ( p * Server ) handleConnection ( clientConn net . Conn , certConfig * certmagic . Config ) {
2020-11-26 23:37:51 -06:00
clientHello , clientReader , err := peekClientHello ( clientConn )
if err != nil {
log . Println ( "peekClientHello error" , err )
return
}
passConn := NewProxyConn ( clientConn , clientReader )
tunnel , exists := p . db . GetTunnel ( clientHello . ServerName )
2022-02-11 14:04:59 -06:00
if exists && ( tunnel . TlsTermination == "client" || tunnel . TlsTermination == "passthrough" ) || tunnel . TlsTermination == "client-tls" {
2020-11-26 23:37:51 -06:00
p . passthroughRequest ( passConn , tunnel )
2022-02-24 15:12:09 -06:00
} else if exists && tunnel . TlsTermination == "server-tls" {
useTls := true
err := ProxyTcp ( passConn , "127.0.0.1" , tunnel . TunnelPort , useTls , certConfig )
if err != nil {
log . Println ( err . Error ( ) )
return
}
2020-11-26 23:37:51 -06:00
} else {
p . httpListener . PassConn ( passConn )
}
}
2020-12-07 22:50:33 -06:00
func ( p * Server ) passthroughRequest ( conn net . Conn , tunnel Tunnel ) {
2020-11-26 23:37:51 -06:00
upstreamAddr := fmt . Sprintf ( "localhost:%d" , tunnel . TunnelPort )
upstreamConn , err := net . Dial ( "tcp" , upstreamAddr )
if err != nil {
log . Print ( err )
return
}
defer upstreamConn . Close ( )
var wg sync . WaitGroup
wg . Add ( 2 )
go func ( ) {
io . Copy ( conn , upstreamConn )
conn . ( * ProxyConn ) . CloseWrite ( )
wg . Done ( )
} ( )
go func ( ) {
io . Copy ( upstreamConn , conn )
upstreamConn . ( * net . TCPConn ) . CloseWrite ( )
wg . Done ( )
} ( )
wg . Wait ( )
2020-10-05 19:12:31 -05:00
}
2021-12-17 23:18:55 -06:00
2022-01-01 17:32:51 -06:00
func setAdminDomain ( certConfig * certmagic . Config , db * Database , namedropClient * namedrop . Client , autoCerts bool ) error {
2022-01-04 19:29:33 -06:00
action := prompt ( "\nNo admin domain set. Select an option below:\nEnter '1' to input manually\nEnter '2' to configure through TakingNames.io\n" )
2021-12-17 23:18:55 -06:00
switch action {
case "1" :
2021-12-19 22:24:52 -06:00
adminDomain := prompt ( "\nEnter admin domain:\n" )
2021-12-17 23:18:55 -06:00
2022-01-01 17:32:51 -06:00
if autoCerts {
err := certConfig . ManageSync ( context . Background ( ) , [ ] string { adminDomain } )
if err != nil {
log . Fatal ( err )
}
2021-12-17 23:18:55 -06:00
}
2021-12-19 22:24:52 -06:00
db . SetAdminDomain ( adminDomain )
case "2" :
2021-12-17 23:18:55 -06:00
2021-12-19 22:24:52 -06:00
log . Println ( "Get bootstrap domain" )
2021-12-17 23:18:55 -06:00
2021-12-30 21:57:28 -06:00
namedropLink , err := namedropClient . BootstrapLink ( )
2021-12-19 22:24:52 -06:00
if err != nil {
log . Fatal ( err )
}
2021-12-17 23:18:55 -06:00
2022-01-04 19:29:33 -06:00
qrterminal . GenerateHalfBlock ( namedropLink , qrterminal . L , os . Stdout )
fmt . Println ( "Use the link below or scan the QR code above to select an admin domain:\n" )
fmt . Printf ( "%s\n\n" , namedropLink )
2021-12-17 23:18:55 -06:00
default :
log . Fatal ( "Invalid option" )
}
2021-12-19 22:24:52 -06:00
return nil
2021-12-17 23:18:55 -06:00
}
func prompt ( promptText string ) string {
reader := bufio . NewReader ( os . Stdin )
fmt . Print ( promptText )
text , _ := reader . ReadString ( '\n' )
return strings . TrimSpace ( text )
}
2021-12-20 01:46:26 -06:00
2022-02-25 00:34:04 -06:00
func printLoginInfo ( token , adminDomain string , httpsPort int ) {
var url string
if httpsPort != 443 {
url = fmt . Sprintf ( "https://%s:%d/login?access_token=%s" , adminDomain , httpsPort , token )
} else {
url = fmt . Sprintf ( "https://%s/login?access_token=%s" , adminDomain , token )
}
2021-12-20 01:46:26 -06:00
log . Println ( fmt . Sprintf ( "Admin login link: %s" , url ) )
qrterminal . GenerateHalfBlock ( url , qrterminal . L , os . Stdout )
}
2022-02-16 13:45:55 -06:00
// Taken from https://stackoverflow.com/a/48519490/943814
func IsIPv4 ( address string ) bool {
return strings . Count ( address , ":" ) < 2
}