2020-11-13 11:53:01 +00:00
|
|
|
//go:generate go run -mod=vendor git.rootprojects.org/root/go-gitver/v2
|
2020-05-26 07:47:22 +00:00
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
2020-05-30 23:14:40 +00:00
|
|
|
"log"
|
2020-05-26 07:47:22 +00:00
|
|
|
"net/http"
|
|
|
|
"os"
|
2020-05-26 09:05:39 +00:00
|
|
|
"strings"
|
2020-05-26 07:47:22 +00:00
|
|
|
|
2020-11-13 09:43:17 +00:00
|
|
|
"git.rootprojects.org/root/telebit/internal/mgmt"
|
|
|
|
"git.rootprojects.org/root/telebit/internal/mgmt/authstore"
|
2020-05-30 23:14:40 +00:00
|
|
|
|
2020-05-26 07:47:22 +00:00
|
|
|
"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"
|
2020-11-12 13:30:52 +00:00
|
|
|
"github.com/go-acme/lego/v3/providers/dns/namedotcom"
|
2020-11-17 19:26:48 +00:00
|
|
|
"github.com/go-chi/chi"
|
2020-11-13 09:43:17 +00:00
|
|
|
|
2020-05-26 07:47:22 +00:00
|
|
|
_ "github.com/joho/godotenv/autoload"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2020-11-13 11:53:01 +00:00
|
|
|
// commit refers to the abbreviated commit hash
|
|
|
|
commit = "0000000"
|
|
|
|
// version refers to the most recent tag, plus any commits made since then
|
|
|
|
version = "v0.0.0-pre0+0000000"
|
2020-05-26 07:47:22 +00:00
|
|
|
// GitTimestamp refers to the timestamp of the most recent commit
|
2020-11-13 11:53:01 +00:00
|
|
|
date = "0000-00-00T00:00:00+0000"
|
2020-11-17 19:26:48 +00:00
|
|
|
|
|
|
|
// serviceName is the service name
|
|
|
|
serviceName = "telebit-mgmt"
|
|
|
|
|
|
|
|
// serviceDesc
|
|
|
|
serviceDesc = "Telebit Device Management"
|
2020-05-26 07:47:22 +00:00
|
|
|
)
|
|
|
|
|
2020-11-17 19:26:48 +00:00
|
|
|
func ver() string {
|
|
|
|
return fmt.Sprintf("%s v%s (%s) %s", serviceName, version, commit[:7], date)
|
|
|
|
}
|
|
|
|
|
2020-05-30 23:14:40 +00:00
|
|
|
var store authstore.Store
|
2020-07-22 03:56:46 +00:00
|
|
|
var secret string
|
2020-05-31 13:02:46 +00:00
|
|
|
|
|
|
|
func help() {
|
2020-07-22 05:47:47 +00:00
|
|
|
fmt.Fprintf(os.Stderr, "Usage: mgmt --domain <devices.example.com> --secret <128-bit secret>\n")
|
2020-05-31 13:02:46 +00:00
|
|
|
}
|
2020-05-30 23:14:40 +00:00
|
|
|
|
2020-05-26 07:47:22 +00:00
|
|
|
func main() {
|
2020-11-13 11:53:01 +00:00
|
|
|
if len(os.Args) >= 2 {
|
|
|
|
if "version" == strings.TrimLeft(os.Args[1], "-") {
|
|
|
|
fmt.Printf("telebit %s (%s) %s\n", version, commit[:7], date)
|
|
|
|
os.Exit(0)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-26 07:47:22 +00:00
|
|
|
var err error
|
|
|
|
|
2020-11-12 13:30:52 +00:00
|
|
|
var port string
|
|
|
|
var lnAddr string
|
|
|
|
var dbURL string
|
|
|
|
var challengesPort string
|
|
|
|
|
|
|
|
flag.StringVar(&port, "port", "",
|
|
|
|
"port to listen to (default localhost 3000)")
|
|
|
|
flag.StringVar(&lnAddr, "listen", "",
|
|
|
|
"IPv4 or IPv6 bind address + port (instead of --port)")
|
|
|
|
flag.StringVar(&challengesPort, "challenges-port", "80",
|
|
|
|
"port to use to respond to .well-known/acme-challenge tokens")
|
|
|
|
flag.StringVar(&dbURL, "db-url", "postgres://postgres:postgres@localhost:5432/postgres",
|
|
|
|
"database (postgres) connection url")
|
|
|
|
flag.StringVar(&secret, "secret", "",
|
|
|
|
"a >= 16-character random string for JWT key signing")
|
2020-11-13 09:43:17 +00:00
|
|
|
flag.StringVar(&mgmt.DeviceDomain, "domain", "",
|
2020-11-12 13:30:52 +00:00
|
|
|
"the base domain to use for all clients")
|
2020-11-13 09:43:17 +00:00
|
|
|
flag.StringVar(&mgmt.RelayDomain, "tunnel-domain", "",
|
2020-11-12 13:30:52 +00:00
|
|
|
"the domain name of the tunnel relay service, if different from base domain")
|
|
|
|
|
2020-05-26 07:47:22 +00:00
|
|
|
flag.Parse()
|
|
|
|
|
2020-11-13 09:43:17 +00:00
|
|
|
if 0 == len(mgmt.DeviceDomain) {
|
|
|
|
mgmt.DeviceDomain = os.Getenv("DOMAIN")
|
2020-05-31 13:02:46 +00:00
|
|
|
}
|
2020-11-12 13:30:52 +00:00
|
|
|
|
2020-11-13 09:43:17 +00:00
|
|
|
if 0 == len(mgmt.RelayDomain) {
|
|
|
|
mgmt.RelayDomain = os.Getenv("TUNNEL_DOMAIN")
|
2020-11-12 13:30:52 +00:00
|
|
|
}
|
2020-11-13 09:43:17 +00:00
|
|
|
if 0 == len(mgmt.RelayDomain) {
|
|
|
|
mgmt.RelayDomain = mgmt.DeviceDomain
|
2020-07-22 05:47:47 +00:00
|
|
|
}
|
2020-05-31 13:02:46 +00:00
|
|
|
|
2020-11-12 13:30:52 +00:00
|
|
|
if 0 == len(dbURL) {
|
|
|
|
dbURL = os.Getenv("DB_URL")
|
|
|
|
}
|
|
|
|
|
|
|
|
if 0 == len(secret) {
|
|
|
|
secret = os.Getenv("SECRET")
|
|
|
|
}
|
|
|
|
|
|
|
|
// prefer --listen (with address) over --port (localhost only)
|
|
|
|
if 0 == len(lnAddr) {
|
|
|
|
lnAddr = os.Getenv("LISTEN")
|
|
|
|
}
|
|
|
|
if 0 == len(lnAddr) {
|
|
|
|
if 0 == len(port) {
|
|
|
|
port = os.Getenv("PORT")
|
|
|
|
}
|
|
|
|
if 0 == len(port) {
|
|
|
|
port = "3000"
|
|
|
|
}
|
|
|
|
lnAddr = "localhost:" + port
|
|
|
|
}
|
|
|
|
|
2020-11-13 09:43:17 +00:00
|
|
|
// TODO are these concurrency-safe?
|
|
|
|
var provider challenge.Provider = nil
|
2020-11-12 13:30:52 +00:00
|
|
|
if len(os.Getenv("GODADDY_API_KEY")) > 0 {
|
2020-05-26 07:47:22 +00:00
|
|
|
id := os.Getenv("GODADDY_API_KEY")
|
2020-05-26 09:05:39 +00:00
|
|
|
apiSecret := os.Getenv("GODADDY_API_SECRET")
|
|
|
|
if provider, err = newGoDaddyDNSProvider(id, apiSecret); nil != err {
|
2020-05-26 07:47:22 +00:00
|
|
|
panic(err)
|
|
|
|
}
|
2020-11-12 13:30:52 +00:00
|
|
|
} else if len(os.Getenv("DUCKDNS_TOKEN")) > 0 {
|
2020-05-26 07:47:22 +00:00
|
|
|
if provider, err = newDuckDNSProvider(os.Getenv("DUCKDNS_TOKEN")); nil != err {
|
|
|
|
panic(err)
|
|
|
|
}
|
2020-11-12 13:30:52 +00:00
|
|
|
} else if len(os.Getenv("NAMECOM_API_TOKEN")) > 0 {
|
|
|
|
if provider, err = newNameDotComDNSProvider(
|
|
|
|
os.Getenv("NAMECOM_USERNAME"),
|
|
|
|
os.Getenv("NAMECOM_API_TOKEN"),
|
|
|
|
); nil != err {
|
|
|
|
panic(err)
|
|
|
|
}
|
2020-05-26 07:47:22 +00:00
|
|
|
} else {
|
2020-11-12 13:30:52 +00:00
|
|
|
fmt.Println("DNS-01 relay disabled")
|
2020-05-26 07:47:22 +00:00
|
|
|
}
|
|
|
|
|
2020-11-13 09:43:17 +00:00
|
|
|
if 0 == len(mgmt.DeviceDomain) || 0 == len(secret) || 0 == len(dbURL) {
|
2020-05-31 13:02:46 +00:00
|
|
|
help()
|
2020-05-26 09:05:39 +00:00
|
|
|
os.Exit(1)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-11-12 13:30:52 +00:00
|
|
|
connStr := dbURL
|
2020-05-30 23:14:40 +00:00
|
|
|
// TODO url.Parse
|
2020-06-03 06:17:30 +00:00
|
|
|
if strings.Contains(connStr, "@localhost/") || strings.Contains(connStr, "@localhost:") {
|
2020-05-30 23:14:40 +00:00
|
|
|
connStr += "?sslmode=disable"
|
|
|
|
} else {
|
|
|
|
connStr += "?sslmode=required"
|
|
|
|
}
|
|
|
|
|
2020-11-13 09:43:17 +00:00
|
|
|
store, err = authstore.NewStore(connStr, mgmt.InitSQL)
|
2020-05-30 23:14:40 +00:00
|
|
|
if nil != err {
|
|
|
|
log.Fatal("connection error", err)
|
|
|
|
return
|
|
|
|
}
|
2020-07-22 03:56:46 +00:00
|
|
|
_ = store.SetMaster(secret)
|
2020-05-30 23:14:40 +00:00
|
|
|
defer store.Close()
|
2020-05-26 07:47:22 +00:00
|
|
|
|
2020-11-13 09:43:17 +00:00
|
|
|
mgmt.Init(store, provider)
|
|
|
|
|
2020-11-05 09:11:17 +00:00
|
|
|
go func() {
|
2020-11-12 13:30:52 +00:00
|
|
|
fmt.Println("Listening for ACME challenges on :" + challengesPort)
|
2020-11-17 19:26:48 +00:00
|
|
|
r := chi.NewRouter()
|
|
|
|
r.Get("/version", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
w.Write([]byte(ver() + "\n"))
|
|
|
|
})
|
|
|
|
r.Get("/api/version", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
w.Write([]byte("TODO (json): " + ver() + "\n"))
|
|
|
|
})
|
|
|
|
if err := http.ListenAndServe(":"+challengesPort, mgmt.RouteStatic(r)); nil != err {
|
2020-11-05 09:19:27 +00:00
|
|
|
log.Fatal(err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2020-11-05 09:11:17 +00:00
|
|
|
}()
|
|
|
|
|
2020-11-12 13:30:52 +00:00
|
|
|
fmt.Println("Listening on", lnAddr)
|
2020-11-13 09:43:17 +00:00
|
|
|
fmt.Fprintf(os.Stderr, "failed: %s", http.ListenAndServe(lnAddr, mgmt.RouteAll()))
|
2020-11-12 13:30:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// newNameDotComDNSProvider is for the sake of demoing the tunnel
|
|
|
|
func newNameDotComDNSProvider(username, apitoken string) (*namedotcom.DNSProvider, error) {
|
|
|
|
config := namedotcom.NewDefaultConfig()
|
|
|
|
config.Username = username
|
|
|
|
config.APIToken = apitoken
|
|
|
|
return namedotcom.NewDNSProviderConfig(config)
|
2020-05-26 07:47:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// newDuckDNSProvider is for the sake of demoing the tunnel
|
|
|
|
func newDuckDNSProvider(token string) (*duckdns.DNSProvider, error) {
|
|
|
|
config := duckdns.NewDefaultConfig()
|
|
|
|
config.Token = token
|
|
|
|
return duckdns.NewDNSProviderConfig(config)
|
|
|
|
}
|
|
|
|
|
|
|
|
// newGoDaddyDNSProvider is for the sake of demoing the tunnel
|
|
|
|
func newGoDaddyDNSProvider(id, secret string) (*godaddy.DNSProvider, error) {
|
|
|
|
config := godaddy.NewDefaultConfig()
|
|
|
|
config.APIKey = id
|
|
|
|
config.APISecret = secret
|
|
|
|
return godaddy.NewDNSProviderConfig(config)
|
|
|
|
}
|