update config, demo letsencrypt
This commit is contained in:
parent
3f9e05e998
commit
94c3680333
|
@ -7,3 +7,6 @@ certs
|
|||
/telebit
|
||||
/cmd/telebit/telebit
|
||||
/debug
|
||||
|
||||
log.txt
|
||||
*.log
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
golog "log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.coolaj86.com/coolaj86/go-telebitd/log"
|
||||
|
@ -17,8 +18,9 @@ import (
|
|||
"git.coolaj86.com/coolaj86/go-telebitd/relay/api"
|
||||
"git.coolaj86.com/coolaj86/go-telebitd/relay/mplexy"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/go-acme/lego/v3/providers/dns/duckdns"
|
||||
lumberjack "gopkg.in/natefinch/lumberjack.v2"
|
||||
|
||||
_ "github.com/joho/godotenv/autoload"
|
||||
|
@ -49,22 +51,62 @@ var (
|
|||
idle int
|
||||
dwell int
|
||||
cancelcheck int
|
||||
lbDefaultMethod string
|
||||
lbDefaultMethod api.LoadBalanceStrategy
|
||||
nickname string
|
||||
acmeEmail string
|
||||
acmeStorage string
|
||||
acmeAgree bool
|
||||
acmeStaging bool
|
||||
allclients string
|
||||
adminDomain string
|
||||
wssDomain string
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&logfile, "log", logfile, "Log file (or stdout/stderr; empty for none)")
|
||||
flag.StringVar(&allclients, "clients", "", "list of client:secret pairings such as example.com:secret123,foo.com:secret321")
|
||||
flag.StringVar(&acmeEmail, "acme-email", "", "email to use for Let's Encrypt / ACME registration")
|
||||
flag.StringVar(&acmeStorage, "acme-storage", "./acme.d/", "path to ACME storage directory")
|
||||
flag.StringVar(&acmeStorage, "acme-storage", "./acme.d/", "path to ACME storage directory")
|
||||
flag.BoolVar(&acmeAgree, "acme-agree", false, "agree to the terms of the ACME service provider (required)")
|
||||
flag.BoolVar(&acmeStaging, "staging", false, "get fake certificates for testing")
|
||||
flag.StringVar(&adminDomain, "admin-domain", "", "the management domain")
|
||||
flag.StringVar(&wssDomain, "wss-domain", "", "the wss domain for connecting devices, if different from admin")
|
||||
flag.StringVar(&configPath, "config-path", configPath, "Configuration File Path")
|
||||
flag.StringVar(&secretKey, "secret", "", "a >= 16-character random string for JWT key signing")
|
||||
flag.StringVar(&secretKey, "secret", "", "a >= 16-character random string for JWT key signing") // SECRET
|
||||
flag.StringVar(&logfile, "log", logfile, "Log file (or stdout/stderr; empty for none)")
|
||||
flag.IntVar(&tcpPort, "port", 0, "tcp port on which to listen") // PORT
|
||||
flag.StringVar(&nickname, "nickname", "", "a nickname for this server, as an identifier") // NICKNAME
|
||||
}
|
||||
|
||||
var logoutput io.Writer
|
||||
|
||||
// Client is a domain and secret pair
|
||||
type Client struct {
|
||||
domain string
|
||||
secret string
|
||||
}
|
||||
|
||||
//Main -- main entry point
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
if !acmeAgree {
|
||||
fmt.Fprintf(os.Stderr, "set --acme-agree=true to accept the terms of the ACME service provider.\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
clients := []Client{}
|
||||
for _, pair := range strings.Split(allclients, ", ") {
|
||||
if len(pair) > 0 {
|
||||
continue
|
||||
}
|
||||
keyval := strings.Split(pair, ":")
|
||||
clients = append(clients, Client{
|
||||
domain: keyval[0],
|
||||
secret: keyval[1],
|
||||
})
|
||||
}
|
||||
|
||||
if "" == secretKey {
|
||||
secretKey = os.Getenv("TELEBIT_SECRET")
|
||||
}
|
||||
|
@ -92,51 +134,73 @@ func main() {
|
|||
// send the output io.Writing to the other packages
|
||||
log.InitLogging(logoutput)
|
||||
|
||||
viper.SetConfigName(configFile)
|
||||
viper.AddConfigPath(configPath)
|
||||
viper.AddConfigPath("./")
|
||||
err := viper.ReadInConfig()
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("fatal error config file: %s", err))
|
||||
}
|
||||
|
||||
flag.IntVar(&argDeadTime, "dead-time-counter", 5, "deadtime counter in seconds")
|
||||
|
||||
wssHostName = viper.Get("rvpn.wssdomain").(string)
|
||||
adminHostName = viper.Get("rvpn.admindomain").(string)
|
||||
tcpPort = viper.GetInt("rvpn.port")
|
||||
deadtime := viper.Get("rvpn.deadtime").(map[string]interface{})
|
||||
idle = deadtime["idle"].(int)
|
||||
dwell = deadtime["dwell"].(int)
|
||||
cancelcheck = deadtime["cancelcheck"].(int)
|
||||
lbDefaultMethod = viper.Get("rvpn.loadbalancing.defaultmethod").(string)
|
||||
nickname = viper.Get("rvpn.serverName").(string)
|
||||
if 0 == tcpPort {
|
||||
tcpPort, err = strconv.Atoi(os.Getenv("PORT"))
|
||||
if nil != err {
|
||||
fmt.Fprintf(os.Stderr, "must specify --port or PORT\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
adminHostName = adminDomain
|
||||
if 0 == len(adminHostName) {
|
||||
adminHostName = os.Getenv("ADMIN_DOMAIN")
|
||||
}
|
||||
wssHostName = wssDomain
|
||||
if 0 == len(wssHostName) {
|
||||
wssHostName = os.Getenv("WSS_DOMAIN")
|
||||
}
|
||||
if 0 == len(wssHostName) {
|
||||
wssHostName = adminHostName
|
||||
}
|
||||
|
||||
// load balancer method
|
||||
lbDefaultMethod = api.RoundRobin
|
||||
if 0 == len(nickname) {
|
||||
nickname = os.Getenv("NICKNAME")
|
||||
}
|
||||
|
||||
// TODO what do these "deadtimes" do exactly?
|
||||
dwell := 120
|
||||
idle := 60
|
||||
cancelcheck := 10
|
||||
|
||||
Loginfo.Println("startup")
|
||||
|
||||
ctx, cancelContext := context.WithCancel(context.Background())
|
||||
defer cancelContext()
|
||||
|
||||
// CertMagic is Greenlock for Go
|
||||
directory := certmagic.LetsEncryptProductionCA
|
||||
if acmeStaging {
|
||||
directory = certmagic.LetsEncryptStagingCA
|
||||
}
|
||||
magic, err := newCertMagic(directory, acmeEmail, &certmagic.FileStorage{Path: acmeStorage})
|
||||
|
||||
serverStatus := api.NewStatus(ctx)
|
||||
serverStatus.AdminDomain = adminHostName
|
||||
serverStatus.WssDomain = wssHostName
|
||||
serverStatus.Name = nickname
|
||||
serverStatus.DeadTime = api.NewStatusDeadTime(dwell, idle, cancelcheck)
|
||||
serverStatus.LoadbalanceDefaultMethod = lbDefaultMethod
|
||||
serverStatus.LoadbalanceDefaultMethod = string(lbDefaultMethod)
|
||||
|
||||
connectionTable := api.NewTable(dwell, idle, lbDefaultMethod)
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
certbundle, err := magic.GetCertificate(hello)
|
||||
|
||||
// TODO
|
||||
// 1. call out to greenlock for validation
|
||||
// 2. push challenges through http channel
|
||||
// 3. receive certificates (or don't)
|
||||
certbundle, err := tls.LoadX509KeyPair("certs/fullchain.pem", "certs/privkey.pem")
|
||||
//certbundle, err := tls.LoadX509KeyPair("certs/fullchain.pem", "certs/privkey.pem")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &certbundle, nil
|
||||
return certbundle, nil
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -188,3 +252,46 @@ func main() {
|
|||
r := relay.New(ctx, tlsConfig, authorizer, serverStatus, connectionTable)
|
||||
r.ListenAndServe(tcpPort)
|
||||
}
|
||||
|
||||
func newCertMagic(directory string, email string, storage certmagic.Storage) (*certmagic.Config, error) {
|
||||
cache := certmagic.NewCache(certmagic.CacheOptions{
|
||||
GetConfigForCert: func(cert certmagic.Certificate) (*certmagic.Config, error) {
|
||||
// do whatever you need to do to get the right
|
||||
// configuration for this certificate; keep in
|
||||
// mind that this config value is used as a
|
||||
// template, and will be completed with any
|
||||
// defaults that are set in the Default config
|
||||
return &certmagic.Config{}, nil
|
||||
},
|
||||
})
|
||||
provider, err := newDuckDNSProvider(os.Getenv("DUCKDNS_TOKEN"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
magic := certmagic.New(cache, certmagic.Config{
|
||||
Storage: storage,
|
||||
OnDemand: &certmagic.OnDemandConfig{
|
||||
DecisionFunc: func(name string) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
})
|
||||
// Ummm... just a little confusing
|
||||
magic.Issuer = certmagic.NewACMEManager(magic, certmagic.ACMEManager{
|
||||
DNSProvider: provider,
|
||||
CA: directory,
|
||||
Email: email,
|
||||
Agreed: true,
|
||||
DisableHTTPChallenge: true,
|
||||
DisableTLSALPNChallenge: true,
|
||||
// plus any other customizations you need
|
||||
})
|
||||
return magic, nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
NICKNAME=server-1
|
||||
ADMIN_HOSTNAME=example.duckdns.org
|
||||
WSS_HOSTNAME=
|
||||
PORT=443
|
||||
DUCKDNS_TOKEN=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
|
|
@ -6,7 +6,6 @@ import (
|
|||
"net/http"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.coolaj86.com/coolaj86/go-telebitd/relay/api"
|
||||
"git.coolaj86.com/coolaj86/go-telebitd/relay/mplexy"
|
||||
|
@ -47,23 +46,9 @@ func ListenAndServe(mx *mplexy.MPlexy, adminListener net.Listener) error {
|
|||
|
||||
switch url := r.URL.Path; url {
|
||||
case "/":
|
||||
var hostname string
|
||||
host := strings.Split(r.Host, ":")
|
||||
if len(host) > 0 {
|
||||
hostname = host[0]
|
||||
}
|
||||
|
||||
// check to see if we are using the administrative Host
|
||||
if hostname == mplexy.InvalidAdminDomain {
|
||||
http.Redirect(w, r, "/admin", 301)
|
||||
serverStatus.AdminStats.IncResponses()
|
||||
return
|
||||
}
|
||||
if hostname == mx.AdminDomain() {
|
||||
http.Redirect(w, r, "/admin", 301)
|
||||
serverStatus.AdminStats.IncResponses()
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/admin", 301)
|
||||
serverStatus.AdminStats.IncResponses()
|
||||
return
|
||||
default:
|
||||
http.Error(w, "Not Found", 404)
|
||||
}
|
||||
|
|
|
@ -6,10 +6,12 @@ import (
|
|||
"sync"
|
||||
)
|
||||
|
||||
type LoadBalanceStrategy string
|
||||
|
||||
const (
|
||||
lbmUnSupported string = "unsuported"
|
||||
lbmRoundRobin string = "round-robin"
|
||||
lbmLeastConnections string = "least-connections"
|
||||
UnSupported LoadBalanceStrategy = "unsuported"
|
||||
RoundRobin LoadBalanceStrategy = "round-robin"
|
||||
LeastConnections LoadBalanceStrategy = "least-connections"
|
||||
)
|
||||
|
||||
//DomainLoadBalance -- Use as a structure for domain connections
|
||||
|
@ -19,7 +21,7 @@ type DomainLoadBalance struct {
|
|||
mutex sync.Mutex
|
||||
|
||||
//lb method, supported round robin.
|
||||
method string
|
||||
method LoadBalanceStrategy
|
||||
|
||||
//the last connection based on calculation
|
||||
lastmember int
|
||||
|
@ -35,7 +37,7 @@ type DomainLoadBalance struct {
|
|||
}
|
||||
|
||||
//NewDomainLoadBalance -- Constructor
|
||||
func NewDomainLoadBalance(defaultMethod string) (p *DomainLoadBalance) {
|
||||
func NewDomainLoadBalance(defaultMethod LoadBalanceStrategy) (p *DomainLoadBalance) {
|
||||
p = new(DomainLoadBalance)
|
||||
p.method = defaultMethod
|
||||
p.lastmember = 0
|
||||
|
@ -56,7 +58,7 @@ func (p *DomainLoadBalance) NextMember() (conn *Connection) {
|
|||
|
||||
//check for round robin, if not RR then drop out and call calculate
|
||||
log.Println("NextMember:", p)
|
||||
if p.method == lbmRoundRobin {
|
||||
if p.method == RoundRobin {
|
||||
p.lastmember++
|
||||
if p.lastmember >= p.count {
|
||||
p.lastmember = 0
|
||||
|
|
|
@ -25,7 +25,7 @@ func NewStatusAPI(c *Status) (s *StatusAPI) {
|
|||
s.Uptime = time.Since(c.StartTime).Seconds()
|
||||
s.WssDomain = c.WssDomain
|
||||
s.AdminDomain = c.AdminDomain
|
||||
s.LoadbalanceDefaultMethod = c.LoadbalanceDefaultMethod
|
||||
s.LoadbalanceDefaultMethod = string(c.LoadbalanceDefaultMethod)
|
||||
s.DeadTime = NewStatusDeadTimeAPI(c.DeadTime.dwell, c.DeadTime.idle, c.DeadTime.Cancelcheck)
|
||||
s.AdminStats = NewTrafficAPI(c.AdminStats.Requests, c.AdminStats.Responses, c.AdminStats.BytesIn, c.AdminStats.BytesOut)
|
||||
s.TrafficStats = NewTrafficAPI(c.TrafficStats.Requests, c.TrafficStats.Responses, c.TrafficStats.BytesIn, c.TrafficStats.BytesOut)
|
||||
|
|
|
@ -22,11 +22,11 @@ type Table struct {
|
|||
domainRevoke chan *DomainMapping
|
||||
dwell int
|
||||
idle int
|
||||
balanceMethod string
|
||||
balanceMethod LoadBalanceStrategy
|
||||
}
|
||||
|
||||
//NewTable -- consructor
|
||||
func NewTable(dwell, idle int, balanceMethod string) (p *Table) {
|
||||
func NewTable(dwell, idle int, balanceMethod LoadBalanceStrategy) (p *Table) {
|
||||
p = new(Table)
|
||||
p.connections = make(map[*Connection][]string)
|
||||
p.Domains = make(map[string]*DomainLoadBalance)
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
rvpn:
|
||||
serverName: rvpn1
|
||||
wssdomain: localhost.rootprojects.org
|
||||
admindomain: rvpn.rootprojects.invalid
|
||||
genericlistener: 8443
|
||||
deadtime:
|
||||
dwell: 120
|
||||
idle: 60
|
||||
cancelcheck: 10
|
||||
domains:
|
||||
test.rootprojects.org:
|
||||
secret: abc123
|
||||
test2.rootprojects.org:
|
||||
secret: abc123
|
||||
test3.rootprojects.org:
|
||||
secret: abc123
|
||||
loadbalancing:
|
||||
defaultmethod: round-robin
|
Loading…
Reference in New Issue