From ade7f0ba80158fa6003259cdd881a6d5048b8cf5 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Fri, 13 Nov 2020 02:43:17 -0700 Subject: [PATCH] refactor cmds for goreleaser --- .goreleaser.yml | 48 +++ README.md | 352 ++++++------------ cmd/mgmt/README.md | 105 +++++- cmd/mgmt/mgmt.go | 38 +- cmd/mgmt/postgres.go | 3 - cmd/telebit/README.md | 79 ++++ cmd/telebit/telebit.go | 17 +- examples/client.env | 64 +++- examples/run-as-client.sh | 5 +- {cmd => internal}/mgmt/acmeroutes.go | 2 +- {mgmt => internal/mgmt}/auth.go | 2 +- .../mgmt}/authstore/authstore.go | 0 .../authstore/authstore_postgres_test.go | 0 .../mgmt}/authstore/authstore_test.go | 0 {mgmt => internal/mgmt}/authstore/insert.sql | 0 .../mgmt}/authstore/postgres.init.sql | 0 .../mgmt}/authstore/postgresql.go | 0 {cmd => internal}/mgmt/devices.go | 5 +- internal/mgmt/mgmt.go | 28 ++ internal/mgmt/postgres.go | 4 + {cmd => internal}/mgmt/route.go | 15 +- {cmd => internal}/telebit/admin.go | 15 +- {cmd => internal}/telebit/authorizer.go | 4 +- 23 files changed, 474 insertions(+), 312 deletions(-) create mode 100644 .goreleaser.yml delete mode 100644 cmd/mgmt/postgres.go rename {cmd => internal}/mgmt/acmeroutes.go (99%) rename {mgmt => internal/mgmt}/auth.go (96%) rename {mgmt => internal/mgmt}/authstore/authstore.go (100%) rename {mgmt => internal/mgmt}/authstore/authstore_postgres_test.go (100%) rename {mgmt => internal/mgmt}/authstore/authstore_test.go (100%) rename {mgmt => internal/mgmt}/authstore/insert.sql (100%) rename {mgmt => internal/mgmt}/authstore/postgres.init.sql (100%) rename {mgmt => internal/mgmt}/authstore/postgresql.go (100%) rename {cmd => internal}/mgmt/devices.go (98%) create mode 100644 internal/mgmt/mgmt.go create mode 100644 internal/mgmt/postgres.go rename {cmd => internal}/mgmt/route.go (96%) rename {cmd => internal}/telebit/admin.go (97%) rename {cmd => internal}/telebit/authorizer.go (95%) diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..890d9ee --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,48 @@ +# This is an example goreleaser.yaml file with some sane defaults. +# Make sure to check the documentation at http://goreleaser.com +before: + hooks: + # You may remove this if you don't use go modules. + - go mod download + # you may remove this if you don't need go generate + - go generate ./... +builds: + - id: telebit-client + main: ./cmd/telebit/telebit.go + env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin + goarch: + - amd64 + - arm64 + - arm + - id: telebit-mgmt + main: ./cmd/mgmt/mgmt.go + env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin + goarch: + - amd64 + - arm64 + - arm +archives: + - replacements: + 386: i386 + amd64: x86_64 + arm64: aarch64 +checksum: + name_template: 'checksums.txt' +snapshot: + name_template: "{{ .Tag }}-next" +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' diff --git a/README.md b/README.md index 5894490..3b70eb0 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,45 @@ # Telebit +| **Telebit Client** | [Telebit Relay](/tree/master/cmd/telebit) | [Telebit Mgmt](/tree/master/cmd/mgmt) | + A secure, end-to-end Encrypted tunnel. Because friends don't let friends localhost. +# Usage + +```bash +telebit --env ./.env --verbose +``` + +Command-line flags or `.env` may be used. + +```bash +# --acme-agree +export ACME_AGREE=true +# --acme-email +export ACME_EMAIL=johndoe@example.com +# --vendor-id +export VENDOR_ID=example.com +# --secret +export SECRET=QQgPyfzVdxJTcUc1ceot3pgJFKtWSHMQ +# --tunnel-relay +export TUNNEL_RELAY_URL=https://tunnel.example.com/ +# --tls-locals +export TLS_LOCALS=https:*:3000 +``` + +See `./telebit --help` for all options. \ +See [`examples/client.env`][client-env] for detail explanations. + +[client-env]: /tree/master/examples/client.env + +# Build + +```bash +goreleaser --rm-dist --skip-publish +``` + ## Install Go Installs Go to `~/.local/opt/go` for MacOS and Linux: @@ -37,239 +73,6 @@ The binary can be built with `VENDOR_ID` and `CLIENT_SECRET` built into the bina You can also change the `serviceName` and `serviceDescription` at build time. See `examples/run-as-client.sh`. -### Configure - -Command-line flags or `.env` may be used. - -See `./telebit --help` for all options, and `examples/relay.env` for their corresponding ENVs. - -### Example - -Copy `examples/relay.env` as `.env` in the working directory. - -```bash -# For Tunnel Relay Server -API_HOSTNAME=devices.example.com -LISTEN=:443 -LOCALS=https:mgmt.devices.example.com:3010 -VERBOSE=false - -# For Device Management & Authentication -AUTH_URL=http://localhost:3010/api - -# For Let's Encrypt / ACME registration -ACME_AGREE=true -ACME_EMAIL=letsencrypt@example.com - -# For Let's Encrypt / ACME challenges -ACME_HTTP_01_RELAY_URL=http://localhost:3010/api/http -ACME_RELAY_URL=http://localhost:3010/api/dns -SECRET=xxxxxxxxxxxxxxxx -GODADDY_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -GODADDY_API_SECRET=xxxxxxxxxxxxxxxxxxxxxx -``` - -Note: It is not necessary to specify the `--flags` when using the ENVs. - -```bash -./telebit \ - --api-hostname $API_HOSTNAME \ - --auth-url "$AUTH_URL" \ - --acme-agree "$ACME_AGREE" \ - --acme-email "$ACME_EMAIL" \ - --secret "$SECRET" \ - --listen "$LISTEN" -``` - -### API - -List all connected devices - -```bash -bash examples/admin-list-devices.sh -``` - -```bash -curl -L https://devices.example.com/api/subscribers -H "Authorization: Bearer ${TOKEN}" -``` - -```json -{ - "success": true, - "subscribers": [{ "since": "2020-07-22T08:20:40Z", "sub": "ruby", "sockets": ["73.228.72.97:50737"], "clients": 0 }] -} -``` - -Show connectivity, of a single device, if any - -```bash -curl -L https://devices.example.com/api/subscribers -H "Authorization: Bearer ${TOKEN}" -``` - -```json -{ - "success": true, - "subscribers": [{ "since": "2020-07-22T08:20:40Z", "sub": "ruby", "sockets": ["73.228.72.97:50737"], "clients": 0 }] -} -``` - -Force a device to disconnect: - -```bash -bash examples/admin-disconnect-device.sh -``` - -```bash -my_subdomain="ruby" -curl -X DELETE http://mgmt.example.com:3010/api/subscribers/ruby" -H "Authorization: Bearer ${TOKEN}" -``` - -```json -{ "success": true } -``` - -## Management Server - -```bash -go generate ./... - -CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -mod vendor -o mgmt-server-linux ./cmd/mgmt/*.go -CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -mod vendor -o mgmt-server-macos ./cmd/mgmt/*.go -CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -mod vendor -o mgmt-server-windows-debug.exe ./cmd/mgmt/*.go -CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -mod vendor -ldflags "-H windowsgui" -o mgmt-server-windows.exe ./cmd/mgmt/*.go -``` - -### Example - -```bash -./telebit-mgmt --domain devices.example.com --port 3010 -``` - -Copy `examples/mgmt.env` as `.env` in the working directory. - -### Device Management API - -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 -VENDOR_ID="test-id" -SECRET="xxxxxxxxxxx" -TOKEN=$(./signjwt \ - --expires-in 15m \ - --vendor-id $VENDOR_ID \ - --secret $SECRET \ - --machine-ppid $SECRET -) -``` - -Authorize a device: - -```bash -my_subdomain="xxxx" -my_mgmt_host=http://mgmt.example.com:3010 -curl -X POST $my_mgmt_host/api/devices \ - -H "Authorization: Bearer ${TOKEN}" \ - -H "Content-Type: application/json" \ - -d '{ "slug": "'$my_subdomain'" }' -``` - -```json -{ "shared_key": "ZZZZZZZZ" } -``` - -Show data of a single device - -```bash -my_subdomain="xxxx" -curl -L http://mgmt.example.com:3010/api/devices/${my_subdomain} -H "Authorization: Bearer ${TOKEN}" -``` - -```json -{ "subdomain": "sub1", "updated_at": "2020-05-20T12:00:01Z" } -``` - -Get a list of connected devices: - -```bash -curl -L http://mgmt.example.com:3010/api/devices -H "Authorization: Bearer ${TOKEN}" -``` - -```json -[{ "subdomain": "sub1", "updated_at": "2020-05-20T12:00:01Z" }] -``` - -Get a list of disconnected devices: - -```bash -curl -L http://mgmt.example.com:3010/api/devices?inactive=true -H "Authorization: Bearer ${TOKEN}" -``` - -Deauthorize a device: - -```bash -my_subdomain="xxxx" -curl -L -X DELETE http://mgmt.example.com:3010/api/devices/${my_subdomain} -H "Authorization: Bearer ${TOKEN}" -``` - -## Tunnel Client - -The tunnel relay binary is also the client binary. - -You do not need to build a separate client binary. - -### Configure - -Command-line flags or `.env` may be used. - -See `./telebit --help` for all options, and `examples/client.env` for their corresponding ENVs. - -### Example - -Copy `examples/client.env` as `.env` in the working directory. - -```bash -# For Client -VENDOR_ID=test-id -CLIENT_SUBJECT=newieb -CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxx -AUTH_URL="https://mgmt.devices.example.com/api" -TUNNEL_RELAY_URL=https://devices.example.com/ -LOCALS=https:newbie.devices.example.com:3000,http:newbie.devices.example.com:3000 -#PORT_FORWARDS=3443:3001,8443:3002 - -# For Debugging -VERBOSE=true -#VERBOSE_BYTES=true -#VERBOSE_RAW=true - -# For Let's Encrypt / ACME registration -ACME_AGREE=true -ACME_EMAIL=letsencrypt@example.com - -# For Let's Encrypt / ACME challenges -ACME_RELAY_URL="https://mgmt.devices.example.com/api/dns" -``` - -```bash -./telebit \ - --vendor-id "$VENDOR_ID" \ - --secret "$CLIENT_SECRET" \ - --tunnel-relay-url $TUNNEL_RELAY_URL \ - --locals "$LOCALS" \ - --acme-agree="$ACME_AGREE" \ - --acme-email "$ACME_EMAIL" \ - --verbose=$VERBOSE -``` - ## Local Web Application Currently only raw TCP is tunneled. @@ -288,20 +91,75 @@ EOF python3 -m http.server 3000 ``` -## Glossary +## Help ``` ---vendor-id $VENDOR_ID an arbitrary id used as part of authentication ---secret $SECRET the secret for creating JWTs ---tunnel-relay-url $TUNNEL_RELAY_URL the url of the tunnel server ---auth-url $AUTH_URL use to override the server-provided auth url ---acme-relay-url $ACME_RELAY_URL use to override the server-provided acme dns 01 proxy ---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 +Usage of telebit: + ACME_AGREE + --acme-agree + agree to the terms of the ACME service provider (required) + --acme-directory string + ACME Directory URL + ACME_EMAIL + --acme-email string + email to use for Let's Encrypt / ACME registration + --acme-http-01 + enable HTTP-01 ACME challenges + ACME_HTTP_01_RELAY_URL + --acme-http-01-relay-url string + the base url of the ACME HTTP-01 relay, if not the same as the DNS-01 relay + --acme-relay-url string + the base url of the ACME DNS-01 relay, if not the same as the tunnel relay + --acme-staging + get fake certificates for testing + --acme-storage string + path to ACME storage directory (default "./acme.d/") + --acme-tls-alpn-01 + enable TLS-ALPN-01 ACME challenges + API_HOSTNAME + --api-hostname string + the hostname used to manage clients + --auth-url string + the base url for authentication, if not the same as the tunnel relay + DEBUG + --debug + show debug output (default true) + --dns-01-delay duration + add an extra delay after dns self-check to allow DNS-01 challenges to propagate + --dns-resolvers string + a list of resolvers in the format 8.8.8.8:53,8.8.4.4:53 + --env string + path to .env file + --leeway duration + allow for time drift / skew (hard-coded to 15 minutes) (default 15m0s) + LISTEN + --listen string + list of bind addresses on which to listen, such as localhost:80, or :443 + LOCALS + --locals string + a list of : + PORT_FORWARD + --port-forward string + a list of : for raw port-forwarding + SECRET + --secret string + the same secret used by telebit-relay (used for JWT authentication) + --spf-domain string + domain with SPF-like list of IP addresses which are allowed to connect to clients + TLS_LOCALS + --tls-locals string + like --locals, but TLS will be used to connect to the local port + --token string + an auth token for the server (instead of generating --secret); use --token=false to ignore any $TOKEN in env + TUNNEL_RELAY_URL + --tunnel-relay-url string + the websocket url at which to connect to the tunnel relay + VENDOR_ID + --vendor-id string + a unique identifier for a deploy target environment + VERBOSE + VERBOSE_BYTES + VERBOSE_RAW + --verbose + log excessively ``` diff --git a/cmd/mgmt/README.md b/cmd/mgmt/README.md index b4b283c..e543196 100644 --- a/cmd/mgmt/README.md +++ b/cmd/mgmt/README.md @@ -1,4 +1,4 @@ -# MGMT Server +# Telebit Mgmt # Config @@ -18,6 +18,17 @@ NAMECOM_USERNAME=johndoe NAMECOM_API_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ``` +## API + +```bash +my_subdomain="ruby" +curl -X DELETE http://mgmt.example.com:3010/api/subscribers/ruby" -H "Authorization: Bearer ${TOKEN}" +``` + +```json +{ "success": true } +``` + # Build ```bash @@ -27,3 +38,95 @@ pushd cmd/mgmt go build -mod vendor -o telebit-mgmt popd ``` + +## Management Server + +```bash +go generate ./... + +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -mod vendor -o mgmt-server-linux ./cmd/mgmt/*.go +CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -mod vendor -o mgmt-server-macos ./cmd/mgmt/*.go +CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -mod vendor -o mgmt-server-windows-debug.exe ./cmd/mgmt/*.go +CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -mod vendor -ldflags "-H windowsgui" -o mgmt-server-windows.exe ./cmd/mgmt/*.go +``` + +### Example + +```bash +./telebit-mgmt --domain devices.example.com --port 3010 +``` + +Copy `examples/mgmt.env` as `.env` in the working directory. + +### Device Management API + +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 +VENDOR_ID="test-id" +SECRET="xxxxxxxxxxx" +TOKEN=$(./signjwt \ + --expires-in 15m \ + --vendor-id $VENDOR_ID \ + --secret $SECRET \ + --machine-ppid $SECRET +) +``` + +Authorize a device: + +```bash +my_subdomain="xxxx" +my_mgmt_host=http://mgmt.example.com:3010 +curl -X POST $my_mgmt_host/api/devices \ + -H "Authorization: Bearer ${TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{ "slug": "'$my_subdomain'" }' +``` + +```json +{ "shared_key": "ZZZZZZZZ" } +``` + +Show data of a single device + +```bash +my_subdomain="xxxx" +curl -L http://mgmt.example.com:3010/api/devices/${my_subdomain} -H "Authorization: Bearer ${TOKEN}" +``` + +```json +{ "subdomain": "sub1", "updated_at": "2020-05-20T12:00:01Z" } +``` + +Get a list of connected devices: + +```bash +curl -L http://mgmt.example.com:3010/api/devices -H "Authorization: Bearer ${TOKEN}" +``` + +```json +[{ "subdomain": "sub1", "updated_at": "2020-05-20T12:00:01Z" }] +``` + +Get a list of disconnected devices: + +```bash +curl -L http://mgmt.example.com:3010/api/devices?inactive=true -H "Authorization: Bearer ${TOKEN}" +``` + +Deauthorize a device: + +```bash +my_subdomain="xxxx" +curl -L -X DELETE http://mgmt.example.com:3010/api/devices/${my_subdomain} -H "Authorization: Bearer ${TOKEN}" +``` diff --git a/cmd/mgmt/mgmt.go b/cmd/mgmt/mgmt.go index c2e9aa3..78df0c0 100644 --- a/cmd/mgmt/mgmt.go +++ b/cmd/mgmt/mgmt.go @@ -10,12 +10,14 @@ import ( "os" "strings" - "git.rootprojects.org/root/telebit/mgmt/authstore" + "git.rootprojects.org/root/telebit/internal/mgmt" + "git.rootprojects.org/root/telebit/internal/mgmt/authstore" "github.com/go-acme/lego/v3/challenge" "github.com/go-acme/lego/v3/providers/dns/duckdns" "github.com/go-acme/lego/v3/providers/dns/godaddy" "github.com/go-acme/lego/v3/providers/dns/namedotcom" + _ "github.com/joho/godotenv/autoload" ) @@ -28,14 +30,8 @@ var ( GitTimestamp = "0000-00-00T00:00:00+0000" ) -// MWKey is a type guard -type MWKey string - var store authstore.Store -var provider challenge.Provider = nil // TODO is this concurrency-safe? var secret string -var primaryDomain string -var relayDomain string func help() { fmt.Fprintf(os.Stderr, "Usage: mgmt --domain --secret <128-bit secret>\n") @@ -59,22 +55,22 @@ func main() { "database (postgres) connection url") flag.StringVar(&secret, "secret", "", "a >= 16-character random string for JWT key signing") - flag.StringVar(&primaryDomain, "domain", "", + flag.StringVar(&mgmt.DeviceDomain, "domain", "", "the base domain to use for all clients") - flag.StringVar(&relayDomain, "tunnel-domain", "", + flag.StringVar(&mgmt.RelayDomain, "tunnel-domain", "", "the domain name of the tunnel relay service, if different from base domain") flag.Parse() - if 0 == len(primaryDomain) { - primaryDomain = os.Getenv("DOMAIN") + if 0 == len(mgmt.DeviceDomain) { + mgmt.DeviceDomain = os.Getenv("DOMAIN") } - if 0 == len(relayDomain) { - relayDomain = os.Getenv("TUNNEL_DOMAIN") + if 0 == len(mgmt.RelayDomain) { + mgmt.RelayDomain = os.Getenv("TUNNEL_DOMAIN") } - if 0 == len(relayDomain) { - relayDomain = primaryDomain + if 0 == len(mgmt.RelayDomain) { + mgmt.RelayDomain = mgmt.DeviceDomain } if 0 == len(dbURL) { @@ -99,6 +95,8 @@ func main() { lnAddr = "localhost:" + port } + // TODO are these concurrency-safe? + var provider challenge.Provider = nil if len(os.Getenv("GODADDY_API_KEY")) > 0 { id := os.Getenv("GODADDY_API_KEY") apiSecret := os.Getenv("GODADDY_API_SECRET") @@ -120,7 +118,7 @@ func main() { fmt.Println("DNS-01 relay disabled") } - if 0 == len(primaryDomain) || 0 == len(secret) || 0 == len(dbURL) { + if 0 == len(mgmt.DeviceDomain) || 0 == len(secret) || 0 == len(dbURL) { help() os.Exit(1) return @@ -134,7 +132,7 @@ func main() { connStr += "?sslmode=required" } - store, err = authstore.NewStore(connStr, initSQL) + store, err = authstore.NewStore(connStr, mgmt.InitSQL) if nil != err { log.Fatal("connection error", err) return @@ -142,16 +140,18 @@ func main() { _ = store.SetMaster(secret) defer store.Close() + mgmt.Init(store, provider) + go func() { fmt.Println("Listening for ACME challenges on :" + challengesPort) - if err := http.ListenAndServe(":"+challengesPort, routeStatic()); nil != err { + if err := http.ListenAndServe(":"+challengesPort, mgmt.RouteStatic()); nil != err { log.Fatal(err) os.Exit(1) } }() fmt.Println("Listening on", lnAddr) - fmt.Fprintf(os.Stderr, "failed: %s", http.ListenAndServe(lnAddr, routeAll())) + fmt.Fprintf(os.Stderr, "failed: %s", http.ListenAndServe(lnAddr, mgmt.RouteAll())) } // newNameDotComDNSProvider is for the sake of demoing the tunnel diff --git a/cmd/mgmt/postgres.go b/cmd/mgmt/postgres.go deleted file mode 100644 index dffa7f2..0000000 --- a/cmd/mgmt/postgres.go +++ /dev/null @@ -1,3 +0,0 @@ -package main - -var initSQL = "./postgres.init.sql" diff --git a/cmd/telebit/README.md b/cmd/telebit/README.md index e69de29..e7cb464 100644 --- a/cmd/telebit/README.md +++ b/cmd/telebit/README.md @@ -0,0 +1,79 @@ +# Telebit Relay + +| [Telebit Client](../../) | **Telebit Relay** | [Telebit Mgmt](../mgmt) | + +### Example + +Copy `examples/relay.env` as `.env` in the working directory. + +```bash +# For Tunnel Relay Server +API_HOSTNAME=devices.example.com +LISTEN=:443 +LOCALS=https:mgmt.devices.example.com:3010 +VERBOSE=false + +# For Device Management & Authentication +AUTH_URL=http://localhost:3010/api + +# For Let's Encrypt / ACME registration +ACME_AGREE=true +ACME_EMAIL=letsencrypt@example.com + +# For Let's Encrypt / ACME challenges +ACME_HTTP_01_RELAY_URL=http://localhost:3010/api/http +ACME_RELAY_URL=http://localhost:3010/api/dns +SECRET=xxxxxxxxxxxxxxxx +GODADDY_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +GODADDY_API_SECRET=xxxxxxxxxxxxxxxxxxxxxx +``` + +Note: It is not necessary to specify the `--flags` when using the ENVs. + +```bash +./telebit \ + --api-hostname $API_HOSTNAME \ + --auth-url "$AUTH_URL" \ + --acme-agree "$ACME_AGREE" \ + --acme-email "$ACME_EMAIL" \ + --secret "$SECRET" \ + --listen "$LISTEN" +``` + +### API + +List all connected devices + +```bash +bash examples/admin-list-devices.sh +``` + +```bash +curl -L https://devices.example.com/api/subscribers -H "Authorization: Bearer ${TOKEN}" +``` + +```json +{ + "success": true, + "subscribers": [{ "since": "2020-07-22T08:20:40Z", "sub": "ruby", "sockets": ["73.228.72.97:50737"], "clients": 0 }] +} +``` + +Show connectivity, of a single device, if any + +```bash +curl -L https://devices.example.com/api/subscribers -H "Authorization: Bearer ${TOKEN}" +``` + +```json +{ + "success": true, + "subscribers": [{ "since": "2020-07-22T08:20:40Z", "sub": "ruby", "sockets": ["73.228.72.97:50737"], "clients": 0 }] +} +``` + +Force a device to disconnect: + +```bash +bash examples/admin-disconnect-device.sh +``` diff --git a/cmd/telebit/telebit.go b/cmd/telebit/telebit.go index a9e7838..5e994ec 100644 --- a/cmd/telebit/telebit.go +++ b/cmd/telebit/telebit.go @@ -11,6 +11,7 @@ import ( "fmt" "io" "net" + "net/http" "net/url" "os" "regexp" @@ -22,21 +23,23 @@ import ( "git.rootprojects.org/root/telebit/dbg" "git.rootprojects.org/root/telebit/internal/dns01" "git.rootprojects.org/root/telebit/internal/http01" + "git.rootprojects.org/root/telebit/internal/mgmt" + "git.rootprojects.org/root/telebit/internal/mgmt/authstore" "git.rootprojects.org/root/telebit/internal/service" + telebitX "git.rootprojects.org/root/telebit/internal/telebit" "git.rootprojects.org/root/telebit/iplist" - "git.rootprojects.org/root/telebit/mgmt" - "git.rootprojects.org/root/telebit/mgmt/authstore" "git.rootprojects.org/root/telebit/table" "git.rootprojects.org/root/telebit/tunnel" - legoDNS01 "github.com/go-acme/lego/v3/challenge/dns01" "github.com/coolaj86/certmagic" "github.com/denisbrodbeck/machineid" jwt "github.com/dgrijalva/jwt-go" "github.com/go-acme/lego/v3/challenge" + legoDNS01 "github.com/go-acme/lego/v3/challenge/dns01" "github.com/go-acme/lego/v3/providers/dns/duckdns" "github.com/go-acme/lego/v3/providers/dns/godaddy" "github.com/go-acme/lego/v3/providers/dns/namedotcom" + "github.com/go-chi/chi" "github.com/joho/godotenv" _ "github.com/joho/godotenv/autoload" ) @@ -80,8 +83,6 @@ type Forward struct { localTLS bool } -var authorizer telebit.Authorizer - var isHostname = regexp.MustCompile(`^[A-Za-z0-9_\.\-]+$`).MatchString // VendorID may be baked in, or supplied via ENVs or --args @@ -383,7 +384,6 @@ func main() { return } fmt.Println("Auth URL", *authURL) - authorizer = NewAuthorizer(*authURL) dns01Base := directory.DNS01Proxy.URL if 0 == len(*acmeRelay) { @@ -428,7 +428,6 @@ func main() { fmt.Printf("[Grants]\n\t%#v\n", grants) *relay = grants.Audience } - authorizer = NewAuthorizer(*authURL) fmt.Printf("Email: %q\n", *email) @@ -590,9 +589,11 @@ func muxAll( } if "" != *apiHostname { // this is a generic net listener - InitAdmin(*authURL) + r := chi.NewRouter() + telebitX.RouteAdmin(*authURL, r) apiListener := tunnel.NewListener() go func() { + httpsrv := &http.Server{Handler: r} httpsrv.Serve(apiListener) }() fmt.Printf("Will respond to Websocket and API requests to %q\n", *apiHostname) diff --git a/examples/client.env b/examples/client.env index 934bd4d..dc9382a 100644 --- a/examples/client.env +++ b/examples/client.env @@ -1,3 +1,17 @@ +# VERBOSE +# Show more output in the logs +#VERBOSE=true + +# DEBUG +# Show binary output in the longs too +#DEBUG=true + +# Used for Let's Encrypt registration +# ACME_AGREE +ACME_AGREE=true +# ACME_EMAIL +ACME_EMAIL=johndoe@example.com + # TUNNEL_RELAY_URL # The URL of the Telebit Relay, of course. # Note that many client configuration details can be preassigned at @@ -9,23 +23,53 @@ TUNNEL_RELAY_URL=https://devices.example.com/ # It's just as well to generate a random ID for your organization. VENDOR_ID= -# Used for Let's Encrypt registration -# ACME_AGREE -ACME_AGREE=true -# ACME_EMAIL -ACME_EMAIL=johndoe@example.com +# SECRET +# This is the shared secret between the client device +# and the device management server. +SECRET=shared-secret # CLIENT_SUBJECT (optional) # NOT used by Telebit. -# This is for the Device Management & Authentication server. +# This is for the example scripts +# (for the Device Management & Authentication server) CLIENT_SUBJECT=newbie CLIENT_SECRET=xxxxxxxxxxxxxxxx -LOCALS=https:$CLIENT_SUBJECT.devices.example.com:3000,https:*.$CLIENT_SUBJECT.devices.example.com:3000 -#ACME_HTTP_01_RELAY_URL=http://localhost:4200/api/http -#PORT_FORWARDS=3443:3001,8443:3002 -#DUCKDNS_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + +# TLS_LOCALS +# ReverseProxy any matching requests to the given local port. +# This DOES NOT terminate TLS +TLS_LOCALS=https:*:3000 + +# LOCALS +# ReverseProxy any matching requests to the given local port. +# This terminates TLS +# Ex: LOCALS=https:$CLIENT_SUBJECT.devices.example.com:3000,https:*.$CLIENT_SUBJECT.devices.example.com:3000 +LOCALS=https:*:3000 + +# PORT_FORWARDS +# ReverseProxy any matching TCP streams from the given remote incoming port, +# directly to the given destination port. +PORT_FORWARDS=3443:3001,8443:3002 # AUTH_URL # The client may receive this as `.authn.url` # through `https://$API_DOMAIN/.well-known/telebit.app/index.json` # Setting the ENV AUTH_URL or the flag --auth-url overrides the discovery endpoint + +# ACME_HTTP_01_RELAY_URL +# Overrides `.acme_http_01_relay.url` +# from `https://$API_DOMAIN/.well-known/telebit.app/index.json` +ACME_HTTP_01_RELAY_URL=https://tunnel.example.com/api/acme-relay + +# ACME_RELAY_URL (deprecated) +# Overrides `.acme_dns_01_relay.url` +# from `https://$API_DOMAIN/.well-known/telebit.app/index.json` +#ACME_RELAY_URL=https://tunnel.example.com/api/acme-relay + +# ACME DNS-01 Challenge Strategies +# Rather than use the http-01 or dns-01 relay you can set one of these +#DUCKDNS_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +#NAMECOM_USERNAME= +#NAMECOM_API_TOKEN= +#GODADDY_API_KEY= +#GODADDY_API_SECRET= \ No newline at end of file diff --git a/examples/run-as-client.sh b/examples/run-as-client.sh index 9baebd1..949e033 100644 --- a/examples/run-as-client.sh +++ b/examples/run-as-client.sh @@ -11,8 +11,9 @@ CLIENT_SECRET="${CLIENT_SECRET:-}" #go build -mod=vendor -o ./telebit \ # -ldflags="-X 'main.VendorID=$VENDOR_ID' -X 'main.ClientSecret=$CLIENT_SECRET' -X 'main.serviceName=telebit' -X 'main.serviceDesc=securely tunnel through telebit.io'" \ # cmd/telebit/*.go -go build -mod=vendor -o telebit \ - cmd/telebit/*.go +pushd cmd/telebit + go build -mod=vendor -o telebit . +popd # For Device Authorization across services #AUTH_URL=${AUTH_URL:-"https://devices.examples.com/api"} diff --git a/cmd/mgmt/acmeroutes.go b/internal/mgmt/acmeroutes.go similarity index 99% rename from cmd/mgmt/acmeroutes.go rename to internal/mgmt/acmeroutes.go index 5de7543..61a5455 100644 --- a/cmd/mgmt/acmeroutes.go +++ b/internal/mgmt/acmeroutes.go @@ -1,4 +1,4 @@ -package main +package mgmt import ( "encoding/json" diff --git a/mgmt/auth.go b/internal/mgmt/auth.go similarity index 96% rename from mgmt/auth.go rename to internal/mgmt/auth.go index 5f2d098..be67a4a 100644 --- a/mgmt/auth.go +++ b/internal/mgmt/auth.go @@ -9,7 +9,7 @@ import ( telebit "git.rootprojects.org/root/telebit" "git.rootprojects.org/root/telebit/dbg" - "git.rootprojects.org/root/telebit/mgmt/authstore" + "git.rootprojects.org/root/telebit/internal/mgmt/authstore" ) type SuccessResponse struct { diff --git a/mgmt/authstore/authstore.go b/internal/mgmt/authstore/authstore.go similarity index 100% rename from mgmt/authstore/authstore.go rename to internal/mgmt/authstore/authstore.go diff --git a/mgmt/authstore/authstore_postgres_test.go b/internal/mgmt/authstore/authstore_postgres_test.go similarity index 100% rename from mgmt/authstore/authstore_postgres_test.go rename to internal/mgmt/authstore/authstore_postgres_test.go diff --git a/mgmt/authstore/authstore_test.go b/internal/mgmt/authstore/authstore_test.go similarity index 100% rename from mgmt/authstore/authstore_test.go rename to internal/mgmt/authstore/authstore_test.go diff --git a/mgmt/authstore/insert.sql b/internal/mgmt/authstore/insert.sql similarity index 100% rename from mgmt/authstore/insert.sql rename to internal/mgmt/authstore/insert.sql diff --git a/mgmt/authstore/postgres.init.sql b/internal/mgmt/authstore/postgres.init.sql similarity index 100% rename from mgmt/authstore/postgres.init.sql rename to internal/mgmt/authstore/postgres.init.sql diff --git a/mgmt/authstore/postgresql.go b/internal/mgmt/authstore/postgresql.go similarity index 100% rename from mgmt/authstore/postgresql.go rename to internal/mgmt/authstore/postgresql.go diff --git a/cmd/mgmt/devices.go b/internal/mgmt/devices.go similarity index 98% rename from cmd/mgmt/devices.go rename to internal/mgmt/devices.go index 36b3bab..4bacc38 100644 --- a/cmd/mgmt/devices.go +++ b/internal/mgmt/devices.go @@ -1,4 +1,4 @@ -package main +package mgmt import ( "crypto/rand" @@ -10,7 +10,8 @@ import ( "strings" "time" - "git.rootprojects.org/root/telebit/mgmt/authstore" + "git.rootprojects.org/root/telebit/internal/mgmt/authstore" + "github.com/go-chi/chi" ) diff --git a/internal/mgmt/mgmt.go b/internal/mgmt/mgmt.go new file mode 100644 index 0000000..6e008a6 --- /dev/null +++ b/internal/mgmt/mgmt.go @@ -0,0 +1,28 @@ +package mgmt + +import ( + "git.rootprojects.org/root/telebit/internal/mgmt/authstore" + + "github.com/go-acme/lego/v3/challenge" +) + +var store authstore.Store + +var provider challenge.Provider = nil + +// DeviceDomain is the base hostname used for devices, such as devices.example.com +// which has devices as foo.devices.example.com +var DeviceDomain string + +// RelayDomain is the API hostname used for the tunnel +// ( currently NOT used, but will be used for wss://RELAY_DOMAIN/ ) +var RelayDomain string + +// MWKey is a type guard +type MWKey string + +// Init initializes some package variables +func Init(s authstore.Store, p challenge.Provider) { + store = s + provider = p +} diff --git a/internal/mgmt/postgres.go b/internal/mgmt/postgres.go new file mode 100644 index 0000000..0a092d1 --- /dev/null +++ b/internal/mgmt/postgres.go @@ -0,0 +1,4 @@ +package mgmt + +// InitSQL is the filepath to the SQL file used to initialize the database on each start +var InitSQL = "./postgres.init.sql" diff --git a/cmd/mgmt/route.go b/internal/mgmt/route.go similarity index 96% rename from cmd/mgmt/route.go rename to internal/mgmt/route.go index 41c87e4..2d10614 100644 --- a/cmd/mgmt/route.go +++ b/internal/mgmt/route.go @@ -1,4 +1,4 @@ -package main +package mgmt import ( "context" @@ -12,7 +12,8 @@ import ( "time" "git.rootprojects.org/root/telebit/dbg" - "git.rootprojects.org/root/telebit/mgmt/authstore" + "git.rootprojects.org/root/telebit/internal/mgmt/authstore" + "github.com/dgrijalva/jwt-go" "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" @@ -27,7 +28,7 @@ type MgmtClaims struct { var presenters = make(chan *Challenge) var cleanups = make(chan *Challenge) -func routeStatic() chi.Router { +func RouteStatic() chi.Router { r := chi.NewRouter() r.Use(middleware.Logger) @@ -49,7 +50,7 @@ func routeStatic() chi.Router { return r } -func routeAll() chi.Router { +func RouteAll() chi.Router { go func() { for { @@ -125,8 +126,8 @@ func routeAll() chi.Router { return nil, fmt.Errorf("invalid jwt payload 'sub' (mismatch)") } claims.Subject = claims.Slug - claims.Issuer = primaryDomain - claims.Audience = fmt.Sprintf("wss://%s/ws", relayDomain) + claims.Issuer = DeviceDomain + claims.Audience = fmt.Sprintf("wss://%s/ws", RelayDomain) /* // a little misdirection there @@ -181,7 +182,7 @@ func routeAll() chi.Router { claims.Subject, claims.Audience, claims.Slug, - primaryDomain, + DeviceDomain, ))) }) diff --git a/cmd/telebit/admin.go b/internal/telebit/admin.go similarity index 97% rename from cmd/telebit/admin.go rename to internal/telebit/admin.go index d21f80a..c7615b0 100644 --- a/cmd/telebit/admin.go +++ b/internal/telebit/admin.go @@ -1,4 +1,4 @@ -package main +package telebit import ( "context" @@ -13,7 +13,7 @@ import ( "sync" "time" - telebit "git.rootprojects.org/root/telebit" + "git.rootprojects.org/root/telebit" "git.rootprojects.org/root/telebit/admin" "git.rootprojects.org/root/telebit/dbg" "git.rootprojects.org/root/telebit/table" @@ -23,10 +23,11 @@ import ( "github.com/gorilla/websocket" ) -var httpsrv *http.Server +var authorizer telebit.Authorizer -func InitAdmin(authURL string) { - r := chi.NewRouter() +// RouteAdmin sets up the API, including the Mgmt proxy and ACME relay +func RouteAdmin(authURL string, r chi.Router) { + authorizer = NewAuthorizer(authURL) r.Use(middleware.Logger) //r.Use(middleware.Timeout(120 * time.Second)) @@ -113,10 +114,6 @@ func InitAdmin(authURL string) { fmt.Println("Request Path:", r.URL.Path) adminUI.ServeHTTP(w, r) }) - - httpsrv = &http.Server{ - Handler: r, - } } var apiPingContent = []byte("{ \"success\": true, \"error\": \"\" }\n") diff --git a/cmd/telebit/authorizer.go b/internal/telebit/authorizer.go similarity index 95% rename from cmd/telebit/authorizer.go rename to internal/telebit/authorizer.go index 943e534..66e6207 100644 --- a/cmd/telebit/authorizer.go +++ b/internal/telebit/authorizer.go @@ -1,11 +1,11 @@ -package main +package telebit import ( "fmt" "net/http" "strings" - telebit "git.rootprojects.org/root/telebit" + "git.rootprojects.org/root/telebit" ) func NewAuthorizer(authURL string) telebit.Authorizer {