WIP: track connection table
This commit is contained in:
parent
2b48a8b8b9
commit
36a3650080
|
@ -4,7 +4,7 @@
|
|||
certs
|
||||
acme.d
|
||||
xversion.go
|
||||
assets_vfsdata.go
|
||||
*_vfsdata.go
|
||||
|
||||
mgmt-server-linux
|
||||
mgmt-server-macos
|
||||
|
|
|
@ -158,7 +158,12 @@ func handleDeviceRoutes(r chi.Router) {
|
|||
http.Error(w, msg, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := store.Delete(auth); nil != err {
|
||||
msg := `{"error":"not really sure what happened, but it didn't go well (check the logs)"}`
|
||||
log.Printf("/api/devices/%s\n", slug)
|
||||
log.Println(err)
|
||||
http.Error(w, msg, http.StatusInternalServerError)
|
||||
}
|
||||
w.Write([]byte(`{"success":true}` + "\n"))
|
||||
})
|
||||
})
|
||||
|
|
|
@ -93,6 +93,10 @@ func routeAll() chi.Router {
|
|||
return nil, fmt.Errorf("extra jwt payload 'slug' (unknown)")
|
||||
}
|
||||
claims.Slug = auth.Slug
|
||||
if "" != claims.Subject && auth.Slug != claims.Subject {
|
||||
return nil, fmt.Errorf("invalid jwt payload 'sub' (mismatch)")
|
||||
}
|
||||
claims.Issuer = primaryDomain
|
||||
|
||||
/*
|
||||
// a little misdirection there
|
||||
|
@ -102,7 +106,7 @@ func routeAll() chi.Router {
|
|||
return []byte(auth.SharedKey), nil
|
||||
*/
|
||||
|
||||
fmt.Println("ppid:", auth.MachinePPID)
|
||||
//fmt.Println("ppid:", auth.MachinePPID)
|
||||
|
||||
return []byte(auth.MachinePPID), nil
|
||||
},
|
||||
|
@ -141,7 +145,12 @@ func routeAll() chi.Router {
|
|||
return
|
||||
}
|
||||
|
||||
w.Write([]byte(fmt.Sprintf(`{ "domains": [ "%s.%s" ] }`+"\n", claims.Slug, primaryDomain)))
|
||||
w.Write([]byte(fmt.Sprintf(
|
||||
`{ "sub": "%s", "domains": [ "%s.%s" ], "ports": [] }`+"\n",
|
||||
claims.Subject,
|
||||
claims.Slug,
|
||||
primaryDomain,
|
||||
)))
|
||||
})
|
||||
|
||||
r.Route("/register-device", func(r chi.Router) {
|
||||
|
|
|
@ -253,8 +253,12 @@ func main() {
|
|||
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)
|
||||
/*
|
||||
|
@ -266,6 +270,10 @@ func main() {
|
|||
fmt.Println("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+"]")
|
||||
}
|
||||
|
||||
/*
|
||||
fmt.Printf("client claims:\n%+v\n", tok.Claims)
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,320 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
telebit "git.coolaj86.com/coolaj86/go-telebitd/mplexer"
|
||||
"git.coolaj86.com/coolaj86/go-telebitd/mplexer/admin"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
var httpsrv *http.Server
|
||||
|
||||
// Servers represent actual connections
|
||||
var Servers *sync.Map
|
||||
|
||||
// Table makes sense to be in-memory, but it could be serialized if needed
|
||||
var Table *sync.Map
|
||||
|
||||
func init() {
|
||||
Servers = &sync.Map{}
|
||||
Table = &sync.Map{}
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.HandleFunc("/ws", upgradeWebsocket)
|
||||
|
||||
r.Route("/api", func(r chi.Router) {
|
||||
// TODO token needs a globally unique subject
|
||||
|
||||
r.Use(func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
grants, err := authorizer(r)
|
||||
if nil != err {
|
||||
log.Println("authorization failed", err)
|
||||
w.Write(apiNotAuthorizedContent)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO define Admins in a better way
|
||||
if "*" != grants.Subject {
|
||||
log.Println("only admins allowed", err)
|
||||
w.Write(apiNotAuthorizedContent)
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
})
|
||||
|
||||
r.Get("/subscribers", getSubscribers)
|
||||
r.Delete("/subscribers/{subject}", delSubscribers)
|
||||
r.NotFound(apiNotFoundHandler)
|
||||
})
|
||||
|
||||
adminUI := http.FileServer(admin.AdminFS)
|
||||
r.Get("/", adminUI.ServeHTTP)
|
||||
|
||||
httpsrv = &http.Server{
|
||||
Handler: r,
|
||||
}
|
||||
}
|
||||
|
||||
var apiNotFoundContent = []byte("{ \"error\": \"not found\" }\n")
|
||||
var apiNotAuthorizedContent = []byte("{ \"error\": \"not authorized\" }\n")
|
||||
|
||||
func apiNotFoundHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(apiNotFoundContent)
|
||||
}
|
||||
|
||||
type SubscriberStatus struct {
|
||||
Subject string
|
||||
Tunnels int
|
||||
Clients int
|
||||
// TODO bytes read
|
||||
}
|
||||
|
||||
func getSubscribers(w http.ResponseWriter, r *http.Request) {
|
||||
statuses := []*SubscriberStatus{}
|
||||
Servers.Range(func(key, value interface{}) bool {
|
||||
tunnels := 0
|
||||
clients := 0
|
||||
//subject := key.(string)
|
||||
srvMap := value.(*sync.Map)
|
||||
srvMap.Range(func(k, v interface{}) bool {
|
||||
tunnels += 1
|
||||
srv := v.(*SubscriberConn)
|
||||
srv.clients.Range(func(k, v interface{}) bool {
|
||||
clients += 1
|
||||
return true
|
||||
})
|
||||
|
||||
statuses = append(statuses, &SubscriberStatus{
|
||||
Subject: k.(string),
|
||||
Tunnels: tunnels,
|
||||
Clients: clients,
|
||||
})
|
||||
return true
|
||||
})
|
||||
return true
|
||||
})
|
||||
_ = json.NewEncoder(w).Encode(&struct {
|
||||
Success bool `json:"success"`
|
||||
Subscribers []*SubscriberStatus `json:"subscribers"`
|
||||
}{
|
||||
Success: true,
|
||||
Subscribers: statuses,
|
||||
})
|
||||
}
|
||||
|
||||
func delSubscribers(w http.ResponseWriter, r *http.Request) {
|
||||
subject := chi.URLParam(r, "subject")
|
||||
|
||||
srvMapX, ok := Servers.Load(subject)
|
||||
if !ok {
|
||||
// TODO should this be an error?
|
||||
_ = json.NewEncoder(w).Encode(&struct {
|
||||
Success bool `json:"success"`
|
||||
}{
|
||||
Success: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
srvMap := srvMapX.(*sync.Map)
|
||||
srvMap.Range(func(k, v interface{}) bool {
|
||||
srv := v.(*SubscriberConn)
|
||||
srv.clients.Range(func(k, v interface{}) bool {
|
||||
conn := v.(net.Conn)
|
||||
_ = conn.Close()
|
||||
return true
|
||||
})
|
||||
srv.wsConn.Close()
|
||||
return true
|
||||
})
|
||||
Servers.Delete(subject)
|
||||
|
||||
_ = json.NewEncoder(w).Encode(&struct {
|
||||
Success bool `json:"success"`
|
||||
}{
|
||||
Success: true,
|
||||
})
|
||||
}
|
||||
|
||||
// SubscriberConn represents a tunneled server, its grants, and its clients
|
||||
type SubscriberConn struct {
|
||||
remoteAddr string
|
||||
wsConn *websocket.Conn
|
||||
wsTun net.Conn // *telebit.WebsocketTunnel
|
||||
grants *telebit.Grants
|
||||
clients *sync.Map
|
||||
|
||||
// TODO is this the right codec type?
|
||||
multiEncoder *telebit.Encoder
|
||||
multiDecoder *telebit.Decoder
|
||||
|
||||
// to fulfill Router interface
|
||||
}
|
||||
|
||||
func (s *SubscriberConn) RouteBytes(src, dst telebit.Addr, payload []byte) {
|
||||
id := src.String()
|
||||
fmt.Println("Routing some more bytes:")
|
||||
fmt.Println("src", id, src)
|
||||
fmt.Println("dst", dst)
|
||||
clientX, ok := s.clients.Load(id)
|
||||
if !ok {
|
||||
// TODO send back closed client error
|
||||
return
|
||||
}
|
||||
|
||||
client, _ := clientX.(net.Conn)
|
||||
for {
|
||||
n, err := client.Write(payload)
|
||||
if nil != err {
|
||||
if n > 0 && io.ErrShortWrite == err {
|
||||
payload = payload[n:]
|
||||
continue
|
||||
}
|
||||
// TODO send back closed client error
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SubscriberConn) Serve(client net.Conn) error {
|
||||
var wconn *telebit.ConnWrap
|
||||
switch conn := client.(type) {
|
||||
case *telebit.ConnWrap:
|
||||
wconn = conn
|
||||
default:
|
||||
// this probably isn't strictly necessary
|
||||
panic("*SubscriberConn.Serve is special in that it must receive &ConnWrap{ Conn: conn }")
|
||||
}
|
||||
|
||||
id := client.RemoteAddr().String()
|
||||
s.clients.Store(id, client)
|
||||
|
||||
fmt.Println("[debug] cancel all the clients")
|
||||
_ = client.Close()
|
||||
|
||||
// TODO
|
||||
// - Encode each client to the tunnel
|
||||
// - Find the right client for decoded messages
|
||||
|
||||
// TODO which order is remote / local?
|
||||
srcParts := strings.Split(client.RemoteAddr().String(), ":")
|
||||
srcAddr := srcParts[0]
|
||||
srcPort, _ := strconv.Atoi(srcParts[1])
|
||||
|
||||
dstParts := strings.Split(client.LocalAddr().String(), ":")
|
||||
dstAddr := dstParts[0]
|
||||
dstPort, _ := strconv.Atoi(dstParts[1])
|
||||
|
||||
termination := telebit.Unknown
|
||||
scheme := telebit.None
|
||||
if 80 == dstPort {
|
||||
// TODO dstAddr = wconn.Servername()
|
||||
scheme = telebit.HTTP
|
||||
} else if 443 == dstPort {
|
||||
dstAddr = wconn.Servername()
|
||||
scheme = telebit.HTTPS
|
||||
}
|
||||
|
||||
src := telebit.NewAddr(
|
||||
scheme,
|
||||
termination,
|
||||
srcAddr,
|
||||
srcPort,
|
||||
)
|
||||
dst := telebit.NewAddr(
|
||||
scheme,
|
||||
termination,
|
||||
dstAddr,
|
||||
dstPort,
|
||||
)
|
||||
|
||||
err := s.multiEncoder.Encode(wconn, *src, *dst)
|
||||
s.clients.Delete(id)
|
||||
return err
|
||||
}
|
||||
|
||||
func upgradeWebsocket(w http.ResponseWriter, r *http.Request) {
|
||||
log.Println("websocket opening ", r.RemoteAddr, " ", r.Host)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if "Upgrade" != r.Header.Get("Connection") && "WebSocket" != r.Header.Get("Upgrade") {
|
||||
w.Write(apiNotFoundContent)
|
||||
return
|
||||
}
|
||||
|
||||
grants, err := authorizer(r)
|
||||
if nil != err {
|
||||
log.Println("WebSocket authorization failed", err)
|
||||
w.Write(apiNotAuthorizedContent)
|
||||
return
|
||||
}
|
||||
upgrader := websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
}
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Println("WebSocket upgrade failed", err)
|
||||
return
|
||||
}
|
||||
|
||||
wsTun := telebit.NewWebsocketTunnel(conn)
|
||||
server := SubscriberConn{
|
||||
remoteAddr: r.RemoteAddr,
|
||||
wsConn: conn,
|
||||
wsTun: wsTun,
|
||||
grants: grants,
|
||||
clients: &sync.Map{},
|
||||
multiEncoder: telebit.NewEncoder(context.TODO(), wsTun),
|
||||
multiDecoder: telebit.NewDecoder(wsTun),
|
||||
}
|
||||
|
||||
go func() {
|
||||
// (this listener is also a telebit.Router)
|
||||
err := server.multiDecoder.Decode(&server)
|
||||
|
||||
// The tunnel itself must be closed explicitly because
|
||||
// there's an encoder with a callback between the websocket
|
||||
// and the multiplexer, so it doesn't know to stop listening otherwise
|
||||
_ = wsTun.Close()
|
||||
fmt.Printf("a subscriber stream is done: %q\n", err)
|
||||
}()
|
||||
|
||||
var srvMap *sync.Map
|
||||
srvMapX, ok := Servers.Load(grants.Subject)
|
||||
if ok {
|
||||
srvMap = srvMapX.(*sync.Map)
|
||||
} else {
|
||||
srvMap = &sync.Map{}
|
||||
}
|
||||
srvMap.Store(r.RemoteAddr, server)
|
||||
Servers.Store(grants.Subject, srvMap)
|
||||
|
||||
// Add this server to the domain name matrix
|
||||
for _, name := range grants.Domains {
|
||||
var srvMap *sync.Map
|
||||
srvMapX, ok := Table.Load(name)
|
||||
if ok {
|
||||
srvMap = srvMapX.(*sync.Map)
|
||||
} else {
|
||||
srvMap = &sync.Map{}
|
||||
}
|
||||
srvMap.Store(r.RemoteAddr, server)
|
||||
Table.Store(name, srvMap)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
telebit "git.coolaj86.com/coolaj86/go-telebitd/mplexer"
|
||||
)
|
||||
|
||||
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("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
|
||||
}
|
||||
}
|
|
@ -8,18 +8,22 @@ import (
|
|||
"encoding/hex"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.coolaj86.com/coolaj86/go-telebitd/mgmt"
|
||||
"git.coolaj86.com/coolaj86/go-telebitd/mgmt/authstore"
|
||||
telebit "git.coolaj86.com/coolaj86/go-telebitd/mplexer"
|
||||
"git.coolaj86.com/coolaj86/go-telebitd/mplexer/dns01"
|
||||
httpshim "git.coolaj86.com/coolaj86/go-telebitd/relay/tunnel"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/denisbrodbeck/machineid"
|
||||
|
@ -45,6 +49,10 @@ type Forward struct {
|
|||
port string
|
||||
}
|
||||
|
||||
var authorizer telebit.Authorizer
|
||||
|
||||
var isHostname = regexp.MustCompile(`^[A-Za-z0-9_\.\-]+$`).MatchString
|
||||
|
||||
func main() {
|
||||
var domains []string
|
||||
var forwards []Forward
|
||||
|
@ -62,6 +70,7 @@ func main() {
|
|||
acmeRelay := flag.String("acme-relay", "", "the base url of the ACME DNS-01 relay, if not the same as the tunnel relay")
|
||||
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")
|
||||
apiHostname := flag.String("admin-hostname", "", "the hostname used to manage clients")
|
||||
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)")
|
||||
bindAddrsStr := flag.String("listen", "", "list of bind addresses on which to listen, such as localhost:80, or :443")
|
||||
|
@ -69,6 +78,8 @@ func main() {
|
|||
portToPorts := flag.String("port-forward", "", "a list of <from-port>:<to-port> for raw port-forwarding")
|
||||
flag.Parse()
|
||||
|
||||
authorizer = NewAuthorizer(*authURL)
|
||||
|
||||
if len(os.Args) >= 2 {
|
||||
if "version" == os.Args[1] {
|
||||
fmt.Printf("telebit %s %s %s", GitVersion, GitRev[:7], GitTimestamp)
|
||||
|
@ -206,7 +217,25 @@ func main() {
|
|||
fmt.Println("Fwd:", fwd.pattern, fwd.port)
|
||||
mux.ForwardTCP(fwd.pattern, "localhost:"+fwd.port, 120*time.Second)
|
||||
}
|
||||
// TODO close connection on invalid hostname
|
||||
mux.HandleTCP("*", telebit.HandlerFunc(routeSubscribersAndClients))
|
||||
mux.HandleTLS("*", acme, mux)
|
||||
|
||||
if 0 == len(*apiHostname) {
|
||||
*apiHostname = os.Getenv("API_HOSTNAME")
|
||||
}
|
||||
if "" != *apiHostname {
|
||||
listener := httpshim.NewListener()
|
||||
go func() {
|
||||
httpsrv.Serve(listener)
|
||||
}()
|
||||
mux.HandleTCP(*apiHostname, telebit.HandlerFunc(func(client net.Conn) error {
|
||||
listener.Feed(client)
|
||||
// TODO use a more correct non-error error?
|
||||
// or perhaps (ok, error) or (handled, error)?
|
||||
return io.EOF
|
||||
}))
|
||||
}
|
||||
for _, fwd := range forwards {
|
||||
//mux.ForwardTCP("*", "localhost:"+fwd.port, 120*time.Second)
|
||||
mux.ForwardTCP(fwd.pattern, "localhost:"+fwd.port, 120*time.Second)
|
||||
|
@ -278,6 +307,96 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
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])
|
||||
|
||||
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()
|
||||
return fmt.Errorf("invalid servername")
|
||||
}
|
||||
|
||||
// Match full servername "sub.domain.example.com"
|
||||
if tryToServeName(servername, wconn) {
|
||||
// TODO better non-error
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
// Match wild names
|
||||
// - "*.domain.example.com"
|
||||
// - "*.example.com"
|
||||
// - (skip)
|
||||
labels := strings.Split(servername, ".")
|
||||
n := len(labels)
|
||||
if n < 3 {
|
||||
return nil
|
||||
}
|
||||
for i := 1; i < n-1; i++ {
|
||||
wildname := "*." + strings.Join(labels[1:], ".")
|
||||
if tryToServeName(wildname, wconn) {
|
||||
return io.EOF
|
||||
}
|
||||
}
|
||||
|
||||
// skip
|
||||
return nil
|
||||
}
|
||||
|
||||
// tryToServeName picks the server tunnel with the least connections, if any
|
||||
func tryToServeName(servername string, wconn *telebit.ConnWrap) bool {
|
||||
var srv *SubscriberConn
|
||||
load := -1
|
||||
srvMapX, ok := Table.Load(servername)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
srvMap := srvMapX.(*sync.Map)
|
||||
srvMap.Range(func(k, v interface{}) bool {
|
||||
myLoad := 0
|
||||
mySrv := v.(*SubscriberConn)
|
||||
mySrv.clients.Range(func(k, v interface{}) bool {
|
||||
load += 1
|
||||
return true
|
||||
})
|
||||
// pick the least loaded server
|
||||
if -1 == load || myLoad < load {
|
||||
load = myLoad
|
||||
srv = mySrv
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
// async so that the call stack can complete and be released
|
||||
//srv.clients.Store(wconn.LocalAddr().String(), wconn)
|
||||
go func() {
|
||||
err := srv.Serve(wconn)
|
||||
fmt.Printf("a browser client stream is done: %q\n", err)
|
||||
//srv.clients.Delete(wconn.LocalAddr().String())
|
||||
}()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func parsePortForwards(portToPorts *string) ([]Forward, error) {
|
||||
var portForwards []Forward
|
||||
|
||||
|
|
|
@ -55,6 +55,8 @@ func HMACToken(secret string) (token string, err error) {
|
|||
_, _ = rand.Read(b)
|
||||
claims := &jwt.StandardClaims{
|
||||
Id: base64.RawURLEncoding.EncodeToString(b),
|
||||
Subject: "", // TODO
|
||||
Issuer: "", // TODO
|
||||
IssuedAt: time.Now().Unix(),
|
||||
ExpiresAt: time.Now().Add(5 * time.Minute).Unix(),
|
||||
}
|
||||
|
|
|
@ -64,6 +64,12 @@ func TestStore(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
auth, err := store.Get(slug)
|
||||
if nil == err {
|
||||
t.Fatal("should get nothing back")
|
||||
return
|
||||
}
|
||||
|
||||
store.Close()
|
||||
|
||||
fmt.Printf("%#v\n", auth)
|
||||
|
|
|
@ -5,6 +5,7 @@ import "fmt"
|
|||
type Scheme string
|
||||
|
||||
const (
|
||||
None = Scheme("")
|
||||
HTTPS = Scheme("https")
|
||||
HTTP = Scheme("http")
|
||||
SSH = Scheme("ssh")
|
||||
|
@ -14,20 +15,22 @@ const (
|
|||
type Termination string
|
||||
|
||||
const (
|
||||
TCP = Termination("none")
|
||||
TLS = Termination("tls")
|
||||
Unknown = Termination("")
|
||||
TCP = Termination("none")
|
||||
TLS = Termination("tls")
|
||||
)
|
||||
|
||||
type Addr struct {
|
||||
family string // TODO what should be the format? "tcpv6"?
|
||||
scheme Scheme
|
||||
termination Termination
|
||||
family string // TODO what should be the format? "tcpv6"?
|
||||
addr string
|
||||
port int
|
||||
}
|
||||
|
||||
func NewAddr(s Scheme, t Termination, a string, p int) *Addr {
|
||||
return &Addr{
|
||||
family: "tun",
|
||||
scheme: s,
|
||||
termination: t,
|
||||
addr: a,
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
// +build !dev
|
||||
//go:generate go run -mod vendor github.com/shurcooL/vfsgen/cmd/vfsgendev -source="git.coolaj86.com/coolaj86/go-telebitd/mplexer/admin".AdminFS
|
||||
|
||||
package admin
|
|
@ -0,0 +1,2 @@
|
|||
Contact: mailto:security@therootcompany.com
|
||||
Preferred-Languages: en, lv, sq
|
|
@ -0,0 +1,32 @@
|
|||
/* TEAM */
|
||||
|
||||
Gopher: AJ ONeal
|
||||
Site: https://coolaj86.com
|
||||
Twitter: @coolaj86
|
||||
Location: Provo, UT, USA.
|
||||
|
||||
Permanent Beta Tester: Ryan Burnette
|
||||
Site: https://ryanburnette.com
|
||||
Twitter: @ryanburnette
|
||||
Location: GA, USA.
|
||||
|
||||
Moral Support: John Shaver
|
||||
Site: https://jshaver.net
|
||||
Twitter: @thejshaver
|
||||
Location: USA.
|
||||
|
||||
/* THANKS */
|
||||
|
||||
Name: Brian Turley
|
||||
|
||||
Name: Seth Gibelyou
|
||||
|
||||
Name: Henry Camacho
|
||||
|
||||
/* SITE */
|
||||
|
||||
Last update: 2020/06/10
|
||||
Standards: HTML5, WebSockets.
|
||||
Components: Chi, Cert Magic, vfsgen.
|
||||
Software: Golang
|
||||
IDE: vim
|
|
@ -0,0 +1,11 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Telebit Local Admin</title>
|
||||
<link rel="author" href="humans.txt" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello, World!</h1>
|
||||
<p>with love,</p>
|
||||
<p>Telebit</p>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,3 @@
|
|||
# Do not crawl the admin interface
|
||||
User-agent: *
|
||||
Disallow: /
|
|
@ -0,0 +1,7 @@
|
|||
// +build dev
|
||||
|
||||
package admin
|
||||
|
||||
import "net/http"
|
||||
|
||||
var AdminFS http.FileSystem = http.Dir("assets")
|
|
@ -18,6 +18,7 @@ type ConnWrap struct {
|
|||
scheme string
|
||||
Conn net.Conn
|
||||
Plain net.Conn
|
||||
encrypted *bool
|
||||
}
|
||||
|
||||
type Peeker interface {
|
||||
|
@ -114,6 +115,9 @@ func (c *ConnWrap) Servername() string {
|
|||
//c.servername = string(conn.relayTargetAddr.addr)
|
||||
return string(conn.relayTargetAddr.addr)
|
||||
}
|
||||
|
||||
// this will get the servername
|
||||
c.isTerminated()
|
||||
return c.servername
|
||||
}
|
||||
|
||||
|
@ -126,6 +130,9 @@ func (c *ConnWrap) isTerminated() bool {
|
|||
return true
|
||||
}
|
||||
*/
|
||||
if nil != c.encrypted {
|
||||
return !*c.encrypted
|
||||
}
|
||||
|
||||
// how to know how many bytes to read? really needs timeout
|
||||
c.SetDeadline(time.Now().Add(5 * time.Second))
|
||||
|
@ -146,13 +153,16 @@ func (c *ConnWrap) isTerminated() bool {
|
|||
length := (int(b[3]) << 8) + int(b[4])
|
||||
b, err := c.Peek(n - 1 + length)
|
||||
if nil != err {
|
||||
return true
|
||||
*c.encrypted = false
|
||||
return !*c.encrypted
|
||||
}
|
||||
c.servername, _ = sni.GetHostname(b)
|
||||
return false
|
||||
*c.encrypted = true
|
||||
return !*c.encrypted
|
||||
}
|
||||
}
|
||||
return true
|
||||
*c.encrypted = false
|
||||
return !*c.encrypted
|
||||
/*
|
||||
if nil != err {
|
||||
return true
|
||||
|
|
|
@ -48,12 +48,13 @@ func Listen(tun net.Conn) *Listener {
|
|||
decoder := NewDecoder(tun)
|
||||
go func() {
|
||||
// TODO pass error to Accept()
|
||||
// (this listener is also a telebit.Router)
|
||||
err := decoder.Decode(listener)
|
||||
|
||||
// The listener itself must be closed explicitly because
|
||||
// there's an encoder with a callback between the websocket
|
||||
// and the multiplexer, so it doesn't know to stop listening otherwise
|
||||
listener.Close()
|
||||
_ = listener.Close()
|
||||
fmt.Printf("the main stream is done: %q\n", err)
|
||||
}()
|
||||
|
||||
|
|
|
@ -66,9 +66,8 @@ func (m *RouteMux) Serve(client net.Conn) error {
|
|||
|
||||
for _, meta := range m.routes {
|
||||
// TODO '*.example.com'
|
||||
if meta.terminate && "" == servername {
|
||||
wconn.isTerminated()
|
||||
servername = wconn.servername
|
||||
if meta.terminate {
|
||||
servername = wconn.Servername()
|
||||
}
|
||||
fmt.Println("Meta:", meta.addr, servername)
|
||||
if servername == meta.addr || "*" == meta.addr || port == meta.addr {
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/bash
|
||||
|
||||
go run cmd/telebit/*.go --acme-agree=true \
|
||||
--acme-relay http://devices.rootprojects.org:3010/api/dns \
|
||||
--auth-url http://devices.rootprojects.org:3010/api \
|
||||
--relay wss://devices.rootprojects.org:8443/api/ws \
|
||||
--app-id test-id \
|
||||
--secret k7nsLSwNKbOeBhDFpbhwGHv \
|
||||
--listen 3443
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/bash
|
||||
|
||||
go run cmd/telebit/*.go --acme-agree=true \
|
||||
--acme-relay http://devices.rootprojects.org:3010/api/dns \
|
||||
--auth-url http://devices.rootprojects.org:3010/api \
|
||||
--relay wss://devices.rootprojects.org:8443/ws \
|
||||
--app-id test-id \
|
||||
--secret xxxxyyyyssss8347 \
|
||||
--listen 3443
|
|
@ -1 +0,0 @@
|
|||
go run cmd/telebit/*.go --acme-agree=true --acme-relay http://devices.rootprojects.org:3010/api/dns --auth-url http://devices.rootprojects.org:3010/api --app-id test-id --secret k7nsLSwNKbOeBhDFpbhwGHv --listen 3443
|
|
@ -35,12 +35,16 @@ var errNetClosing = "use of closed network connection"
|
|||
|
||||
// A Handler routes, proxies, terminates, or responds to a net.Conn.
|
||||
type Handler interface {
|
||||
// TODO ServeTCP
|
||||
Serve(net.Conn) error
|
||||
}
|
||||
|
||||
// HandlerFunc should handle, proxy, or terminate the connection
|
||||
type HandlerFunc func(net.Conn) error
|
||||
|
||||
// Authorizer is called when a new client connects and we need to know something about it
|
||||
type Authorizer func(*http.Request) (*Grants, error)
|
||||
|
||||
// Serve calls f(conn).
|
||||
func (f HandlerFunc) Serve(conn net.Conn) error {
|
||||
return f(conn)
|
||||
|
@ -313,7 +317,9 @@ func newCertMagic(acme *ACME) (*certmagic.Config, error) {
|
|||
}
|
||||
|
||||
type Grants struct {
|
||||
Subject string `json:"sub"`
|
||||
Domains []string `json:"domains"`
|
||||
Ports []int `json:"ports"`
|
||||
}
|
||||
|
||||
func Inspect(authURL, token string) (*Grants, error) {
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"git.coolaj86.com/coolaj86/go-telebitd/log"
|
||||
telebit "git.coolaj86.com/coolaj86/go-telebitd/mplexer"
|
||||
|
@ -34,9 +33,6 @@ type Authz struct {
|
|||
Domains []string
|
||||
}
|
||||
|
||||
// Authorizer is called when a new client connects and we need to know something about it
|
||||
type Authorizer func(*http.Request) (*telebit.Grants, error)
|
||||
|
||||
const (
|
||||
listenerAdded ListenerRegistrationStatus = iota
|
||||
listenerExists
|
||||
|
@ -77,8 +73,8 @@ type MPlexy struct {
|
|||
ctx context.Context
|
||||
connnectionTable *api.Table
|
||||
connectionTracking *api.Tracking
|
||||
AuthorizeTarget Authorizer
|
||||
AuthorizeAdmin Authorizer
|
||||
AuthorizeTarget telebit.Authorizer
|
||||
AuthorizeAdmin telebit.Authorizer
|
||||
tlsConfig *tls.Config
|
||||
register chan *ListenerRegistration
|
||||
wssHostName string
|
||||
|
@ -94,8 +90,8 @@ type MPlexy struct {
|
|||
func New(
|
||||
ctx context.Context,
|
||||
tlsConfig *tls.Config,
|
||||
authAdmin Authorizer,
|
||||
authz Authorizer,
|
||||
authAdmin telebit.Authorizer,
|
||||
authz telebit.Authorizer,
|
||||
serverStatus *api.Status,
|
||||
) (mx *MPlexy) {
|
||||
mx = &MPlexy{
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"net"
|
||||
"net/http"
|
||||
|
||||
telebit "git.coolaj86.com/coolaj86/go-telebitd/mplexer"
|
||||
"git.coolaj86.com/coolaj86/go-telebitd/relay/admin"
|
||||
"git.coolaj86.com/coolaj86/go-telebitd/relay/api"
|
||||
"git.coolaj86.com/coolaj86/go-telebitd/relay/mplexy"
|
||||
|
@ -25,7 +26,7 @@ type Relay struct {
|
|||
}
|
||||
|
||||
// New initializes and returns a relay service
|
||||
func New(ctx context.Context, tlsConfig *tls.Config, authz mplexy.Authorizer, status *api.Status, table *api.Table) *Relay {
|
||||
func New(ctx context.Context, tlsConfig *tls.Config, authz telebit.Authorizer, status *api.Status, table *api.Table) *Relay {
|
||||
// TODO do we need this already setup here? or is it just for logging?
|
||||
status.ConnectionTracking = api.NewTracking()
|
||||
status.ConnectionTable = table
|
||||
|
|
Loading…
Reference in New Issue