From ce39066bc80b3ae38501feea6800a2933ad2f11b Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 20 Jul 2020 12:18:08 -0600 Subject: [PATCH] add --expires-in to signjwt --- README.md | 31 +++++++++++++++++++++++-- cmd/signjwt/signjwt.go | 41 +++++++++++++++++++++++++++++++++- examples/mgmt-ping-as-admin.sh | 1 + mgmt/authstore/authstore.go | 11 +++++++-- 4 files changed, 79 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f425d8e..cb56647 100644 --- a/README.md +++ b/README.md @@ -104,13 +104,22 @@ Copy `examples/mgmt.env` as `.env` in the working directory. Create a token with the same `SECRET` used with the `mgmt` server, and add a device by its `subdomain`. +To build `signjwt`: + +```bash +go build -mod=vendor -ldflags "-s -w" -o signjwt cmd/signjwt/*.go +``` + +To generate an `admin` token: + ```bash VERDOR_ID="test-id" SECRET="xxxxxxxxxxx" -TOKEN=$(go run -mod=vendor cmd/signjwt/*.go \ +TOKEN=$(./signjwt \ + --expires-in 15m \ --vendor-id $VENDOR_ID \ --secret $SECRET \ - --machine-id $SECRET + --machine-ppid $SECRET ) ``` @@ -240,3 +249,21 @@ EOF python3 -m http.server 3000 ``` + +## Glossary + +``` +--vendor-id $VENDOR_ID an arbitrary id used as part of authentication +--secret $SECRET the secret for creating JWTs +--auth-url $AUTH_URL the full url prefix of the server that will validate tokens +--tunnel-relay-url $TUNNEL_RELAY_URL the full url of the websocket tunnel server +--locals $LOCALS a list of `scheme:domainname:port` + for forwarding incoming `domainname` to local `port` +--port-forwards $PORT_FORWARDS a list of `remote:local` tcp port-forwarding +--verbose $VERBOSE logs everything, including abbreviated data (as hex) + $VERBOSE_BYTES logs full data (as hex) + $VERBOSE_RAW logs full data (as string) +--acme-agree $ACME_AGREE agree to the ACME service agreement +--acme-email $ACME_EMAIL the webmaster email for ACME notices +--acme-relay-url $ACME_RELAY_URL the server that will relay ACME DNS-01 requests +``` diff --git a/cmd/signjwt/signjwt.go b/cmd/signjwt/signjwt.go index db1e57e..91417ae 100644 --- a/cmd/signjwt/signjwt.go +++ b/cmd/signjwt/signjwt.go @@ -8,6 +8,7 @@ import ( "flag" "fmt" "os" + "strconv" "git.coolaj86.com/coolaj86/go-telebitd/mgmt/authstore" telebit "git.coolaj86.com/coolaj86/go-telebitd/mplexer" @@ -16,6 +17,15 @@ import ( _ "github.com/joho/godotenv/autoload" ) +var durAbbrs = map[byte]bool{ + 's': true, // second + 'm': true, // minute + 'h': true, // hour + 'd': true, // day + 'w': true, // week + // month and year cannot be measured +} + func main() { var secret, clientSecret, relaySecret string @@ -23,6 +33,7 @@ func main() { machineID := flag.String("machine-id", "", "spoof the raw machine id") 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") + humanExp := flag.String("expires-in", "15m", "set the token to expire units after `iat` (issued at)") getMachinePPID := flag.Bool("machine-ppid-only", false, "just print the machine ppid, not the token") flag.StringVar(&secret, "secret", "", "either the remote server or the tunnel relay secret (used for JWT authentication)") flag.Parse() @@ -31,6 +42,34 @@ func main() { *authURL = os.Getenv("AUTH_URL") } + humanExpLen := len(*humanExp) + if 0 == humanExpLen { + fmt.Fprintf(os.Stderr, "Invalid --expires-in: %q (minimum: 5s)", *humanExp) + } + expNum, _ := strconv.Atoi((*humanExp)[:humanExpLen-1]) + expSuffix := (*humanExp)[humanExpLen-1:] + switch expSuffix { + case "w": + expNum *= 7 + fallthrough + case "d": + expNum *= 24 + fallthrough + case "h": + expNum *= 60 + fallthrough + case "m": + expNum *= 60 + fallthrough + case "s": + // do nothing + default: + fmt.Fprintf(os.Stderr, "Invalid --expires-in: %q (minimum: 5s)", *humanExp) + } + if expNum < 5 { + fmt.Fprintf(os.Stderr, "Invalid --expires-in: %q (minimum: 5s)", *humanExp) + } + if 0 == len(*vendorID) { *vendorID = os.Getenv("VENDOR_ID") } @@ -99,7 +138,7 @@ func main() { fmt.Fprintf(os.Stderr, "[debug] ppid = %s\n", ppid) fmt.Fprintf(os.Stderr, "[debug] pub = %s\n", pub) - tok, err := authstore.HMACToken(ppid) + tok, err := authstore.HMACToken(ppid, expNum) if nil != err { fmt.Fprintf(os.Stderr, "signing error: %s\n", err) os.Exit(1) diff --git a/examples/mgmt-ping-as-admin.sh b/examples/mgmt-ping-as-admin.sh index 833fedb..3d2b1aa 100644 --- a/examples/mgmt-ping-as-admin.sh +++ b/examples/mgmt-ping-as-admin.sh @@ -14,6 +14,7 @@ AUTH_URL="${AUTH_URL:-"http://localhost:3000/api"}" echo "RELAY_SECRET: $RELAY_SECRET" TOKEN=$(go run cmd/signjwt/*.go \ + --expires-in 1m \ --vendor-id "$VENDOR_ID" \ --secret "$RELAY_SECRET" \ --machine-ppid "$RELAY_SECRET" diff --git a/mgmt/authstore/authstore.go b/mgmt/authstore/authstore.go index f23c910..42473bd 100644 --- a/mgmt/authstore/authstore.go +++ b/mgmt/authstore/authstore.go @@ -51,12 +51,19 @@ func ToPublicKeyString(secret string) string { return pub } -func HMACToken(secret string) (token string, err error) { +func HMACToken(secret string, maybeExp ...int) (token string, err error) { keyID := ToPublicKeyString(secret) if dbg.Debug { fmt.Fprintf(os.Stderr, "[debug] keyID=%s\n", keyID) } + var exp int64 + if 0 == len(maybeExp) || 0 == maybeExp[0] { + exp = time.Now().Add(5 * time.Minute).Unix() + } else { + exp = time.Now().Add(time.Duration(maybeExp[0]) * time.Second).Unix() + } + b := make([]byte, 16) _, _ = rand.Read(b) claims := &jwt.StandardClaims{ @@ -64,7 +71,7 @@ func HMACToken(secret string) (token string, err error) { Subject: "", // TODO Issuer: "", // TODO IssuedAt: time.Now().Unix(), - ExpiresAt: time.Now().Add(5 * time.Minute).Unix(), + ExpiresAt: exp, } jwtToken := &jwt.Token{