From a457029b8c79327c3c2e645283f15b28dfb138e8 Mon Sep 17 00:00:00 2001 From: Matt Drollette Date: Thu, 11 Dec 2014 00:05:27 -0600 Subject: [PATCH] https server --- cmd/keygen.go | 4 +- cmd/reseeder.go | 60 ++++++++++++----- cmd/su3.go | 60 ----------------- reseed/legacy.go | 19 ------ reseed/server.go | 171 ++++++++++++++++++----------------------------- reseed/utils.go | 6 +- 6 files changed, 115 insertions(+), 205 deletions(-) delete mode 100644 cmd/su3.go diff --git a/cmd/keygen.go b/cmd/keygen.go index 8e3ff53..bf7d1d1 100644 --- a/cmd/keygen.go +++ b/cmd/keygen.go @@ -19,8 +19,8 @@ import ( func NewKeygenCommand() cli.Command { return cli.Command{ Name: "keygen", - Usage: "Generate keys for reseed Su3 signing", - Description: "Generate keys for reseed Su3 signing", + Usage: "Generate keys for reseed SU3 signing", + Description: "Generate keys for reseed SU3 signing", Action: keygenAction, Flags: []cli.Flag{ cli.StringFlag{ diff --git a/cmd/reseeder.go b/cmd/reseeder.go index 4998790..e1c2619 100644 --- a/cmd/reseeder.go +++ b/cmd/reseeder.go @@ -3,7 +3,6 @@ package cmd import ( "fmt" "log" - "net/http" "github.com/MDrollette/go-i2p/reseed" "github.com/codegangsta/cli" @@ -18,31 +17,60 @@ func NewReseedCommand() cli.Command { Flags: []cli.Flag{ cli.StringFlag{ Name: "addr", - Value: "127.0.0.1:8080", + Value: "127.0.0.1:9090", Usage: "IP and port to listen on", }, + cli.StringFlag{ + Name: "netdb", + Usage: "Path to NetDB directory containing routerInfos", + }, + cli.StringFlag{ + Name: "tlscert", + Value: "cert.pem", + Usage: "Path to tls certificate", + }, + cli.StringFlag{ + Name: "tlskey", + Value: "key.pem", + Usage: "Path to tls key", + }, + cli.StringFlag{ + Name: "keyfile", + Value: "reseed_private.pem", + Usage: "Path to your su3 signing private key", + }, }, } } func reseedAction(c *cli.Context) { - log.Println("Starting server on", c.String("addr")) + netdbDir := c.String("netdb") + if netdbDir == "" { + fmt.Println("--netdb is required") + return + } - netdb := reseed.NewLocalNetDb(c.Args().Get(0)) + // load our signing privKey + privKey, err := loadPrivateKey(c.String("keyfile")) + if nil != err { + log.Fatalln(err) + } + + // create a local file netdb provider + netdb := reseed.NewLocalNetDb(netdbDir) + + // create a reseeder reseeder := reseed.NewReseeder(netdb) + reseeder.SigningKey = privKey + reseeder.SignerId = []byte("matt@drollette.com") - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - peer := reseeder.Peer(r) - seeds, err := reseeder.Seed(peer) - if nil != err { - fmt.Fprintf(w, "Problem: '%s'", err) - return - } + // create a server + server := reseed.NewServer() + server.Reseeder = reseeder + server.Addr = c.String("addr") - for _, s := range seeds { - fmt.Fprintf(w, "%s\n", s.Name) - } - }) + // @todo generate self-signed keys if they don't exist - http.ListenAndServe("127.0.0.1:9090", nil) + log.Printf("Server listening on %s\n", server.Addr) + server.ListenAndServeTLS(c.String("tlscert"), c.String("tlskey")) } diff --git a/cmd/su3.go b/cmd/su3.go deleted file mode 100644 index 7a9cdad..0000000 --- a/cmd/su3.go +++ /dev/null @@ -1,60 +0,0 @@ -package cmd - -import ( - "crypto/x509" - "encoding/pem" - "io/ioutil" - "log" - "net/http" - - "github.com/MDrollette/go-i2p/reseed" - "github.com/codegangsta/cli" -) - -func NewSu3Command() cli.Command { - return cli.Command{ - Name: "su3", - Usage: "Do SU3 things", - Description: "Do SU3 things", - Action: su3Action, - Flags: []cli.Flag{}, - } -} - -func su3Action(c *cli.Context) { - netdb := reseed.NewLocalNetDb(c.Args().Get(0)) - reseeder := reseed.NewReseeder(netdb) - - // make a fake request to get a peer - r, _ := http.NewRequest("GET", "/i2pseeds.su3", nil) - - peer := reseeder.Peer(r) - seeds, err := reseeder.Seed(peer) - if nil != err { - log.Fatalln(err) - return - } - - // load our signing privKey - privPem, err := ioutil.ReadFile("reseed_private.pem") - if nil != err { - log.Fatalln(err) - return - } - privDer, _ := pem.Decode(privPem) - privKey, err := x509.ParsePKCS1PrivateKey(privDer.Bytes) - if nil != err { - log.Fatalln(err) - return - } - - // create an SU3 from the seed - su3File, err := reseeder.CreateSu3(seeds) - - // sign the su3 with our key - su3File.SignerId = []byte("matt@drollette.com") - su3File.Sign(privKey) - - //write the file to disk - ioutil.WriteFile("i2pseeds.su3", su3File.Bytes(), 0777) -} diff --git a/reseed/legacy.go b/reseed/legacy.go index f8fa87e..a633308 100644 --- a/reseed/legacy.go +++ b/reseed/legacy.go @@ -22,21 +22,6 @@ const ( LIST_TEMPLATE = `NetDB` ) -func proxiedHandler(h http.Handler) http.Handler { - return remoteAddrFixup{h} -} - -type remoteAddrFixup struct { - h http.Handler -} - -func (h remoteAddrFixup) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if prior, ok := r.Header["X-Forwarded-For"]; ok { - r.RemoteAddr = prior[0] - } - h.h.ServeHTTP(w, r) -} - type Config struct { NetDBDir string RefreshInterval time.Duration @@ -67,10 +52,6 @@ func Run(config *Config) { th := throttled.RateLimit(throttled.PerMin(config.RateLimit), &throttled.VaryBy{RemoteAddr: true}, store.NewMemStore(1000)) muxWithMiddlewares := th.Throttle(r) - if config.Proxy { - muxWithMiddlewares = proxiedHandler(muxWithMiddlewares) - } - if config.Verbose { muxWithMiddlewares = handlers.CombinedLoggingHandler(os.Stdout, muxWithMiddlewares) } diff --git a/reseed/server.go b/reseed/server.go index e5f180b..d1c20a0 100644 --- a/reseed/server.go +++ b/reseed/server.go @@ -1,128 +1,89 @@ package reseed import ( - "io/ioutil" - "log" - "math/rand" + "bytes" + "crypto/tls" + "fmt" + "io" "net/http" "os" - "path/filepath" - "regexp" - "sync" - "github.com/MDrollette/go-i2p/su3" + "github.com/PuerkitoBio/throttled" + "github.com/PuerkitoBio/throttled/store" + "github.com/gorilla/handlers" + "github.com/justinas/alice" ) -type routerInfo struct { - Name string - Data []byte +const ( + I2P_USER_AGENT = "Wget/1.11.4" +) + +type Server struct { + *http.Server + Reseeder Reseeder } -type Peer string -type Seed []routerInfo +func NewServer() *Server { + config := &tls.Config{MinVersion: tls.VersionTLS10} + h := &http.Server{TLSConfig: config} + server := Server{h, nil} -type Reseeder interface { - // seed a peer with routerinfos - Seed(p Peer) (Seed, error) - // get a peer from a given request - Peer(r *http.Request) Peer - // create an Su3 file from the given seeds - CreateSu3(seeds Seed) (*su3.Su3File, error) + th := throttled.RateLimit(throttled.PerHour(4), &throttled.VaryBy{RemoteAddr: true}, store.NewMemStore(10000)) + + middlewareChain := alice.New(proxiedMiddleware, loggingMiddleware, verifyMiddleware, th.Throttle) + + mux := http.NewServeMux() + mux.Handle("/i2pseeds.su3", middlewareChain.Then(http.HandlerFunc(server.reseedHandler))) + server.Handler = mux + + return &server } -type ReseederImpl struct { - netdb NetDbProvider - peers map[string]Peer - m sync.Mutex +func (s *Server) indexHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Index") } -func NewReseeder(netdb NetDbProvider) *ReseederImpl { - return &ReseederImpl{ - netdb: netdb, - peers: make(map[string]Peer), - } -} +func (s *Server) reseedHandler(w http.ResponseWriter, r *http.Request) { + peer := s.Reseeder.Peer(r) -func (rs *ReseederImpl) Seed(p Peer) (Seed, error) { - all, err := rs.netdb.RouterInfos() + seeds, err := s.Reseeder.Seeds(peer) if nil != err { - return nil, err - } - - return Seed(all), nil -} - -func (rs *ReseederImpl) Peer(r *http.Request) Peer { - rs.m.Lock() - defer rs.m.Unlock() - - if p, ok := rs.peers[r.RemoteAddr]; !ok { - rs.peers[r.RemoteAddr] = Peer(r.RemoteAddr) - } else { - return p - } - - return rs.peers[r.RemoteAddr] -} - -func (rs *ReseederImpl) CreateSu3(seeds Seed) (*su3.Su3File, error) { - su3File := su3.NewSu3File() - su3File.FileType = su3.FILE_TYPE_ZIP - su3File.ContentType = su3.CONTENT_TYPE_RESEED - - zipped, err := zipSeeds(seeds) - if nil != err { - return nil, err - } - su3File.Content = zipped - - return su3File, nil -} - -type NetDbProvider interface { - // Get all router infos - RouterInfos() ([]routerInfo, error) -} - -type LocalNetDbImpl struct { - Path string -} - -func NewLocalNetDb(path string) *LocalNetDbImpl { - return &LocalNetDbImpl{ - Path: path, - } -} - -func (db *LocalNetDbImpl) RouterInfos() (routerInfos []routerInfo, err error) { - var src []os.FileInfo - if src, err = ioutil.ReadDir(db.Path); nil != err { + http.Error(w, "500 Unable to provide seeds", http.StatusInternalServerError) return } - // randomize the file order - files := make([]os.FileInfo, len(src)) - perm := rand.Perm(len(src)) - for i, v := range perm { - files[v] = src[i] + su3, err := s.Reseeder.CreateSu3(seeds) + if nil != err { + http.Error(w, "500 Unable to generate SU3", http.StatusInternalServerError) + return } - r, _ := regexp.Compile("^routerInfo-[A-Za-z0-9-=~]+.dat$") - - for _, file := range files { - if r.MatchString(file.Name()) { - riBytes, err := ioutil.ReadFile(filepath.Join(db.Path, file.Name())) - if nil != err { - log.Println(err) - continue - } - - routerInfos = append(routerInfos, routerInfo{ - Name: file.Name(), - Data: riBytes, - }) - } - } - - return + io.Copy(w, bytes.NewReader(su3.Bytes())) +} + +func loggingMiddleware(next http.Handler) http.Handler { + return handlers.CombinedLoggingHandler(os.Stdout, next) +} + +func verifyMiddleware(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + if I2P_USER_AGENT != r.UserAgent() { + http.Error(w, "403 Forbidden", http.StatusForbidden) + return + } + + next.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) +} + +func proxiedMiddleware(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + if prior, ok := r.Header["X-Forwarded-For"]; ok { + r.RemoteAddr = prior[0] + } + + next.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) } diff --git a/reseed/utils.go b/reseed/utils.go index a526bc2..8a58b75 100644 --- a/reseed/utils.go +++ b/reseed/utils.go @@ -6,7 +6,7 @@ import ( "io/ioutil" ) -func zipSeeds(seeds Seed) ([]byte, error) { +func zipSeeds(seeds Seeds) ([]byte, error) { // Create a buffer to write our archive to. buf := new(bytes.Buffer) @@ -32,14 +32,14 @@ func zipSeeds(seeds Seed) ([]byte, error) { return buf.Bytes(), nil } -func uzipSeeds(c []byte) (Seed, error) { +func uzipSeeds(c []byte) (Seeds, error) { input := bytes.NewReader(c) zipReader, err := zip.NewReader(input, int64(len(c))) if nil != err { return nil, err } - var seeds Seed + var seeds Seeds for _, f := range zipReader.File { rc, err := f.Open() if err != nil {