2020-06-03 07:47:06 +00:00
//go:generate go run -mod=vendor git.rootprojects.org/root/go-gitver
2017-03-29 23:49:02 +00:00
package main
import (
2017-03-31 00:04:28 +00:00
"context"
2020-06-03 07:47:06 +00:00
"encoding/base64"
"encoding/hex"
2020-07-22 08:09:06 +00:00
"encoding/json"
2020-05-05 06:44:21 +00:00
"flag"
2017-04-04 18:09:20 +00:00
"fmt"
2020-06-22 06:34:42 +00:00
"io"
2020-05-05 06:44:21 +00:00
"log"
2020-06-03 07:47:06 +00:00
"net"
"net/url"
2020-05-05 06:44:21 +00:00
"os"
2020-06-22 06:34:42 +00:00
"regexp"
2020-06-09 00:58:52 +00:00
"strconv"
2017-04-04 18:09:20 +00:00
"strings"
2020-06-03 07:47:06 +00:00
"time"
2017-03-31 00:04:28 +00:00
2020-07-21 07:59:32 +00:00
telebit "git.rootprojects.org/root/telebit"
2020-07-20 22:20:59 +00:00
"git.rootprojects.org/root/telebit/dbg"
2020-07-21 07:59:32 +00:00
tbDns01 "git.rootprojects.org/root/telebit/dns01"
2020-07-20 22:20:59 +00:00
"git.rootprojects.org/root/telebit/mgmt"
"git.rootprojects.org/root/telebit/mgmt/authstore"
"git.rootprojects.org/root/telebit/table"
2020-07-22 04:42:21 +00:00
"git.rootprojects.org/root/telebit/tunnel"
2020-07-06 09:56:29 +00:00
legoDns01 "github.com/go-acme/lego/v3/challenge/dns01"
2020-05-05 06:44:21 +00:00
2020-05-06 17:11:13 +00:00
"github.com/caddyserver/certmagic"
2020-06-03 07:47:06 +00:00
"github.com/denisbrodbeck/machineid"
2017-03-29 23:49:02 +00:00
jwt "github.com/dgrijalva/jwt-go"
2020-06-03 07:47:06 +00:00
"github.com/go-acme/lego/v3/challenge"
2020-05-06 17:11:13 +00:00
"github.com/go-acme/lego/v3/providers/dns/duckdns"
2020-06-03 07:47:06 +00:00
"github.com/go-acme/lego/v3/providers/dns/godaddy"
2020-05-05 06:44:21 +00:00
_ "github.com/joho/godotenv/autoload"
2017-03-29 23:49:02 +00:00
)
2020-08-17 17:35:00 +00:00
const (
// exitOk is for normal exits, such as a graceful disconnect or shutdown
exitOk = 0
// exitBadArguments is for positive failures as a result of arguments
exitBadArguments = 1
// exitBadConfig is for positive failures from an external service
exitBadConfig = 2
// exitRetry is for potentially false negative failures from temporary conditions such as a DNS resolution or network failure for which it would be reasonable to wait 10 seconds and try again
exitRetry = 29
)
2020-06-03 07:47:06 +00:00
var (
// GitRev refers to the abbreviated commit hash
GitRev = "0000000"
// GitVersion refers to the most recent tag, plus any commits made since then
GitVersion = "v0.0.0-pre0+0000000"
// GitTimestamp refers to the timestamp of the most recent commit
GitTimestamp = "0000-00-00T00:00:00+0000"
)
2017-04-04 18:09:20 +00:00
2020-06-03 07:47:06 +00:00
type Forward struct {
2020-08-13 08:34:39 +00:00
scheme string
pattern string
port string
localTLS bool
2017-04-04 18:09:20 +00:00
}
2020-06-22 06:34:42 +00:00
var authorizer telebit . Authorizer
var isHostname = regexp . MustCompile ( ` ^[A-Za-z0-9_\.\-]+$ ` ) . MatchString
2020-07-20 15:27:31 +00:00
// VendorID may be baked in, or supplied via ENVs or --args
var VendorID string
2020-07-18 03:51:48 +00:00
// ClientSecret may be baked in, or supplied via ENVs or --args
2020-07-17 07:50:55 +00:00
var ClientSecret string
2020-06-03 07:47:06 +00:00
func main ( ) {
var domains [ ] string
var forwards [ ] Forward
2020-06-09 08:42:56 +00:00
var portForwards [ ] Forward
2020-06-03 07:47:06 +00:00
// TODO replace the websocket connection with a mock server
2020-07-20 15:27:31 +00:00
vendorID := flag . String ( "vendor-id" , "" , "a unique identifier for a deploy target environment" )
2020-06-03 07:47:06 +00:00
email := flag . String ( "acme-email" , "" , "email to use for Let's Encrypt / ACME registration" )
certpath := flag . String ( "acme-storage" , "./acme.d/" , "path to ACME storage directory" )
acmeAgree := flag . Bool ( "acme-agree" , false , "agree to the terms of the ACME service provider (required)" )
acmeStaging := flag . Bool ( "acme-staging" , false , "get fake certificates for testing" )
acmeDirectory := flag . String ( "acme-directory" , "" , "ACME Directory URL" )
enableHTTP01 := flag . Bool ( "acme-http-01" , false , "enable HTTP-01 ACME challenges" )
enableTLSALPN01 := flag . Bool ( "acme-tls-alpn-01" , false , "enable TLS-ALPN-01 ACME challenges" )
2020-07-17 05:41:04 +00:00
acmeRelay := flag . String ( "acme-relay-url" , "" , "the base url of the ACME DNS-01 relay, if not the same as the tunnel relay" )
2020-06-03 07:47:06 +00:00
authURL := flag . String ( "auth-url" , "" , "the base url for authentication, if not the same as the tunnel relay" )
2020-07-18 05:41:08 +00:00
relay := flag . String ( "tunnel-relay-url" , "" , "the websocket url at which to connect to the tunnel relay" )
2020-07-17 05:41:04 +00:00
apiHostname := flag . String ( "api-hostname" , "" , "the hostname used to manage clients" )
2020-06-03 07:47:06 +00:00
secret := flag . String ( "secret" , "" , "the same secret used by telebit-relay (used for JWT authentication)" )
token := flag . String ( "token" , "" , "a pre-generated token to give the server (instead of generating one with --secret)" )
2020-06-09 00:58:52 +00:00
bindAddrsStr := flag . String ( "listen" , "" , "list of bind addresses on which to listen, such as localhost:80, or :443" )
2020-08-13 08:34:39 +00:00
tlsLocals := flag . String ( "tls-locals" , "" , "like --locals, but TLS will be used to connect to the local port" )
2020-06-03 07:47:06 +00:00
locals := flag . String ( "locals" , "" , "a list of <from-domain>:<to-port>" )
2020-06-09 08:42:56 +00:00
portToPorts := flag . String ( "port-forward" , "" , "a list of <from-port>:<to-port> for raw port-forwarding" )
2020-07-18 03:51:48 +00:00
verbose := flag . Bool ( "verbose" , false , "log excessively" )
2020-06-03 07:47:06 +00:00
flag . Parse ( )
2020-05-01 08:18:47 +00:00
2020-07-18 03:51:48 +00:00
if ! dbg . Debug {
2020-07-18 05:28:12 +00:00
dbg . Debug = * verbose
2020-07-18 03:51:48 +00:00
}
2020-06-03 07:47:06 +00:00
if len ( os . Args ) >= 2 {
if "version" == os . Args [ 1 ] {
2020-07-21 08:06:11 +00:00
fmt . Printf ( "telebit %s %s %s\n" , GitVersion , GitRev [ : 7 ] , GitTimestamp )
2020-08-17 17:35:00 +00:00
os . Exit ( exitOk )
2017-04-04 18:09:20 +00:00
}
2020-05-01 08:18:47 +00:00
}
2020-06-09 00:58:52 +00:00
if len ( * acmeDirectory ) > 0 {
2020-06-03 07:47:06 +00:00
if * acmeStaging {
fmt . Fprintf ( os . Stderr , "pick either acme-directory or acme-staging\n" )
2020-08-17 17:35:00 +00:00
os . Exit ( exitBadArguments )
2020-06-09 00:58:52 +00:00
return
2020-05-01 08:18:47 +00:00
}
2017-04-04 18:09:20 +00:00
}
2020-06-03 07:47:06 +00:00
if * acmeStaging {
* acmeDirectory = certmagic . LetsEncryptStagingCA
2017-04-04 18:09:20 +00:00
}
2020-07-09 09:37:05 +00:00
if ! * acmeAgree {
if "true" == os . Getenv ( "ACME_AGREE" ) {
* acmeAgree = true
}
}
2020-07-22 04:42:21 +00:00
if 0 == len ( * acmeRelay ) {
* acmeRelay = os . Getenv ( "ACME_RELAY_URL" )
}
2020-07-09 09:37:05 +00:00
if 0 == len ( * email ) {
* email = os . Getenv ( "ACME_EMAIL" )
}
2017-04-04 18:09:20 +00:00
2020-06-09 00:58:52 +00:00
if 0 == len ( * locals ) {
2020-06-03 07:47:06 +00:00
* locals = os . Getenv ( "LOCALS" )
}
for _ , cfg := range strings . Fields ( strings . ReplaceAll ( * locals , "," , " " ) ) {
parts := strings . Split ( cfg , ":" )
last := len ( parts ) - 1
port := parts [ last ]
domain := parts [ last - 1 ]
scheme := ""
if len ( parts ) > 2 {
scheme = parts [ 0 ]
2020-05-01 08:18:47 +00:00
}
2020-06-03 07:47:06 +00:00
forwards = append ( forwards , Forward {
scheme : scheme ,
pattern : domain ,
port : port ,
} )
// don't load wildcard into jwt domains
if "*" == domain {
continue
2020-05-01 08:18:47 +00:00
}
2020-06-03 07:47:06 +00:00
domains = append ( domains , domain )
2017-04-04 18:09:20 +00:00
}
2020-05-01 08:18:47 +00:00
2020-08-13 08:34:39 +00:00
if 0 == len ( * tlsLocals ) {
* tlsLocals = os . Getenv ( "TLS_LOCALS" )
}
for _ , cfg := range strings . Fields ( strings . ReplaceAll ( * tlsLocals , "," , " " ) ) {
parts := strings . Split ( cfg , ":" )
last := len ( parts ) - 1
port := parts [ last ]
domain := parts [ last - 1 ]
scheme := ""
if len ( parts ) > 2 {
scheme = parts [ 0 ]
}
forwards = append ( forwards , Forward {
scheme : scheme ,
pattern : domain ,
port : port ,
localTLS : true ,
} )
// don't load wildcard into jwt domains
if "*" == domain {
continue
}
domains = append ( domains , domain )
}
2020-06-09 08:42:56 +00:00
if 0 == len ( * portToPorts ) {
* portToPorts = os . Getenv ( "PORT_FORWARDS" )
}
portForwards , err := parsePortForwards ( portToPorts )
if nil != err {
fmt . Fprintf ( os . Stderr , "%s" , err )
2020-08-17 17:35:00 +00:00
os . Exit ( exitBadArguments )
2020-06-09 08:42:56 +00:00
return
}
2020-07-17 05:41:04 +00:00
if 0 == len ( * bindAddrsStr ) {
* bindAddrsStr = os . Getenv ( "LISTEN" )
}
2020-06-09 00:58:52 +00:00
bindAddrs , err := parseBindAddrs ( * bindAddrsStr )
2020-06-03 07:47:06 +00:00
if nil != err {
2020-06-09 00:58:52 +00:00
fmt . Fprintf ( os . Stderr , "invalid bind address(es) given to --listen\n" )
2020-08-17 17:35:00 +00:00
os . Exit ( exitBadArguments )
2020-06-09 00:58:52 +00:00
return
2017-04-04 18:09:20 +00:00
}
2020-07-17 07:50:55 +00:00
// Baked-in takes precedence
2020-07-20 15:27:31 +00:00
if 0 == len ( VendorID ) {
VendorID = * vendorID
} else if 0 != len ( * vendorID ) {
if VendorID != * vendorID {
fmt . Fprintf ( os . Stderr , "invalid --vendor-id\n" )
2020-08-17 17:35:00 +00:00
os . Exit ( exitBadArguments )
2020-07-17 08:32:23 +00:00
}
2020-07-17 07:50:55 +00:00
}
2020-07-20 15:27:31 +00:00
if 0 == len ( VendorID ) {
VendorID = os . Getenv ( "VENDOR_ID" )
} else if 0 != len ( os . Getenv ( "VENDOR_ID" ) ) {
if VendorID != os . Getenv ( "VENDOR_ID" ) {
fmt . Fprintf ( os . Stderr , "invalid VENDOR_ID\n" )
2020-08-17 17:35:00 +00:00
os . Exit ( exitBadArguments )
2020-07-17 08:32:23 +00:00
}
2020-07-17 07:50:55 +00:00
}
if 0 == len ( ClientSecret ) {
ClientSecret = * secret
2020-07-17 08:32:23 +00:00
} else if 0 != len ( * secret ) {
if ClientSecret != * secret {
fmt . Fprintf ( os . Stderr , "invalid --secret\n" )
2020-08-17 17:35:00 +00:00
os . Exit ( exitBadArguments )
2020-07-17 08:32:23 +00:00
}
2017-04-04 18:09:20 +00:00
}
2020-07-17 07:50:55 +00:00
if 0 == len ( ClientSecret ) {
ClientSecret = os . Getenv ( "SECRET" )
2020-07-17 08:32:23 +00:00
} else if 0 != len ( os . Getenv ( "SECRET" ) ) {
if ClientSecret != os . Getenv ( "SECRET" ) {
fmt . Fprintf ( os . Stderr , "invalid SECRET\n" )
2020-08-17 17:35:00 +00:00
os . Exit ( exitBadArguments )
2020-07-17 08:32:23 +00:00
}
2020-07-17 07:50:55 +00:00
}
2020-07-20 15:27:31 +00:00
ppid , err := machineid . ProtectedID ( fmt . Sprintf ( "%s|%s" , VendorID , ClientSecret ) )
2020-06-03 07:47:06 +00:00
if nil != err {
2020-06-09 00:58:52 +00:00
fmt . Fprintf ( os . Stderr , "unauthorized device\n" )
2020-08-17 17:35:00 +00:00
os . Exit ( exitBadConfig )
2020-06-03 07:47:06 +00:00
return
2017-04-04 18:09:20 +00:00
}
2020-06-09 00:58:52 +00:00
ppidBytes , err := hex . DecodeString ( ppid )
ppid = base64 . RawURLEncoding . EncodeToString ( ppidBytes )
2017-04-04 18:09:20 +00:00
2020-07-17 07:50:55 +00:00
if 0 == len ( * token ) {
* token = os . Getenv ( "TOKEN" )
}
2020-06-09 00:58:52 +00:00
if 0 == len ( * token ) {
* token , err = authstore . HMACToken ( ppid )
2020-07-19 08:16:11 +00:00
if dbg . Debug {
2020-07-20 15:27:31 +00:00
fmt . Printf ( "[debug] app_id: %q\n" , VendorID )
2020-07-19 08:16:11 +00:00
//fmt.Printf("[debug] client_secret: %q\n", ClientSecret)
//fmt.Printf("[debug] ppid: %q\n", ppid)
//fmt.Printf("[debug] ppid: [redacted]\n")
fmt . Printf ( "[debug] token: %q\n" , * token )
}
2020-06-09 00:58:52 +00:00
if nil != err {
fmt . Fprintf ( os . Stderr , "neither secret nor token provided\n" )
2020-08-17 17:35:00 +00:00
os . Exit ( exitBadArguments )
2020-06-09 00:58:52 +00:00
return
}
}
if 0 == len ( * relay ) {
2020-07-18 05:28:12 +00:00
* relay = os . Getenv ( "TUNNEL_RELAY_URL" ) // "wss://example.com:443"
2017-04-04 18:09:20 +00:00
}
2020-06-09 00:58:52 +00:00
if 0 == len ( * relay ) {
2020-07-06 09:56:29 +00:00
if len ( bindAddrs ) > 0 {
fmt . Fprintf ( os . Stderr , "Acting as Relay\n" )
} else {
2020-07-17 05:41:04 +00:00
fmt . Fprintf ( os . Stderr , "error: must provide Relay, or act as Relay\n" )
2020-08-17 17:35:00 +00:00
os . Exit ( exitBadArguments )
2020-07-06 09:56:29 +00:00
return
}
2020-05-05 06:44:21 +00:00
}
2020-07-06 09:56:29 +00:00
if 0 == len ( * authURL ) {
* authURL = os . Getenv ( "AUTH_URL" )
}
2020-07-19 06:52:17 +00:00
var grants * telebit . Grants
2020-07-17 05:41:04 +00:00
if len ( * relay ) > 0 /* || len(*acmeRelay) > 0 */ {
2020-07-22 04:42:21 +00:00
directory , err := tunnel . Discover ( * relay )
if nil != err {
fmt . Fprintf ( os . Stderr , "Error: invalid Tunnel Relay URL %q: %s\n" , * relay , err )
2020-08-17 17:35:00 +00:00
os . Exit ( exitRetry )
2020-07-22 04:42:21 +00:00
}
2020-07-22 08:09:06 +00:00
fmt . Printf ( "[Directory] %s\n" , * relay )
jsonb , _ := json . Marshal ( directory )
fmt . Printf ( "\t%s\n" , string ( jsonb ) )
2020-07-22 04:42:21 +00:00
2020-08-17 17:35:00 +00:00
// TODO trimming this should no longer be necessary, but I need to double check
2020-07-22 05:47:47 +00:00
authBase := strings . TrimSuffix ( directory . Authenticate . URL , "/inspect" )
2020-06-09 00:58:52 +00:00
if "" == * authURL {
2020-07-22 05:47:47 +00:00
* authURL = authBase
2020-07-22 04:42:21 +00:00
} else {
2020-07-22 05:47:47 +00:00
fmt . Println ( "Suggested Auth URL:" , authBase )
2020-07-22 04:42:21 +00:00
fmt . Println ( "--auth-url Auth URL:" , * authURL )
2017-04-04 18:09:20 +00:00
}
2020-07-22 04:42:21 +00:00
if "" == * authURL {
fmt . Fprintf ( os . Stderr , "Discovered Directory Endpoints: %+v\n" , directory )
2020-07-22 08:09:06 +00:00
fmt . Fprintf ( os . Stderr , "No Auth URL detected, nor supplied\n" )
2020-08-17 17:35:00 +00:00
os . Exit ( exitBadConfig )
2020-07-22 04:42:21 +00:00
return
}
2020-07-09 09:37:05 +00:00
fmt . Println ( "Auth URL" , * authURL )
authorizer = NewAuthorizer ( * authURL )
2020-07-22 08:09:06 +00:00
dns01Base := directory . DNS01Proxy . URL
if "" == * acmeRelay {
* acmeRelay = dns01Base
} else {
fmt . Println ( "Suggested ACME DNS 01 Proxy URL:" , dns01Base )
fmt . Println ( "--acme-relay-url ACME DNS 01 Proxy URL:" , * acmeRelay )
}
2020-08-17 17:35:00 +00:00
if "" == * acmeRelay {
2020-07-22 08:09:06 +00:00
fmt . Fprintf ( os . Stderr , "Discovered Directory Endpoints: %+v\n" , directory )
fmt . Fprintf ( os . Stderr , "No ACME DNS 01 Proxy URL detected, nor supplied\n" )
2020-08-17 17:35:00 +00:00
os . Exit ( exitBadConfig )
2020-07-22 08:09:06 +00:00
return
}
fmt . Println ( "DNS 01 URL" , * acmeRelay )
2020-07-19 06:52:17 +00:00
grants , err = telebit . Inspect ( * authURL , * token )
2020-06-09 00:58:52 +00:00
if nil != err {
2020-08-14 09:38:29 +00:00
if dbg . Debug {
fmt . Fprintf ( os . Stderr , "failed to inspect token: %s\n" , err )
}
2020-07-17 07:50:55 +00:00
_ , err := mgmt . Register ( * authURL , ClientSecret , ppid )
2020-06-09 00:58:52 +00:00
if nil != err {
2020-08-17 21:13:13 +00:00
if strings . Contains ( err . Error ( ) , ` "E_NOT_FOUND" ` ) {
2020-08-14 09:38:29 +00:00
fmt . Fprintf ( os . Stderr , "invalid client credentials: %s\n" , err )
2020-08-17 17:35:00 +00:00
// the server confirmed that the client is bad
os . Exit ( exitBadConfig )
2020-08-14 09:38:29 +00:00
} else {
2020-08-17 17:35:00 +00:00
// there may have been a network error
2020-08-14 09:38:29 +00:00
fmt . Fprintf ( os . Stderr , "failed to register client: %s\n" , err )
2020-08-17 17:35:00 +00:00
os . Exit ( exitRetry )
2020-08-14 09:38:29 +00:00
}
return
2020-06-09 00:58:52 +00:00
}
grants , err = telebit . Inspect ( * authURL , * token )
if nil != err {
fmt . Fprintf ( os . Stderr , "failed to authenticate after registering client: %s\n" , err )
2020-08-17 17:35:00 +00:00
// there was no error registering the client, yet there was one authenticating
// therefore this may be an error that will be resolved
os . Exit ( exitRetry )
2020-06-09 00:58:52 +00:00
}
2017-04-04 18:09:20 +00:00
}
2020-07-22 04:42:21 +00:00
fmt . Printf ( "[Grants]\n\t%#v\n" , grants )
* relay = grants . Audience
2017-04-04 18:09:20 +00:00
}
2020-07-06 09:56:29 +00:00
authorizer = NewAuthorizer ( * authURL )
2017-04-04 18:09:20 +00:00
2020-06-09 00:58:52 +00:00
provider , err := getACMEProvider ( acmeRelay , token )
2020-06-03 07:47:06 +00:00
if nil != err {
2020-06-09 00:58:52 +00:00
fmt . Fprintf ( os . Stderr , "%s\n" , err )
2020-08-17 17:35:00 +00:00
// it's possible for some providers this could be a failed network request,
// but I think in the case of what we specifically support it's bad arguments
os . Exit ( exitBadArguments )
2020-06-09 00:58:52 +00:00
return
2020-05-05 06:44:21 +00:00
}
2020-07-06 09:56:29 +00:00
fmt . Printf ( "Email: %q\n" , * email )
2020-06-03 07:47:06 +00:00
acme := & telebit . ACME {
2020-07-06 09:56:29 +00:00
Email : * email ,
StoragePath : * certpath ,
Agree : * acmeAgree ,
Directory : * acmeDirectory ,
DNSProvider : provider ,
//DNSChallengeOption: legoDns01.DNSProviderOption,
DNSChallengeOption : legoDns01 . WrapPreCheck ( func ( domain , fqdn , value string , orig legoDns01 . PreCheckFunc ) ( bool , error ) {
ok , err := orig ( fqdn , value )
if ok {
fmt . Println ( "[Telebit-ACME-DNS] sleeping an additional 5 seconds" )
time . Sleep ( 5 * time . Second )
}
return ok , err
} ) ,
2020-06-03 07:47:06 +00:00
EnableHTTPChallenge : * enableHTTP01 ,
EnableTLSALPNChallenge : * enableTLSALPN01 ,
2017-04-04 18:09:20 +00:00
}
2020-07-22 05:23:49 +00:00
mux := muxAll ( portForwards , forwards , acme , apiHostname , authURL , grants )
2020-05-05 06:44:21 +00:00
2020-06-09 00:58:52 +00:00
done := make ( chan error )
2020-07-18 03:51:48 +00:00
if dbg . Debug {
fmt . Println ( "[debug] bindAddrs" , bindAddrs , * bindAddrsStr )
}
2020-06-09 00:58:52 +00:00
for _ , addr := range bindAddrs {
2020-06-29 08:43:46 +00:00
go func ( addr string ) {
2020-06-09 00:58:52 +00:00
fmt . Printf ( "Listening on %s\n" , addr )
ln , err := net . Listen ( "tcp" , addr )
if nil != err {
fmt . Fprintf ( os . Stderr , "failed to bind to %q: %s" , addr , err )
done <- err
return
}
if err := telebit . Serve ( ln , mux ) ; nil != err {
fmt . Fprintf ( os . Stderr , "failed to bind to %q: %s" , addr , err )
done <- err
return
}
2020-06-29 08:43:46 +00:00
} ( addr )
2020-06-09 00:58:52 +00:00
}
//connected := make(chan net.Conn)
2020-06-03 07:47:06 +00:00
go func ( ) {
2020-06-09 00:58:52 +00:00
if "" == * relay {
return
}
2020-06-03 07:47:06 +00:00
timeoutCtx , cancel := context . WithDeadline ( context . Background ( ) , time . Now ( ) . Add ( 10 * time . Second ) )
defer cancel ( )
tun , err := telebit . DialWebsocketTunnel ( timeoutCtx , * relay , * token )
if nil != err {
msg := ""
if strings . Contains ( err . Error ( ) , "bad handshake" ) {
msg = " (may be auth related)"
}
fmt . Fprintf ( os . Stderr , "Error connecting to %s: %s%s\n" , * relay , err , msg )
2020-08-17 17:35:00 +00:00
os . Exit ( exitRetry )
2020-06-03 07:47:06 +00:00
return
2017-04-04 18:09:20 +00:00
}
2020-06-03 07:47:06 +00:00
err = mgmt . Ping ( * authURL , * token )
if nil != err {
2020-06-09 00:58:52 +00:00
fmt . Fprintf ( os . Stderr , "failed to ping mgmt server: %s\n" , err )
2020-08-17 17:35:00 +00:00
//os.Exit(exitRetry)
2017-04-04 18:09:20 +00:00
}
2017-03-29 23:49:02 +00:00
2020-06-09 00:58:52 +00:00
go func ( ) {
for {
time . Sleep ( 10 * time . Minute )
2020-07-22 05:47:47 +00:00
if "" != ClientSecret {
// re-create token unless no secret was supplied
* token , err = authstore . HMACToken ( ppid )
}
2020-06-09 00:58:52 +00:00
err = mgmt . Ping ( * authURL , * token )
if nil != err {
fmt . Fprintf ( os . Stderr , "failed to ping mgmt server: %s\n" , err )
2020-08-17 17:35:00 +00:00
//os.Exit(exitRetry)
2020-06-09 00:58:52 +00:00
}
}
} ( )
//connected <- tun
//tun := <-connected
2020-06-29 08:43:46 +00:00
fmt . Printf ( "Listening through %s\n" , * relay )
2020-06-09 00:58:52 +00:00
err = telebit . ListenAndServe ( tun , mux )
log . Fatal ( "Closed server: " , err )
done <- err
2020-06-03 07:47:06 +00:00
} ( )
2020-05-06 17:11:13 +00:00
2020-06-09 00:58:52 +00:00
if err := <- done ; nil != err {
2020-08-17 17:35:00 +00:00
os . Exit ( exitRetry )
2020-06-09 00:58:52 +00:00
}
}
2020-07-19 06:52:17 +00:00
func muxAll (
portForwards , forwards [ ] Forward ,
acme * telebit . ACME ,
2020-07-22 05:23:49 +00:00
apiHostname , authURL * string ,
2020-07-19 06:52:17 +00:00
grants * telebit . Grants ,
) * telebit . RouteMux {
2020-07-18 06:18:09 +00:00
//mux := telebit.NewRouteMux(acme)
mux := telebit . NewRouteMux ( )
// Port forward without TerminatingTLS
for _ , fwd := range portForwards {
msg := fmt . Sprintf ( "Fwd: %s %s" , fwd . pattern , fwd . port )
fmt . Println ( msg )
mux . ForwardTCP ( fwd . pattern , "localhost:" + fwd . port , 120 * time . Second , msg , "[Port Forward]" )
}
// TODO close connection on invalid hostname
mux . HandleTCP ( "*" , telebit . HandlerFunc ( routeSubscribersAndClients ) , "[Tun => Remote Servers]" )
if 0 == len ( * apiHostname ) {
* apiHostname = os . Getenv ( "API_HOSTNAME" )
}
if "" != * apiHostname {
2020-07-22 04:42:21 +00:00
// this is a generic net listener
2020-07-22 05:23:49 +00:00
InitAdmin ( * authURL )
2020-07-22 04:42:21 +00:00
apiListener := tunnel . NewListener ( )
2020-07-18 06:18:09 +00:00
go func ( ) {
2020-07-21 09:29:49 +00:00
httpsrv . Serve ( apiListener )
2020-07-18 06:18:09 +00:00
} ( )
fmt . Printf ( "Will respond to Websocket and API requests to %q\n" , * apiHostname )
2020-07-19 06:52:17 +00:00
mux . HandleTLS ( * apiHostname , acme , mux , "[Terminate TLS & Recurse] for " + * apiHostname )
2020-07-18 06:18:09 +00:00
mux . HandleTCP ( * apiHostname , telebit . HandlerFunc ( func ( client net . Conn ) error {
if dbg . Debug {
fmt . Printf ( "[debug] Accepting API or WebSocket client %q\n" , * apiHostname )
}
2020-07-21 09:29:49 +00:00
apiListener . Feed ( client )
2020-07-18 06:18:09 +00:00
if dbg . Debug {
fmt . Printf ( "[debug] done with %q client\n" , * apiHostname )
}
// nil now means handler in-progress (go routine)
// EOF now means handler finished
return nil
} ) , "[Admin API & Server Relays]" )
}
2020-07-19 06:52:17 +00:00
if nil != grants {
2020-07-19 08:16:11 +00:00
for i , domainname := range grants . Domains {
fmt . Printf ( "[%d] Will decrypt remote requests to %q\n" , i , domainname )
2020-07-19 06:52:17 +00:00
mux . HandleTLS ( domainname , acme , mux , "[Terminate TLS & Recurse] for (tunnel) " + domainname )
}
}
2020-07-19 08:16:11 +00:00
for i , fwd := range forwards {
2020-07-22 04:42:21 +00:00
fmt . Printf ( "[%d] Will decrypt local requests to \"%s://%s\"\n" , i , fwd . scheme , fwd . pattern )
2020-07-19 06:52:17 +00:00
mux . HandleTLS ( fwd . pattern , acme , mux , "[Terminate TLS & Recurse] for (local) " + fwd . pattern )
}
//mux.HandleTLSFunc(func (sni) bool {
// // do whatever
// return false
//}, acme, mux, "[Terminate TLS & Recurse]")
2020-07-18 06:18:09 +00:00
for _ , fwd := range forwards {
//mux.ForwardTCP("*", "localhost:"+fwd.port, 120*time.Second)
2020-07-21 09:29:49 +00:00
if "https" == fwd . scheme {
2020-08-13 08:34:39 +00:00
if fwd . localTLS {
// this doesn't make much sense, but... security theatre
mux . ReverseProxyHTTPS ( fwd . pattern , "localhost:" + fwd . port , 120 * time . Second , "[Servername Reverse Proxy TLS]" )
} else {
mux . ReverseProxyHTTP ( fwd . pattern , "localhost:" + fwd . port , 120 * time . Second , "[Servername Reverse Proxy]" )
}
2020-07-21 09:29:49 +00:00
}
2020-07-18 06:18:09 +00:00
mux . ForwardTCP ( fwd . pattern , "localhost:" + fwd . port , 120 * time . Second , "[Servername Forward]" )
}
return mux
}
2020-06-22 06:34:42 +00:00
func routeSubscribersAndClients ( client net . Conn ) error {
var wconn * telebit . ConnWrap
switch conn := client . ( type ) {
case * telebit . ConnWrap :
wconn = conn
default :
panic ( "HandleTun is special in that it must receive &ConnWrap{ Conn: conn }" )
}
// We know this to be two parts "ip:port"
dstParts := strings . Split ( client . LocalAddr ( ) . String ( ) , ":" )
//dstAddr := dstParts[0]
dstPort , _ := strconv . Atoi ( dstParts [ 1 ] )
2020-07-18 03:51:48 +00:00
if dbg . Debug {
fmt . Printf ( "[debug] wconn.LocalAddr() %+v\n" , wconn . LocalAddr ( ) )
fmt . Printf ( "[debug] wconn.RemoteAddr() %+v\n" , wconn . RemoteAddr ( ) )
}
2020-07-12 05:59:34 +00:00
2020-06-22 06:34:42 +00:00
if 80 != dstPort && 443 != dstPort {
// TODO handle by port without peeking at Servername / Hostname
// if tryToServePort(client.LocalAddr().String(), wconn) {
// return io.EOF
// }
}
// TODO hostname for plain http?
servername := strings . ToLower ( wconn . Servername ( ) )
if "" != servername && ! isHostname ( servername ) {
_ = client . Close ( )
2020-07-18 03:51:48 +00:00
if dbg . Debug {
fmt . Println ( "[debug] invalid servername" )
}
2020-06-22 06:34:42 +00:00
return fmt . Errorf ( "invalid servername" )
}
2020-07-18 03:51:48 +00:00
if dbg . Debug {
fmt . Printf ( "[debug] wconn.Servername() %+v\n" , servername )
}
2020-06-22 06:34:42 +00:00
// Match full servername "sub.domain.example.com"
if tryToServeName ( servername , wconn ) {
// TODO better non-error
2020-07-09 09:03:04 +00:00
return nil
2020-06-22 06:34:42 +00:00
}
// Match wild names
// - "*.domain.example.com"
// - "*.example.com"
// - (skip)
labels := strings . Split ( servername , "." )
n := len ( labels )
if n < 3 {
2020-07-08 10:28:32 +00:00
// skip
return telebit . ErrNotHandled
2020-06-22 06:34:42 +00:00
}
for i := 1 ; i < n - 1 ; i ++ {
2020-07-18 03:51:48 +00:00
wildname := "*." + strings . Join ( labels [ i : ] , "." )
2020-06-22 06:34:42 +00:00
if tryToServeName ( wildname , wconn ) {
return io . EOF
}
}
// skip
2020-07-08 10:28:32 +00:00
return telebit . ErrNotHandled
2020-06-22 06:34:42 +00:00
}
// tryToServeName picks the server tunnel with the least connections, if any
func tryToServeName ( servername string , wconn * telebit . ConnWrap ) bool {
2020-06-29 06:35:19 +00:00
srv , ok := table . GetServer ( servername )
2020-07-17 05:41:04 +00:00
if ! ok || nil == srv {
if ok {
// TODO BUG: Sometimes srv=nil & ok=true, which should not be possible
fmt . Println ( "[bug] found 'srv=nil'" , servername , srv )
}
2020-07-18 03:51:48 +00:00
if dbg . Debug {
fmt . Println ( "[debug] no server to server" , servername )
}
2020-06-22 06:34:42 +00:00
return false
}
// async so that the call stack can complete and be released
//srv.clients.Store(wconn.LocalAddr().String(), wconn)
go func ( ) {
2020-07-18 03:51:48 +00:00
if dbg . Debug {
fmt . Printf ( "[debug] found server to handle client:\n%#v\n" , srv )
}
2020-06-22 06:34:42 +00:00
err := srv . Serve ( wconn )
2020-07-18 03:51:48 +00:00
if dbg . Debug {
fmt . Printf ( "[debug] a browser client stream is done: %v\n" , err )
}
2020-06-22 06:34:42 +00:00
} ( )
return true
}
2020-06-09 08:42:56 +00:00
func parsePortForwards ( portToPorts * string ) ( [ ] Forward , error ) {
var portForwards [ ] Forward
for _ , cfg := range strings . Fields ( strings . ReplaceAll ( * portToPorts , "," , " " ) ) {
parts := strings . Split ( cfg , ":" )
if 2 != len ( parts ) {
return nil , fmt . Errorf ( "--port-forward should be in the format 1234:5678, not %q" , cfg )
}
if _ , err := strconv . Atoi ( parts [ 0 ] ) ; nil != err {
return nil , fmt . Errorf ( "couldn't parse port %q of %q" , parts [ 0 ] , cfg )
}
if _ , err := strconv . Atoi ( parts [ 1 ] ) ; nil != err {
return nil , fmt . Errorf ( "couldn't parse port %q of %q" , parts [ 1 ] , cfg )
}
portForwards = append ( portForwards , Forward {
pattern : ":" + parts [ 0 ] ,
port : parts [ 1 ] ,
} )
}
return portForwards , nil
}
2020-06-09 00:58:52 +00:00
func parseBindAddrs ( bindAddrsStr string ) ( [ ] string , error ) {
bindAddrs := [ ] string { }
for _ , addr := range strings . Fields ( strings . ReplaceAll ( bindAddrsStr , "," , " " ) ) {
parts := strings . Split ( addr , ":" )
if len ( parts ) > 2 {
return nil , fmt . Errorf ( "too many colons (:) in bind address %s" , addr )
2020-06-03 07:47:06 +00:00
}
2020-07-17 05:41:04 +00:00
if "" == addr {
2020-06-09 00:58:52 +00:00
continue
}
var hostname , port string
if 2 == len ( parts ) {
hostname = parts [ 0 ]
port = parts [ 1 ]
} else {
port = parts [ 0 ]
}
if _ , err := strconv . Atoi ( port ) ; nil != err {
return nil , fmt . Errorf ( "couldn't parse port of %q" , addr )
}
bindAddrs = append ( bindAddrs , hostname + ":" + port )
}
return bindAddrs , nil
}
func getACMEProvider ( acmeRelay , token * string ) ( challenge . Provider , error ) {
var err error
var provider challenge . Provider = nil
if "" != os . Getenv ( "GODADDY_API_KEY" ) {
id := os . Getenv ( "GODADDY_API_KEY" )
apiSecret := os . Getenv ( "GODADDY_API_SECRET" )
if provider , err = newGoDaddyDNSProvider ( id , apiSecret ) ; nil != err {
return nil , err
}
} else if "" != os . Getenv ( "DUCKDNS_TOKEN" ) {
if provider , err = newDuckDNSProvider ( os . Getenv ( "DUCKDNS_TOKEN" ) ) ; nil != err {
return nil , err
}
} else {
if "" == * acmeRelay {
2020-07-17 05:41:04 +00:00
return nil , fmt . Errorf ( "No relay for ACME DNS-01 challenges given to --acme-relay-url" )
2020-06-09 00:58:52 +00:00
}
endpoint := * acmeRelay
2020-07-17 08:16:18 +00:00
if ! strings . HasSuffix ( endpoint , "/" ) {
endpoint += "/"
2020-06-09 00:58:52 +00:00
}
2020-07-17 08:16:18 +00:00
/ *
if strings . HasSuffix ( endpoint , "/" ) {
endpoint = endpoint [ : len ( endpoint ) - 1 ]
}
endpoint += "/api/dns/"
* /
2020-06-09 00:58:52 +00:00
if provider , err = newAPIDNSProvider ( endpoint , * token ) ; nil != err {
return nil , err
}
}
2020-05-06 17:11:13 +00:00
2020-06-09 00:58:52 +00:00
return provider , nil
2020-06-03 07:47:06 +00:00
}
2020-05-05 06:44:21 +00:00
2020-06-03 07:47:06 +00:00
type ACMEProvider struct {
BaseURL string
provider challenge . Provider
2020-05-05 06:44:21 +00:00
}
2020-06-03 07:47:06 +00:00
func ( p * ACMEProvider ) Present ( domain , token , keyAuth string ) error {
return p . provider . Present ( domain , token , keyAuth )
}
func ( p * ACMEProvider ) CleanUp ( domain , token , keyAuth string ) error {
return p . provider . CleanUp ( domain , token , keyAuth )
2020-05-06 17:11:13 +00:00
}
// newDuckDNSProvider is for the sake of demoing the tunnel
func newDuckDNSProvider ( token string ) ( * duckdns . DNSProvider , error ) {
config := duckdns . NewDefaultConfig ( )
config . Token = token
return duckdns . NewDNSProviderConfig ( config )
}
2020-06-03 07:47:06 +00:00
// newGoDaddyDNSProvider is for the sake of demoing the tunnel
func newGoDaddyDNSProvider ( id , secret string ) ( * godaddy . DNSProvider , error ) {
config := godaddy . NewDefaultConfig ( )
config . APIKey = id
config . APISecret = secret
return godaddy . NewDNSProviderConfig ( config )
}
// newAPIDNSProvider is for the sake of demoing the tunnel
2020-07-06 09:56:29 +00:00
func newAPIDNSProvider ( baseURL string , token string ) ( * tbDns01 . DNSProvider , error ) {
config := tbDns01 . NewDefaultConfig ( )
2020-06-03 07:47:06 +00:00
config . Token = token
endpoint , err := url . Parse ( baseURL )
if nil != err {
return nil , err
}
config . Endpoint = endpoint
2020-07-06 09:56:29 +00:00
return tbDns01 . NewDNSProviderConfig ( config )
2020-06-03 07:47:06 +00:00
}
/ *
// TODO for http proxy
return mplexer . TargetOptions {
Hostname // default localhost
Termination // default TLS
XFWD // default... no?
Port // default 0
Conn // should be dialed beforehand
} , nil
* /
/ *
t := telebit . New ( token )
mux := telebit . RouteMux { }
mux . HandleTLS ( "*" , mux ) // go back to itself
mux . HandleProxy ( "example.com" , "localhost:3000" )
mux . HandleTCP ( "example.com" , func ( c * telebit . Conn ) {
return httpmux . Serve ( )
} )
l := t . Listen ( "wss://example.com" )
conn := l . Accept ( )
telebit . Serve ( listener , mux )
t . ListenAndServe ( "wss://example.com" , mux )
* /
2020-06-09 08:42:56 +00:00
func getToken ( secret string , domains , ports [ ] string ) ( token string , err error ) {
tokenData := jwt . MapClaims { "domains" : domains , "ports" : ports }
2020-06-03 07:47:06 +00:00
jwtToken := jwt . NewWithClaims ( jwt . SigningMethodHS256 , tokenData )
if token , err = jwtToken . SignedString ( [ ] byte ( secret ) ) ; err != nil {
return "" , err
2020-05-05 06:44:21 +00:00
}
2020-06-03 07:47:06 +00:00
return token , nil
2017-03-29 23:49:02 +00:00
}