refactor cmds for goreleaser

This commit is contained in:
AJ ONeal 2020-11-13 02:43:17 -07:00
parent 750f339f58
commit ade7f0ba80
23 changed files with 474 additions and 312 deletions

48
.goreleaser.yml Normal file
View File

@ -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:'

352
README.md
View File

@ -1,9 +1,45 @@
# Telebit # Telebit
| **Telebit Client** | [Telebit Relay](/tree/master/cmd/telebit) | [Telebit Mgmt](/tree/master/cmd/mgmt) |
A secure, end-to-end Encrypted tunnel. A secure, end-to-end Encrypted tunnel.
Because friends don't let friends localhost. 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 ## Install Go
Installs Go to `~/.local/opt/go` for MacOS and Linux: 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. You can also change the `serviceName` and `serviceDescription` at build time.
See `examples/run-as-client.sh`. 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 ## Local Web Application
Currently only raw TCP is tunneled. Currently only raw TCP is tunneled.
@ -288,20 +91,75 @@ EOF
python3 -m http.server 3000 python3 -m http.server 3000
``` ```
## Glossary ## Help
``` ```
--vendor-id $VENDOR_ID an arbitrary id used as part of authentication Usage of telebit:
--secret $SECRET the secret for creating JWTs ACME_AGREE
--tunnel-relay-url $TUNNEL_RELAY_URL the url of the tunnel server --acme-agree
--auth-url $AUTH_URL use to override the server-provided auth url agree to the terms of the ACME service provider (required)
--acme-relay-url $ACME_RELAY_URL use to override the server-provided acme dns 01 proxy --acme-directory string
--locals $LOCALS a list of `scheme:domainname:port` ACME Directory URL
for forwarding incoming `domainname` to local `port` ACME_EMAIL
--port-forwards $PORT_FORWARDS a list of `remote:local` tcp port-forwarding --acme-email string
--verbose $VERBOSE logs everything, including abbreviated data (as hex) email to use for Let's Encrypt / ACME registration
$VERBOSE_BYTES logs full data (as hex) --acme-http-01
$VERBOSE_RAW logs full data (as string) enable HTTP-01 ACME challenges
--acme-agree $ACME_AGREE agree to the ACME service agreement ACME_HTTP_01_RELAY_URL
--acme-email $ACME_EMAIL the webmaster email for ACME notices --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 <from-domain>:<to-port>
PORT_FORWARD
--port-forward string
a list of <from-port>:<to-port> 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
``` ```

View File

