diff --git a/mplexer/cmd/mgmt/acmeroutes.go.bak b/mplexer/cmd/mgmt/acmeroutes.go.bak deleted file mode 100644 index f8c1169..0000000 --- a/mplexer/cmd/mgmt/acmeroutes.go.bak +++ /dev/null @@ -1,24 +0,0 @@ - r.Use(func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - valid, _ := ctx.Value(MWKey("valid")).(bool) - - if !valid { - // misdirection - time.Sleep(250 * time.Millisecond) - w.Write([]byte("{\"success\":true}\n")) - //http.Error(w, `{"error":"could not verify token"}`, http.StatusBadRequest) - return - } - /* - if nil != err2 { - // a little misdirection there - msg := `{"error":"internal server error"}` - http.Error(w, msg, http.StatusInternalServerError) - return - } - */ - - next.ServeHTTP(w, r.WithContext(ctx)) - }) - }) diff --git a/mplexer/cmd/signjwt/signjwt.go b/mplexer/cmd/signjwt/signjwt.go index e3bb753..dcd297b 100644 --- a/mplexer/cmd/signjwt/signjwt.go +++ b/mplexer/cmd/signjwt/signjwt.go @@ -1,17 +1,14 @@ package main import ( - "crypto/rand" "encoding/base64" "encoding/hex" "fmt" "os" - "time" "git.coolaj86.com/coolaj86/go-telebitd/mplexer/mgmt/authstore" "github.com/denisbrodbeck/machineid" - jwt "github.com/dgrijalva/jwt-go" _ "github.com/joho/godotenv/autoload" ) @@ -44,14 +41,7 @@ func main() { return } - b := make([]byte, 16) - _, _ = rand.Read(b) - claims := &jwt.StandardClaims{ - Id: base64.RawURLEncoding.EncodeToString(b), - IssuedAt: time.Now().Unix(), - ExpiresAt: time.Now().Add(5 * time.Minute).Unix(), - } - tok, err := getToken(secret, claims) + tok, err := authstore.HMACToken(secret) if nil != err { fmt.Fprintf(os.Stderr, "signing error: %s", err) os.Exit(1) @@ -60,25 +50,3 @@ func main() { fmt.Println(tok) } - -func getToken(secret string, tokenData *jwt.StandardClaims) (token string, err error) { - keyID := authstore.ToPublicKeyString(secret) - - fmt.Fprintf(os.Stderr, "secret: %s\n", secret) - fmt.Fprintf(os.Stderr, "kid: %s\n", keyID) - - jwtToken := &jwt.Token{ - Header: map[string]interface{}{ - "kid": keyID, - "typ": "JWT", - "alg": jwt.SigningMethodHS256.Alg(), - }, - Claims: tokenData, - Method: jwt.SigningMethodHS256, - } - - if token, err = jwtToken.SignedString([]byte(secret)); err != nil { - return "", err - } - return token, nil -} diff --git a/mplexer/cmd/telebit/telebit.go b/mplexer/cmd/telebit/telebit.go index d154a96..1e0c885 100644 --- a/mplexer/cmd/telebit/telebit.go +++ b/mplexer/cmd/telebit/telebit.go @@ -17,6 +17,8 @@ import ( telebit "git.coolaj86.com/coolaj86/go-telebitd/mplexer" dns01 "git.coolaj86.com/coolaj86/go-telebitd/mplexer/dns01" + "git.coolaj86.com/coolaj86/go-telebitd/mplexer/mgmt" + "git.coolaj86.com/coolaj86/go-telebitd/mplexer/mgmt/authstore" "github.com/caddyserver/certmagic" "github.com/denisbrodbeck/machineid" @@ -58,23 +60,16 @@ func main() { 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") 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") 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)") locals := flag.String("locals", "", "a list of :") flag.Parse() - muid, err := machineid.ProtectedID(*appID) - if nil != err { - fmt.Fprintf(os.Stderr, "unauthorized device") - os.Exit(1) - } - muidb, err := hex.DecodeString(muid) - muid = base64.RawURLEncoding.EncodeToString(muidb) - if len(os.Args) >= 2 { if "version" == os.Args[1] { - fmt.Printf("telebit %s %s %s %s", GitVersion, GitRev[:7], GitTimestamp, muid[:24]) + fmt.Printf("telebit %s %s %s %s", GitVersion, GitRev[:7], GitTimestamp) os.Exit(0) } } @@ -114,11 +109,19 @@ func main() { domains = append(domains, domain) } + ppid, err := machineid.ProtectedID(fmt.Sprintf("%s|%s", *appID, *secret)) + if nil != err { + fmt.Fprintf(os.Stderr, "unauthorized device") + os.Exit(1) + } + ppidBytes, err := hex.DecodeString(ppid) + ppid = base64.RawURLEncoding.EncodeToString(ppidBytes) + if "" == *token { if "" == *secret { *secret = os.Getenv("SECRET") } - *token, err = getToken(*secret, domains) + *token, err = authstore.HMACToken(ppid) } if nil != err { fmt.Fprintf(os.Stderr, "neither secret nor token provided") @@ -137,6 +140,9 @@ func main() { if "" == *acmeRelay { *acmeRelay = strings.Replace(*relay, "ws", "http", 1) // "https://example.com:443" } + if "" == *authURL { + *authURL = strings.Replace(*relay, "ws", "http", 1) // "https://example.com:443" + } if "" != os.Getenv("GODADDY_API_KEY") { id := os.Getenv("GODADDY_API_KEY") @@ -159,6 +165,21 @@ func main() { } } + grants, err := mgmt.Inspect(*authURL, *token) + if nil != err { + _, err := mgmt.Register(*authURL, *secret, ppid) + if nil != err { + fmt.Fprintf(os.Stderr, "failed to register client: %s", err) + os.Exit(1) + } + grants, err = mgmt.Inspect(*authURL, *token) + if nil != err { + fmt.Fprintf(os.Stderr, "failed to authenticate after registering client: %s", err) + os.Exit(1) + } + } + fmt.Println("grants", grants) + acme := &telebit.ACME{ Email: *email, StoragePath: *certpath, diff --git a/mplexer/mgmt-ping.sh b/mplexer/mgmt-ping.sh index 128c343..99d2ddb 100644 --- a/mplexer/mgmt-ping.sh +++ b/mplexer/mgmt-ping.sh @@ -20,4 +20,4 @@ echo "PPID: $my_ppid KeyID: $my_keyid" TOKEN=$(go run cmd/signjwt/*.go $my_ppid) curl -X POST http://localhost:3000/api/ping -H "Authorization: Bearer ${TOKEN}" -curl -X POST http://localhost:3000/api/inspect -H "Authorization: Bearer ${TOKEN}" +curl http://localhost:3000/api/inspect -H "Authorization: Bearer ${TOKEN}" diff --git a/mplexer/mgmt/auth.go b/mplexer/mgmt/auth.go new file mode 100644 index 0000000..2791cfc --- /dev/null +++ b/mplexer/mgmt/auth.go @@ -0,0 +1,60 @@ +package mgmt + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + + telebit "git.coolaj86.com/coolaj86/go-telebitd/mplexer" + "git.coolaj86.com/coolaj86/go-telebitd/mplexer/mgmt/authstore" +) + +type Grants struct { + Domains []string `json:"domains"` +} + +func Inspect(authURL, token string) (*Grants, error) { + msg, err := telebit.Request("GET", authURL+"/inspect", token, nil) + if nil != err { + return nil, err + } + if nil == msg { + return nil, fmt.Errorf("invalid response") + } + + grants := &Grants{} + err = json.NewDecoder(msg).Decode(grants) + if err != nil { + return nil, err + } + return grants, nil +} + +func Register(authURL, secret, ppid string) (kid string, err error) { + pub := authstore.ToPublicKeyString(ppid) + jsonb := bytes.NewBuffer([]byte( + fmt.Sprintf(`{ "machine_ppid": "%s", "public_key": "%s" }`, ppid, pub), + )) + msg, err := telebit.Request("POST", authURL+"/register-device/"+secret, "", jsonb) + if nil != err { + return "", err + } + if nil == msg { + return "", fmt.Errorf("invalid response") + } + + auth := &authstore.Authorization{} + err = json.NewDecoder(msg).Decode(auth) + if err != nil { + return "", err + } + msgBytes, _ := ioutil.ReadAll(msg) + if "" == auth.PublicKey { + return "", fmt.Errorf("unexpected server response: no public key: %s", string(msgBytes)) + } + if pub != auth.PublicKey { + return "", fmt.Errorf("server disagrees about public key id: %s vs %s", kid, auth.PublicKey) + } + return auth.PublicKey, nil +} diff --git a/mplexer/mgmt/authstore/authstore.go b/mplexer/mgmt/authstore/authstore.go index 7eb448b..ba26874 100644 --- a/mplexer/mgmt/authstore/authstore.go +++ b/mplexer/mgmt/authstore/authstore.go @@ -1,10 +1,13 @@ package authstore import ( + "crypto/rand" "crypto/sha256" "encoding/base64" "errors" "time" + + jwt "github.com/dgrijalva/jwt-go" ) var ErrExists = errors.New("token already exists") @@ -42,3 +45,30 @@ func ToPublicKeyString(secret string) string { } return pub } + +func HMACToken(secret string) (token string, err error) { + keyID := ToPublicKeyString(secret) + + b := make([]byte, 16) + _, _ = rand.Read(b) + claims := &jwt.StandardClaims{ + Id: base64.RawURLEncoding.EncodeToString(b), + IssuedAt: time.Now().Unix(), + ExpiresAt: time.Now().Add(5 * time.Minute).Unix(), + } + + jwtToken := &jwt.Token{ + Header: map[string]interface{}{ + "kid": keyID, + "typ": "JWT", + "alg": jwt.SigningMethodHS256.Alg(), + }, + Claims: claims, + Method: jwt.SigningMethodHS256, + } + + if token, err = jwtToken.SignedString([]byte(secret)); err != nil { + return "", err + } + return token, nil +} diff --git a/mplexer/cmd/mgmt/acmeroutes.go b/mplexer/mgmt/cmd/mgmt/acmeroutes.go similarity index 100% rename from mplexer/cmd/mgmt/acmeroutes.go rename to mplexer/mgmt/cmd/mgmt/acmeroutes.go diff --git a/mplexer/cmd/mgmt/devices.go b/mplexer/mgmt/cmd/mgmt/devices.go similarity index 100% rename from mplexer/cmd/mgmt/devices.go rename to mplexer/mgmt/cmd/mgmt/devices.go diff --git a/mplexer/cmd/mgmt/mgmt.go b/mplexer/mgmt/cmd/mgmt/mgmt.go similarity index 100% rename from mplexer/cmd/mgmt/mgmt.go rename to mplexer/mgmt/cmd/mgmt/mgmt.go diff --git a/mplexer/cmd/mgmt/route.go b/mplexer/mgmt/cmd/mgmt/route.go similarity index 98% rename from mplexer/cmd/mgmt/route.go rename to mplexer/mgmt/cmd/mgmt/route.go index 5c22af5..015fef0 100644 --- a/mplexer/cmd/mgmt/route.go +++ b/mplexer/mgmt/cmd/mgmt/route.go @@ -131,7 +131,7 @@ func routeAll() chi.Router { handleDNSRoutes(r) handleDeviceRoutes(r) - r.Post("/inspect", func(w http.ResponseWriter, r *http.Request) { + r.Get("/inspect", func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() claims, ok := ctx.Value(MWKey("claims")).(*MgmtClaims) if !ok { diff --git a/mplexer/telebit.go b/mplexer/telebit.go index 375bda9..7270967 100644 --- a/mplexer/telebit.go +++ b/mplexer/telebit.go @@ -1,11 +1,14 @@ package telebit import ( + "bytes" "crypto/tls" "errors" "fmt" "io" + "io/ioutil" "net" + "net/http" "os" "time" @@ -254,3 +257,36 @@ func newCertMagic(acme *ACME) (*certmagic.Config, error) { }) return magic, nil } + +func Request(method, fullurl, token string, payload io.Reader) (io.Reader, error) { + HTTPClient := &http.Client{ + Timeout: 15 * time.Second, + } + req, err := http.NewRequest(method, fullurl, payload) + if err != nil { + return nil, err + } + if len(token) > 0 { + req.Header.Set("Authorization", "Bearer "+token) + } + if nil != payload { + req.Header.Set("Content-Type", "application/json") + } + + resp, err := HTTPClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("%d: failed to read response body: %w", resp.StatusCode, err) + } + + if resp.StatusCode >= http.StatusBadRequest { + return nil, fmt.Errorf("%d: request failed: %v", resp.StatusCode, string(body)) + } + + return bytes.NewBuffer(body), nil +}