server: support new database, shim certificate management
This commit is contained in:
parent
e8acea0351
commit
30ebdbac7d
197
server/server.go
197
server/server.go
|
@ -1,27 +1,15 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/rpc"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.xeserv.us/xena/route/database"
|
"git.xeserv.us/xena/route/database"
|
||||||
"git.xeserv.us/xena/route/lib/elfs"
|
|
||||||
"git.xeserv.us/xena/route/lib/tun2"
|
"git.xeserv.us/xena/route/lib/tun2"
|
||||||
"git.xeserv.us/xena/route/routerpc"
|
|
||||||
"github.com/Xe/uuid"
|
|
||||||
"github.com/Yawning/bulb"
|
|
||||||
"github.com/brandur/simplebox"
|
|
||||||
"github.com/mtneug/pkg/ulid"
|
"github.com/mtneug/pkg/ulid"
|
||||||
"golang.org/x/crypto/acme/autocert"
|
"golang.org/x/crypto/acme/autocert"
|
||||||
)
|
)
|
||||||
|
@ -34,114 +22,81 @@ const (
|
||||||
// Server is the main server type
|
// Server is the main server type
|
||||||
type Server struct {
|
type Server struct {
|
||||||
cfg *Config
|
cfg *Config
|
||||||
|
db database.Storage
|
||||||
|
ts *tun2.Server
|
||||||
|
|
||||||
db *database.DB
|
autocert.Manager
|
||||||
|
|
||||||
rpcS *rpc.Server
|
|
||||||
rpcAddr string
|
|
||||||
|
|
||||||
ts *tun2.Server
|
|
||||||
|
|
||||||
CertCache *database.CertCache
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config configures Server
|
// Config configures Server
|
||||||
type Config struct {
|
type Config struct {
|
||||||
RethinkDBHost string
|
BoltDBPath string `env:"BOLTDB_PATH,required"`
|
||||||
RethinkDBDatabase string
|
|
||||||
|
|
||||||
TorDataDir string
|
WebAddr string `env:"WEB_ADDR,required"`
|
||||||
TorHashedPassword string
|
SSLAddr string `env:"SSL_ADDR,required"`
|
||||||
TorPassword string
|
BackendTCPAddr string `env:"BACKEND_TCP_ADDR,required"`
|
||||||
|
BackendKCPAddr string `env:"BACKEND_KCP_ADDR,required"`
|
||||||
|
GRPCAddr string `env:"GRPC_ADDR,required"`
|
||||||
|
|
||||||
WebPort string
|
DomainSuffix string `env:"DOMAIN_SUFFIX,required"`
|
||||||
SSLPort string
|
ACMEEmail string `env:"ACME_EMAIL,required"`
|
||||||
BackendPort string
|
|
||||||
KCPPort string
|
|
||||||
|
|
||||||
DomainSuffix string
|
|
||||||
ACMEEmail string
|
|
||||||
CertKey *[32]byte
|
CertKey *[32]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Server
|
// New creates a new Server
|
||||||
func New(cfg Config) (*Server, error) {
|
func New(cfg Config) (*Server, error) {
|
||||||
db, err := database.New(cfg.RethinkDBHost, cfg.RethinkDBDatabase)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rpcs := rpc.NewServer()
|
|
||||||
|
|
||||||
s := &Server{
|
|
||||||
cfg: &cfg,
|
|
||||||
|
|
||||||
db: db,
|
|
||||||
|
|
||||||
rpcS: rpcs,
|
|
||||||
rpcAddr: l.Addr().String(),
|
|
||||||
|
|
||||||
CertCache: &database.CertCache{
|
|
||||||
DB: db,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
m := autocert.Manager{
|
|
||||||
Prompt: autocert.AcceptTOS,
|
|
||||||
Cache: s.CertCache,
|
|
||||||
HostPolicy: nil,
|
|
||||||
Email: cfg.ACMEEmail,
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.CertKey == nil {
|
if cfg.CertKey == nil {
|
||||||
return nil, errors.New("no cert decryption key, can't do anything")
|
return nil, errors.New("no cert decryption key, can't do anything")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.CertCache.SimpleBox = simplebox.NewFromSecretKey(cfg.CertKey)
|
db, err := database.NewBoltStorage(cfg.BoltDBPath, cfg.CertKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m := autocert.Manager{
|
||||||
|
Prompt: autocert.AcceptTOS,
|
||||||
|
Cache: database.Cache(db),
|
||||||
|
HostPolicy: nil,
|
||||||
|
Email: cfg.ACMEEmail,
|
||||||
|
}
|
||||||
|
|
||||||
tcfg := &tun2.ServerConfig{
|
tcfg := &tun2.ServerConfig{
|
||||||
TCPAddr: cfg.BackendPort,
|
TCPAddr: cfg.BackendTCPAddr,
|
||||||
KCPAddr: cfg.KCPPort,
|
KCPAddr: cfg.BackendKCPAddr,
|
||||||
TLSConfig: &tls.Config{
|
TLSConfig: &tls.Config{
|
||||||
GetCertificate: m.GetCertificate,
|
GetCertificate: m.GetCertificate,
|
||||||
},
|
},
|
||||||
Storage: s.db,
|
Storage: &storageWrapper{
|
||||||
|
Storage: db,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
ts, err := tun2.NewServer(tcfg)
|
ts, err := tun2.NewServer(tcfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
s := &Server{
|
||||||
|
cfg: &cfg,
|
||||||
|
db: db,
|
||||||
|
ts: ts,
|
||||||
|
|
||||||
|
Manager: m,
|
||||||
|
}
|
||||||
|
|
||||||
s.ts = ts
|
s.ts = ts
|
||||||
go ts.ListenAndServe()
|
go ts.ListenAndServe()
|
||||||
|
|
||||||
rpcs.RegisterName("Urls", &RPCServer{Server: s})
|
|
||||||
go rpcs.Accept(l)
|
|
||||||
log.Println("rpc at tcp://" + l.Addr().String())
|
|
||||||
|
|
||||||
err = s.restore()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) onionPath(name string) string {
|
// Director removes headers that are typically stripped off at request ingress.
|
||||||
return filepath.Join(s.cfg.TorDataDir, name)
|
func (s *Server) Director(r *http.Request) {
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Director(r *http.Request) {}
|
|
||||||
|
|
||||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
r.Header.Del("X-Forwarded-For")
|
r.Header.Del("X-Forwarded-For")
|
||||||
r.Header.Del("X-Client-Ip")
|
r.Header.Del("X-Client-Ip")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Header.Get("X-Tor2web") != "" {
|
if r.Header.Get("X-Tor2web") != "" {
|
||||||
http.Error(w, "tor2web proxy use is not allowed", 400)
|
http.Error(w, "tor2web proxy use is not allowed", 400)
|
||||||
return
|
return
|
||||||
|
@ -162,15 +117,6 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
// http://www.gnuterrypratchett.com/
|
// http://www.gnuterrypratchett.com/
|
||||||
w.Header().Set("X-Clacks-Overhead", "GNU Ashlynn")
|
w.Header().Set("X-Clacks-Overhead", "GNU Ashlynn")
|
||||||
|
|
||||||
if strings.HasSuffix(r.Host, ".onion") {
|
|
||||||
r.Header.Add("DNT", "1")
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.RequestURI == rpc.DefaultRPCPath {
|
|
||||||
s.rpcS.ServeHTTP(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rp := &httputil.ReverseProxy{
|
rp := &httputil.ReverseProxy{
|
||||||
Director: s.Director,
|
Director: s.Director,
|
||||||
Transport: s.ts,
|
Transport: s.ts,
|
||||||
|
@ -179,68 +125,3 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
rp.ServeHTTP(w, r)
|
rp.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) restore() error {
|
|
||||||
rts, err := s.db.GetAllRoutes()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rt := range rts {
|
|
||||||
var block *pem.Block
|
|
||||||
|
|
||||||
block, _ = pem.Decode([]byte(rt.OnionKey))
|
|
||||||
_, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("added: %s (%s)", rt.Hostname, rt.OnionHostname)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func genPortSpec(incoming uint16, outgoing string) bulb.OnionPortSpec {
|
|
||||||
return bulb.OnionPortSpec{
|
|
||||||
VirtPort: incoming,
|
|
||||||
Target: outgoing,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RPCServer is a Server wrapped to work inside the context of net/rpc
|
|
||||||
type RPCServer struct {
|
|
||||||
*Server
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddHost adds a host to the server.
|
|
||||||
func (rs *RPCServer) AddHost(req routerpc.AddHostRequest, resp *routerpc.AddHostResponse) error {
|
|
||||||
if req.APIKey != "hunter2" {
|
|
||||||
return errors.New("invalid api key")
|
|
||||||
}
|
|
||||||
|
|
||||||
token := uuid.New()
|
|
||||||
|
|
||||||
var pKey *rsa.PrivateKey
|
|
||||||
if kk, ok := req.PrivKey.(*rsa.PrivateKey); ok {
|
|
||||||
pKey = kk
|
|
||||||
} else {
|
|
||||||
return errors.New("there must be a 1024 bit RSA private key")
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Token = token
|
|
||||||
resp.PrivKey = pKey
|
|
||||||
|
|
||||||
if req.Hostname != "" {
|
|
||||||
resp.Hostname = req.Hostname
|
|
||||||
} else {
|
|
||||||
resp.Hostname = elfs.MakeName() + rs.cfg.DomainSuffix
|
|
||||||
}
|
|
||||||
|
|
||||||
err := rs.db.SaveRoute(resp)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.xeserv.us/xena/route/database"
|
||||||
|
"git.xeserv.us/xena/route/lib/tun2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type storageWrapper struct {
|
||||||
|
database.Storage
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ tun2.Storage = &storageWrapper{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *storageWrapper) HasToken(token string) (string, []string, error) {
|
||||||
|
t, err := s.Storage.GetToken(context.Background(), token)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.Owner, t.Scopes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *storageWrapper) HasRoute(domain string) (string, error) {
|
||||||
|
r, err := s.Storage.GetRoute(context.Background(), domain)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.Creator, nil
|
||||||
|
}
|
Loading…
Reference in New Issue