enable auth checking

This commit is contained in:
AJ ONeal 2020-06-01 01:38:18 -06:00
parent 6736d68446
commit 7303e36a16
11 changed files with 160 additions and 69 deletions

View File

@ -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))
})
})

View File

@ -1,17 +1,14 @@
package main package main
import ( import (
"crypto/rand"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"os" "os"
"time"
"git.coolaj86.com/coolaj86/go-telebitd/mplexer/mgmt/authstore" "git.coolaj86.com/coolaj86/go-telebitd/mplexer/mgmt/authstore"
"github.com/denisbrodbeck/machineid" "github.com/denisbrodbeck/machineid"
jwt "github.com/dgrijalva/jwt-go"
_ "github.com/joho/godotenv/autoload" _ "github.com/joho/godotenv/autoload"
) )
@ -44,14 +41,7 @@ func main() {
return return
} }
b := make([]byte, 16) tok, err := authstore.HMACToken(secret)
_, _ = 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)
if nil != err { if nil != err {
fmt.Fprintf(os.Stderr, "signing error: %s", err) fmt.Fprintf(os.Stderr, "signing error: %s", err)
os.Exit(1) os.Exit(1)
@ -60,25 +50,3 @@ func main() {
fmt.Println(tok) 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
}

View File

@ -17,6 +17,8 @@ import (
telebit "git.coolaj86.com/coolaj86/go-telebitd/mplexer" telebit "git.coolaj86.com/coolaj86/go-telebitd/mplexer"
dns01 "git.coolaj86.com/coolaj86/go-telebitd/mplexer/dns01" 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/caddyserver/certmagic"
"github.com/denisbrodbeck/machineid" "github.com/denisbrodbeck/machineid"
@ -58,23 +60,16 @@ func main() {
enableHTTP01 := flag.Bool("acme-http-01", false, "enable HTTP-01 ACME challenges") 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") 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") 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") 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)") 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)") 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 <from-domain>:<to-port>") locals := flag.String("locals", "", "a list of <from-domain>:<to-port>")
flag.Parse() 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 len(os.Args) >= 2 {
if "version" == os.Args[1] { 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) os.Exit(0)
} }
} }
@ -114,11 +109,19 @@ func main() {
domains = append(domains, domain) 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 "" == *token {
if "" == *secret { if "" == *secret {
*secret = os.Getenv("SECRET") *secret = os.Getenv("SECRET")
} }
*token, err = getToken(*secret, domains) *token, err = authstore.HMACToken(ppid)
} }
if nil != err { if nil != err {
fmt.Fprintf(os.Stderr, "neither secret nor token provided") fmt.Fprintf(os.Stderr, "neither secret nor token provided")
@ -137,6 +140,9 @@ func main() {
if "" == *acmeRelay { if "" == *acmeRelay {
*acmeRelay = strings.Replace(*relay, "ws", "http", 1) // "https://example.com:443" *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") { if "" != os.Getenv("GODADDY_API_KEY") {
id := 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{ acme := &telebit.ACME{
Email: *email, Email: *email,
StoragePath: *certpath, StoragePath: *certpath,

View File

@ -20,4 +20,4 @@ echo "PPID: $my_ppid KeyID: $my_keyid"
TOKEN=$(go run cmd/signjwt/*.go $my_ppid) 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/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}"

60
mplexer/mgmt/auth.go Normal file
View File

@ -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
}

View File

@ -1,10 +1,13 @@
package authstore package authstore
import ( import (
"crypto/rand"
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"errors" "errors"
"time" "time"
jwt "github.com/dgrijalva/jwt-go"
) )
var ErrExists = errors.New("token already exists") var ErrExists = errors.New("token already exists")
@ -42,3 +45,30 @@ func ToPublicKeyString(secret string) string {
} }
return pub 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
}

View File

@ -131,7 +131,7 @@ func routeAll() chi.Router {
handleDNSRoutes(r) handleDNSRoutes(r)
handleDeviceRoutes(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() ctx := r.Context()
claims, ok := ctx.Value(MWKey("claims")).(*MgmtClaims) claims, ok := ctx.Value(MWKey("claims")).(*MgmtClaims)
if !ok { if !ok {

View File

@ -1,11 +1,14 @@
package telebit package telebit
import ( import (
"bytes"
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net" "net"
"net/http"
"os" "os"
"time" "time"
@ -254,3 +257,36 @@ func newCertMagic(acme *ACME) (*certmagic.Config, error) {
}) })
return magic, nil 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
}