update config, demo letsencrypt

This commit is contained in:
AJ ONeal 2020-05-04 22:12:11 -06:00
vanhempi 3f9e05e998
commit 94c3680333
9 muutettua tiedostoa jossa 155 lisäystä ja 71 poistoa

3
.gitignore vendored
Näytä tiedosto

@ -7,3 +7,6 @@ certs
/telebit
/cmd/telebit/telebit
/debug
log.txt
*.log

Näytä tiedosto

@ -1,4 +1,4 @@
# RVPN Server
# Telebit
## branch: load-balancing

Näytä tiedosto

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

5
examples/example.env Normal file
Näytä tiedosto

@ -0,0 +1,5 @@
NICKNAME=server-1
ADMIN_HOSTNAME=example.duckdns.org
WSS_HOSTNAME=
PORT=443
DUCKDNS_TOKEN=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX

Näytä tiedosto

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

Näytä tiedosto

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

Näytä tiedosto

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

Näytä tiedosto

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

Näytä tiedosto

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