diff --git a/cmd/telebit/telebit.go b/cmd/telebit/telebit.go index b553a0d..ccb9cc5 100644 --- a/cmd/telebit/telebit.go +++ b/cmd/telebit/telebit.go @@ -24,7 +24,7 @@ import ( "git.rootprojects.org/root/telebit/mgmt" "git.rootprojects.org/root/telebit/mgmt/authstore" "git.rootprojects.org/root/telebit/table" - httpshim "git.rootprojects.org/root/telebit/tunnel" + "git.rootprojects.org/root/telebit/tunnel" legoDns01 "github.com/go-acme/lego/v3/challenge/dns01" "github.com/caddyserver/certmagic" @@ -113,6 +113,10 @@ func main() { *acmeAgree = true } } + if 0 == len(*acmeRelay) { + *acmeRelay = os.Getenv("ACME_RELAY_URL") + } + if 0 == len(*email) { *email = os.Getenv("ACME_EMAIL") } @@ -234,21 +238,32 @@ func main() { return } } - if 0 == len(*acmeRelay) { - *acmeRelay = os.Getenv("ACME_RELAY_URL") - } - if 0 == len(*acmeRelay) { - *acmeRelay = strings.Replace(*relay, "ws", "http", 1) + "/dns" // "https://example.com:443" - } if 0 == len(*authURL) { *authURL = os.Getenv("AUTH_URL") } var grants *telebit.Grants if len(*relay) > 0 /* || len(*acmeRelay) > 0 */ { - if "" == *authURL { - *authURL = strings.Replace(*relay, "ws", "http", 1) + "/api" // "https://example.com:443" + directory, err := tunnel.Discover(*relay) + if nil != err { + fmt.Fprintf(os.Stderr, "Error: invalid Tunnel Relay URL %q: %s\n", *relay, err) + os.Exit(1) } + fmt.Printf("[Directory] %s\n\t%#v\n", *relay, directory) + + if "" == *authURL { + *authURL = directory.Authenticate.URL + } else { + fmt.Println("Suggested Auth URL:", directory.Authenticate.URL) + fmt.Println("--auth-url Auth URL:", *authURL) + } + if "" == *authURL { + fmt.Fprintf(os.Stderr, "Discovered Directory Endpoints: %+v\n", directory) + fmt.Fprintf(os.Stderr, "No Auth URL detected, no supplied\n") + os.Exit(1) + return + } + // TODO look at relay rather than authURL? fmt.Println("Auth URL", *authURL) authorizer = NewAuthorizer(*authURL) @@ -265,7 +280,8 @@ func main() { os.Exit(1) } } - fmt.Println("grants", grants) + fmt.Printf("[Grants]\n\t%#v\n", grants) + *relay = grants.Audience } authorizer = NewAuthorizer(*authURL) @@ -388,7 +404,8 @@ func muxAll( *apiHostname = os.Getenv("API_HOSTNAME") } if "" != *apiHostname { - apiListener := httpshim.NewListener() + // this is a generic net listener + apiListener := tunnel.NewListener() go func() { httpsrv.Serve(apiListener) }() @@ -416,7 +433,7 @@ func muxAll( } for i, fwd := range forwards { - fmt.Printf("[%d] Will decrypt local requests to %q\n", i, fwd.pattern) + fmt.Printf("[%d] Will decrypt local requests to \"%s://%s\"\n", i, fwd.scheme, fwd.pattern) mux.HandleTLS(fwd.pattern, acme, mux, "[Terminate TLS & Recurse] for (local) "+fwd.pattern) } diff --git a/telebit.go b/telebit.go index b928252..d6ec5be 100644 --- a/telebit.go +++ b/telebit.go @@ -364,13 +364,14 @@ func NewCertMagic(acme *ACME) (*certmagic.Config, error) { } type Grants struct { - Subject string `json:"sub"` - Domains []string `json:"domains"` - Ports []int `json:"ports"` + Subject string `json:"sub"` + Audience string `json:"aud"` + Domains []string `json:"domains"` + Ports []int `json:"ports"` } func Inspect(authURL, token string) (*Grants, error) { - inspectURL := authURL + "/inspect" + inspectURL := strings.TrimSuffix(authURL, "/inspect") + "/inspect" //fmt.Fprintf(os.Stderr, "[debug] telebit.Inspect(\n\tinspectURL = %s,\n\ttoken = %s,\n)", inspectURL, token) msg, err := Request("GET", inspectURL, token, nil) if nil != err { diff --git a/tunnel/discover.go b/tunnel/discover.go new file mode 100644 index 0000000..3e13bfb --- /dev/null +++ b/tunnel/discover.go @@ -0,0 +1,103 @@ +package tunnel + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "os" + "strings" +) + +// Endpoints represents the endpoints listed in the API service directory. +// Ideally the Relay URL is discoverable and will provide the authn endpoint, +// which will provide the tunnel endpoint. However, for the sake of testing, +// these things may happen out-of-order. +type Endpoints struct { + ToS string `json:"terms_of_service"` + APIHost string `json:"api_host"` + Tunnel Endpoint `json:"tunnel"` + Authenticate Endpoint `json:"authn"` + /* + { + "terms_of_service": ":hostname/tos/", + "api_host": ":hostname/api", + "pair_request": { + "method": "POST", + "pathname": "api/telebit.app/pair_request" + } + } + */ +} + +// Endpoint represents a URL Request +type Endpoint struct { + URL string `json:"-"` + Method string `json:"method"` + Scheme string `json:"scheme"` + Host string `json:"host"` + Pathname string `json:"pathname"` +} + +// Discover checks the .well-known directory for service endpoints +func Discover(relay string) (*Endpoints, error) { + directives := &Endpoints{} + relayURL, err := url.Parse(relay) + if nil != err { + fmt.Fprintf(os.Stderr, "Error: invalid Tunnel Relay URL %q: %s\n", relay, err) + os.Exit(1) + } + if "ws" != relayURL.Scheme[:2] { + + if '/' != relayURL.Path[len(relayURL.Path)-1] { + relayURL.Path += "/" + } + resp, err := http.Get(relayURL.String() + ".well-known/telebit.app/index.json") + if nil != err { + fmt.Fprintf(os.Stderr, "Error: invalid Tunnel Relay URL %q: %s\n", relay, err) + os.Exit(1) + } + b, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if nil != err { + return nil, err + } + body := strings.Replace(string(b), ":hostname", relayURL.Host, -1) + err = json.Unmarshal([]byte(body), directives) + if nil != err { + return nil, err + } + + directives.Tunnel.URL = endpointToURLString(directives.APIHost, directives.Tunnel) + + } else { + + directives.Tunnel.URL = relayURL.String() + directives.APIHost = relayURL.Host + + } + + directives.Authenticate.URL = endpointToURLString(directives.APIHost, directives.Authenticate) + + return directives, nil +} + +func endpointToURLString(apiHost string, endpoint Endpoint) string { + pathname := endpoint.Pathname + if "" == pathname { + return "" + } + + host := endpoint.Host + if "" == host { + host = apiHost + } + + scheme := endpoint.Scheme + if "" == scheme { + scheme = "https:" + } + + return fmt.Sprintf("%s//%s/%s", scheme, host, pathname) +} diff --git a/websocket.go b/websocket.go index b93e478..596663f 100644 --- a/websocket.go +++ b/websocket.go @@ -100,6 +100,7 @@ func DialWebsocketTunnel(ctx context.Context, relay, authz string) (net.Conn, er if dbg.Debug { fmt.Fprintf(os.Stderr, "[debug] [wstun] simple dial failed %q %v %v\n", err, wsconn, ctx) } + return nil, err } return NewWebsocketTunnel(wsconn), err }