@ -1,4 +1,4 @@
# MGMT Server # Telebit Mgmt
# Config # Config
@ -18,6 +18,17 @@ NAMECOM_USERNAME=johndoe
NAMECOM_API_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 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 # Build
```bash ```bash
@ -27,3 +38,95 @@ pushd cmd/mgmt
go build -mod vendor -o telebit-mgmt go build -mod vendor -o telebit-mgmt
popd 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}"
```

View File

@ -10,12 +10,14 @@ import (
"os" "os"
"strings" "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/challenge"
"github.com/go-acme/lego/v3/providers/dns/duckdns" "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/godaddy"
"github.com/go-acme/lego/v3/providers/dns/namedotcom" "github.com/go-acme/lego/v3/providers/dns/namedotcom"
_ "github.com/joho/godotenv/autoload" _ "github.com/joho/godotenv/autoload"
) )
@ -28,14 +30,8 @@ var (
GitTimestamp = "0000-00-00T00:00:00+0000" GitTimestamp = "0000-00-00T00:00:00+0000"
) )
// MWKey is a type guard
type MWKey string
var store authstore.Store var store authstore.Store
var provider challenge.Provider = nil // TODO is this concurrency-safe?
var secret string var secret string
var primaryDomain string
var relayDomain string
func help() { func help() {
fmt.Fprintf(os.Stderr, "Usage: mgmt --domain <devices.example.com> --secret <128-bit secret>\n") fmt.Fprintf(os.Stderr, "Usage: mgmt --domain <devices.example.com> --secret <128-bit secret>\n")
@ -59,22 +55,22 @@ func main() {
"database (postgres) connection url") "database (postgres) connection url")
flag.StringVar(&secret, "secret", "", flag.StringVar(&secret, "secret", "",
"a >= 16-character random string for JWT key signing") "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") "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") "the domain name of the tunnel relay service, if different from base domain")
flag.Parse() flag.Parse()
if 0 == len(primaryDomain) { if 0 == len(mgmt.DeviceDomain) {
primaryDomain = os.Getenv("DOMAIN") mgmt.DeviceDomain = os.Getenv("DOMAIN")
} }
if 0 == len(relayDomain) { if 0 == len(mgmt.RelayDomain) {
relayDomain = os.Getenv("TUNNEL_DOMAIN") mgmt.RelayDomain = os.Getenv("TUNNEL_DOMAIN")
} }
if 0 == len(relayDomain) { if 0 == len(mgmt.RelayDomain) {
relayDomain = primaryDomain mgmt.RelayDomain = mgmt.DeviceDomain
} }
if 0 == len(dbURL) { if 0 == len(dbURL) {
@ -99,6 +95,8 @@ func main() {
lnAddr = "localhost:" + port lnAddr = "localhost:" + port
} }
// TODO are these concurrency-safe?
var provider challenge.Provider = nil
if len(os.Getenv("GODADDY_API_KEY")) > 0 { if len(os.Getenv("GODADDY_API_KEY")) > 0 {
id := os.Getenv("GODADDY_API_KEY") id := os.Getenv("GODADDY_API_KEY")
apiSecret := os.Getenv("GODADDY_API_SECRET") apiSecret := os.Getenv("GODADDY_API_SECRET")
@ -120,7 +118,7 @@ func main() {
fmt.Println("DNS-01 relay disabled") 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() help()
os.Exit(1) os.Exit(1)
return return
@ -134,7 +132,7 @@ func main() {
connStr += "?sslmode=required" connStr += "?sslmode=required"
} }
store, err = authstore.NewStore(connStr, initSQL) store, err = authstore.NewStore(connStr, mgmt.InitSQL)
if nil != err { if nil != err {
log.Fatal("connection error", err) log.Fatal("connection error", err)
return return
@ -142,16 +140,18 @@ func main() {
_ = store.SetMaster(secret) _ = store.SetMaster(secret)
defer store.Close() defer store.Close()
mgmt.Init(store, provider)
go func() { go func() {
fmt.Println("Listening for ACME challenges on :" + challengesPort) 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) log.Fatal(err)
os.Exit(1) os.Exit(1)
} }
}() }()
fmt.Println("Listening on", lnAddr) 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 // newNameDotComDNSProvider is for the sake of demoing the tunnel

View File

@ -1,3 +0,0 @@
package main
var initSQL = "./postgres.init.sql"

View File

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

View File

@ -11,6 +11,7 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"net/http"
"net/url" "net/url"
"os" "os"
"regexp" "regexp"
@ -22,21 +23,23 @@ import (
"git.rootprojects.org/root/telebit/dbg" "git.rootprojects.org/root/telebit/dbg"
"git.rootprojects.org/root/telebit/internal/dns01" "git.rootprojects.org/root/telebit/internal/dns01"
"git.rootprojects.org/root/telebit/internal/http01" "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" "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/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/table"
"git.rootprojects.org/root/telebit/tunnel" "git.rootprojects.org/root/telebit/tunnel"
legoDNS01 "github.com/go-acme/lego/v3/challenge/dns01"
"github.com/coolaj86/certmagic" "github.com/coolaj86/certmagic"
"github.com/denisbrodbeck/machineid" "github.com/denisbrodbeck/machineid"
jwt "github.com/dgrijalva/jwt-go" jwt "github.com/dgrijalva/jwt-go"
"github.com/go-acme/lego/v3/challenge" "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/duckdns"
"github.com/go-acme/lego/v3/providers/dns/godaddy" "github.com/go-acme/lego/v3/providers/dns/godaddy"
"github.com/go-acme/lego/v3/providers/dns/namedotcom" "github.com/go-acme/lego/v3/providers/dns/namedotcom"
"github.com/go-chi/chi"
"github.com/joho/godotenv" "github.com/joho/godotenv"
_ "github.com/joho/godotenv/autoload" _ "github.com/joho/godotenv/autoload"
) )
@ -80,8 +83,6 @@ type Forward struct {
localTLS bool localTLS bool
} }
var authorizer telebit.Authorizer
var isHostname = regexp.MustCompile(`^[A-Za-z0-9_\.\-]+$`).MatchString var isHostname = regexp.MustCompile(`^[A-Za-z0-9_\.\-]+$`).MatchString
// VendorID may be baked in, or supplied via ENVs or --args // VendorID may be baked in, or supplied via ENVs or --args
@ -383,7 +384,6 @@ func main() {
return return
} }
fmt.Println("Auth URL", *authURL) fmt.Println("Auth URL", *authURL)
authorizer = NewAuthorizer(*authURL)
dns01Base := directory.DNS01Proxy.URL dns01Base := directory.DNS01Proxy.URL
if 0 == len(*acmeRelay) { if 0 == len(*acmeRelay) {
@ -428,7 +428,6 @@ func main() {
fmt.Printf("[Grants]\n\t%#v\n", grants) fmt.Printf("[Grants]\n\t%#v\n", grants)
*relay = grants.Audience *relay = grants.Audience
} }
authorizer = NewAuthorizer(*authURL)
fmt.Printf("Email: %q\n", *email) fmt.Printf("Email: %q\n", *email)
@ -590,9 +589,11 @@ func muxAll(
} }
if "" != *apiHostname { if "" != *apiHostname {
// this is a generic net listener // this is a generic net listener
InitAdmin(*authURL) r := chi.NewRouter()
telebitX.RouteAdmin(*authURL, r)
apiListener := tunnel.NewListener() apiListener := tunnel.NewListener()
go func() { go func() {
httpsrv := &http.Server{Handler: r}
httpsrv.Serve(apiListener) httpsrv.Serve(apiListener)
}() }()
fmt.Printf("Will respond to Websocket and API requests to %q\n", *apiHostname) fmt.Printf("Will respond to Websocket and API requests to %q\n", *apiHostname)

View File

@ -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 # TUNNEL_RELAY_URL
# The URL of the Telebit Relay, of course. # The URL of the Telebit Relay, of course.
# Note that many client configuration details can be preassigned at # 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. # It's just as well to generate a random ID for your organization.
VENDOR_ID= VENDOR_ID=
# Used for Let's Encrypt registration # SECRET
# ACME_AGREE # This is the shared secret between the client device
ACME_AGREE=true # and the device management server.
# ACME_EMAIL SECRET=shared-secret
ACME_EMAIL=johndoe@example.com
# CLIENT_SUBJECT (optional) # CLIENT_SUBJECT (optional)
# NOT used by Telebit. # 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_SUBJECT=newbie
CLIENT_SECRET=xxxxxxxxxxxxxxxx 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 # TLS_LOCALS
#PORT_FORWARDS=3443:3001,8443:3002 # ReverseProxy any matching requests to the given local port.
#DUCKDNS_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx # 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 # AUTH_URL
# The client may receive this as `.authn.url` # The client may receive this as `.authn.url`
# through `https://$API_DOMAIN/.well-known/telebit.app/index.json` # through `https://$API_DOMAIN/.well-known/telebit.app/index.json`
# Setting the ENV AUTH_URL or the flag --auth-url overrides the discovery endpoint # 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=

