telebit/cmd/wsconnect/wsconnect.go

160 lines
4.3 KiB
Go

package main
import (
"context"
"encoding/base64"
"encoding/hex"
"flag"
"fmt"
"net/http"
"os"
"strings"
"time"
"git.rootprojects.org/root/telebit/mgmt"
"git.rootprojects.org/root/telebit/mgmt/authstore"
telebit "git.rootprojects.org/root/telebit"
"github.com/denisbrodbeck/machineid"
"github.com/gorilla/websocket"
_ "github.com/joho/godotenv/autoload"
)
var authorizer telebit.Authorizer
func main() {
// TODO replace the websocket connection with a mock server
vendorID := flag.String("vendor-id", "", "a unique identifier for a deploy target environment")
authURL := flag.String("auth-url", "", "the base url for authentication, if not the same as the tunnel relay")
relay := flag.String("relay", "", "the domain (or ip address) at which the relay server is running")
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)")
flag.Parse()
if 0 == len(*vendorID) {
*vendorID = os.Getenv("VENDOR_ID")
}
if 0 == len(*vendorID) {
*vendorID = "telebit.io"
}
if 0 == len(*secret) {
*secret = os.Getenv("CLIENT_SECRET")
}
ppid, err := machineid.ProtectedID(fmt.Sprintf("%s|%s", *vendorID, *secret))
if nil != err {
fmt.Fprintf(os.Stderr, "unauthorized device\n")
os.Exit(1)
return
}
ppidBytes, err := hex.DecodeString(ppid)
ppid = base64.RawURLEncoding.EncodeToString(ppidBytes)
fmt.Println("[debug] vendor-id, secret, ppid", *vendorID, *secret, ppid)
if 0 == len(*token) {
*token, err = authstore.HMACToken(ppid)
if nil != err {
fmt.Fprintf(os.Stderr, "neither secret nor token provided\n")
os.Exit(1)
return
}
}
if 0 == len(*relay) {
*relay = os.Getenv("TUNNEL_RELAY_URL") // "wss://example.com:443"
}
if 0 == len(*relay) {
fmt.Fprintf(os.Stderr, "Missing relay url\n")
//os.Exit(1)
//return
}
if "" == *authURL {
*authURL = os.Getenv("AUTH_URL")
}
if "" == *authURL {
*authURL = strings.Replace(*relay, "ws", "http", 1) // "https://example.com:443"
}
authorizer = NewAuthorizer(*authURL)
// TODO look at relay rather than authURL?
grants, err := telebit.Inspect(*authURL, *token)
if nil != err {
fmt.Println("[debug] inpsect failed:")
fmt.Println(err)
_, err := mgmt.Register(*authURL, *secret, ppid)
if nil != err {
fmt.Fprintf(os.Stderr, "failed to register client: %s\n", err)
os.Exit(1)
}
grants, err = telebit.Inspect(*authURL, *token)
if nil != err {
fmt.Fprintf(os.Stderr, "failed to authenticate after registering client: %s\n", err)
os.Exit(1)
}
}
fmt.Println("grants", grants)
wsd := websocket.Dialer{}
headers := http.Header{}
headers.Set("Authorization", fmt.Sprintf("Bearer %s", *token))
// *http.Response
sep := "?"
if strings.Contains(*relay, sep) {
sep = "&"
}
timeoutCtx, cancel := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))
defer cancel()
wsconn, _, err := wsd.DialContext(timeoutCtx, *relay+sep+"access_token="+*token+"&versions=v1", headers)
if nil != err {
fmt.Println("[debug] err")
fmt.Println(err)
return
}
for {
_, msgr, err := wsconn.NextReader()
if nil != err {
fmt.Println("debug wsconn NextReader err:", err)
return
//return 0, err
}
for {
b := make([]byte, 512)
n, err := msgr.Read(b)
if nil != err {
fmt.Println("debug msgr Read err:", err)
return
}
fmt.Println(n, string(b[0:n]))
}
}
}
func NewAuthorizer(authURL string) telebit.Authorizer {
return func(r *http.Request) (*telebit.Grants, error) {
// do we have a valid wss_client?
var tokenString string
if auth := strings.Split(r.Header.Get("Authorization"), " "); len(auth) > 1 {
// TODO handle Basic auth tokens as well
tokenString = auth[1]
}
if "" == tokenString {
// Browsers do not allow Authorization Headers and must use access_token query string
tokenString = r.URL.Query().Get("access_token")
}
if "" != r.URL.Query().Get("access_token") {
r.URL.Query().Set("access_token", "[redacted]")
}
grants, err := telebit.Inspect(authURL, tokenString)
if nil != err {
fmt.Println("[wsconnect] return an error, do not go on")
return nil, err
}
if "" != r.URL.Query().Get("access_token") {
r.URL.Query().Set("access_token", "[redacted:"+grants.Subject+"]")
}
return grants, err
}
}