View File

@ -11,8 +11,9 @@ CLIENT_SECRET="${CLIENT_SECRET:-}"
#go build -mod=vendor -o ./telebit \ #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'" \ # -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 # cmd/telebit/*.go
go build -mod=vendor -o telebit \ pushd cmd/telebit
cmd/telebit/*.go go build -mod=vendor -o telebit .
popd
# For Device Authorization across services # For Device Authorization across services
#AUTH_URL=${AUTH_URL:-"https://devices.examples.com/api"} #AUTH_URL=${AUTH_URL:-"https://devices.examples.com/api"}

View File

@ -1,4 +1,4 @@
package main package mgmt
import ( import (
"encoding/json" "encoding/json"

View File

@ -9,7 +9,7 @@ import (
telebit "git.rootprojects.org/root/telebit" telebit "git.rootprojects.org/root/telebit"
"git.rootprojects.org/root/telebit/dbg" "git.rootprojects.org/root/telebit/dbg"
"git.rootprojects.org/root/telebit/mgmt/authstore" "git.rootprojects.org/root/telebit/internal/mgmt/authstore"
) )
type SuccessResponse struct { type SuccessResponse struct {

View File

@ -1,4 +1,4 @@
package main package mgmt
import ( import (
"crypto/rand" "crypto/rand"
@ -10,7 +10,8 @@ import (
"strings" "strings"
"time" "time"
"git.rootprojects.org/root/telebit/mgmt/authstore" "git.rootprojects.org/root/telebit/internal/mgmt/authstore"
"github.com/go-chi/chi" "github.com/go-chi/chi"
) )

28
internal/mgmt/mgmt.go Normal file
View File

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

View File

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

View File

@ -1,4 +1,4 @@
package main package mgmt
import ( import (
"context" "context"
@ -12,7 +12,8 @@ import (
"time" "time"
"git.rootprojects.org/root/telebit/dbg" "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/dgrijalva/jwt-go"
"github.com/go-chi/chi" "github.com/go-chi/chi"
"github.com/go-chi/chi/middleware" "github.com/go-chi/chi/middleware"
@ -27,7 +28,7 @@ type MgmtClaims struct {
var presenters = make(chan *Challenge) var presenters = make(chan *Challenge)
var cleanups = make(chan *Challenge) var cleanups = make(chan *Challenge)
func routeStatic() chi.Router { func RouteStatic() chi.Router {
r := chi.NewRouter() r := chi.NewRouter()
r.Use(middleware.Logger) r.Use(middleware.Logger)
@ -49,7 +50,7 @@ func routeStatic() chi.Router {
return r return r
} }
func routeAll() chi.Router { func RouteAll() chi.Router {
go func() { go func() {
for { for {
@ -125,8 +126,8 @@ func routeAll() chi.Router {
return nil, fmt.Errorf("invalid jwt payload 'sub' (mismatch)") return nil, fmt.Errorf("invalid jwt payload 'sub' (mismatch)")
} }
claims.Subject = claims.Slug claims.Subject = claims.Slug
claims.Issuer = primaryDomain claims.Issuer = DeviceDomain
claims.Audience = fmt.Sprintf("wss://%s/ws", relayDomain) claims.Audience = fmt.Sprintf("wss://%s/ws", RelayDomain)
/* /*
// a little misdirection there // a little misdirection there
@ -181,7 +182,7 @@ func routeAll() chi.Router {
claims.Subject, claims.Subject,
claims.Audience, claims.Audience,
claims.Slug, claims.Slug,
primaryDomain, DeviceDomain,
))) )))
}) })

View File

@ -1,4 +1,4 @@
package main package telebit
import ( import (
"context" "context"
@ -13,7 +13,7 @@ import (
"sync" "sync"
"time" "time"
telebit "git.rootprojects.org/root/telebit" "git.rootprojects.org/root/telebit"
"git.rootprojects.org/root/telebit/admin" "git.rootprojects.org/root/telebit/admin"
"git.rootprojects.org/root/telebit/dbg" "git.rootprojects.org/root/telebit/dbg"
"git.rootprojects.org/root/telebit/table" "git.rootprojects.org/root/telebit/table"
@ -23,10 +23,11 @@ import (
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
var httpsrv *http.Server var authorizer telebit.Authorizer
func InitAdmin(authURL string) { // RouteAdmin sets up the API, including the Mgmt proxy and ACME relay
r := chi.NewRouter() func RouteAdmin(authURL string, r chi.Router) {
authorizer = NewAuthorizer(authURL)
r.Use(middleware.Logger) r.Use(middleware.Logger)
//r.Use(middleware.Timeout(120 * time.Second)) //r.Use(middleware.Timeout(120 * time.Second))
@ -113,10 +114,6 @@ func InitAdmin(authURL string) {
fmt.Println("Request Path:", r.URL.Path) fmt.Println("Request Path:", r.URL.Path)
adminUI.ServeHTTP(w, r) adminUI.ServeHTTP(w, r)
}) })
httpsrv = &http.Server{
Handler: r,
}
} }
var apiPingContent = []byte("{ \"success\": true, \"error\": \"\" }\n") var apiPingContent = []byte("{ \"success\": true, \"error\": \"\" }\n")

View File

@ -1,11 +1,11 @@
package main package telebit
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"strings" "strings"
telebit "git.rootprojects.org/root/telebit" "git.rootprojects.org/root/telebit"
) )
func NewAuthorizer(authURL string) telebit.Authorizer { func NewAuthorizer(authURL string) telebit.Authorizer {