diff --git a/go.mod b/go.mod index c4f3f67..75797fd 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( ) require ( + github.com/IncSW/geoip2 v0.1.2 github.com/go-git/go-billy/v5 v5.3.1 github.com/go-git/go-git/v5 v5.4.2 github.com/gorilla/mux v1.8.0 diff --git a/go.sum b/go.sum index b1c4744..9b40210 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/IncSW/geoip2 v0.1.2 h1:v7iAyDiNZjHES45P1JPM3SMvkw0VNeJtz0XSVxkRwOY= +github.com/IncSW/geoip2 v0.1.2/go.mod h1:adcasR40vXiUBjtzdaTTKL/6wSf+fgO4M8Gve/XzPUk= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= diff --git a/internal/commands/host.go b/internal/commands/host.go index 39ee7fc..c23265c 100644 --- a/internal/commands/host.go +++ b/internal/commands/host.go @@ -31,13 +31,13 @@ import ( ) type HostConfig struct { - Server internal.ServerConfig `json:"server"` - Git git.Config `json:"git"` + internal.ServerConfig + Git git.Config `json:"git"` } var ( hostConfig = &HostConfig{ - Server: internal.ServerConfig{ + ServerConfig: internal.ServerConfig{ Public: internal.BindConfig{Address: "0.0.0.0:8080"}, Private: internal.BindConfig{Address: "0.0.0.0:8081"}, }, @@ -57,7 +57,7 @@ var ( return err } - server, err := internal.NewServer(ctx.Context, hostConfig.Server) + server, err := internal.NewServer(ctx.Context, hostConfig.ServerConfig) if err != nil { return err } @@ -78,8 +78,8 @@ var ( server.PublicMux.PathPrefix("/").Handler(http.FileServer(git.HTTP(gitService.FS))).Methods(http.MethodGet) log.Info("serving", - zap.String("public", hostConfig.Server.Public.Address), - zap.String("private", hostConfig.Server.Private.Address)) + zap.String("public", hostConfig.Public.Address), + zap.String("private", hostConfig.Private.Address)) group, c := errgroup.WithContext(ctx.Context) group.Go(server.ListenAndServe) diff --git a/internal/geoip/config.go b/internal/geoip/config.go new file mode 100644 index 0000000..9b61012 --- /dev/null +++ b/internal/geoip/config.go @@ -0,0 +1,76 @@ +// Copyright (C) 2022 The pages authors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +package geoip + +import ( + "net" + + "github.com/IncSW/geoip2" + "github.com/pkg/errors" +) + +// Config contains the set of configuration options needed to configure looking up a users location. +type Config struct { + DB string `json:"db" usage:"path of the Maxmind GeoIP2Country database"` +} + +// Open uses information from the Config to determine which type of lookup to use. +func (c Config) Open() (Interface, error) { + if c.DB != "" { + return CountryLite(c.DB) + } + + return Empty{}, nil +} + +type Info struct { + CountryCode string +} + +type Interface interface { + Lookup(ip string) Info +} + +type Empty struct{} + +func (e Empty) Lookup(ip string) Info { + return Info{} +} + +func CountryLite(file string) (*Maxmind, error) { + reader, err := geoip2.NewCountryReaderFromFile(file) + if err != nil { + return nil, errors.Wrap(err, "failed to open file") + } + + return &Maxmind{reader}, nil +} + +type Maxmind struct { + reader *geoip2.CountryReader +} + +func (m *Maxmind) Lookup(ip string) Info { + record, err := m.reader.Lookup(net.ParseIP(ip)) + if err != nil { + return Info{} + } + + return Info{ + CountryCode: record.Country.ISOCode, + } +} diff --git a/internal/geoip/middleware.go b/internal/geoip/middleware.go index b8e9f1f..96ea17b 100644 --- a/internal/geoip/middleware.go +++ b/internal/geoip/middleware.go @@ -28,10 +28,6 @@ import ( var key = myago.ContextKey("geoip.info") -type Info struct { - CountryCode string -} - func Extract(ctx context.Context) Info { val := ctx.Value(key) v, ok := val.(Info) @@ -43,16 +39,6 @@ func Extract(ctx context.Context) Info { return v } -type Interface interface { - Lookup(ip string) Info -} - -type Empty struct{} - -func (e Empty) Lookup(ip string) Info { - return Info{} -} - func Middleware(geoip Interface) mux.MiddlewareFunc { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/internal/server.go b/internal/server.go index 19487d1..f9700e6 100644 --- a/internal/server.go +++ b/internal/server.go @@ -54,6 +54,7 @@ type BindConfig struct { // ServerConfig defines configuration for a public and private interface. type ServerConfig struct { Admin AdminConfig `json:"admin"` + GeoIP geoip.Config `json:"geoip"` Session session.Config `json:"session"` TLS livetls.Config `json:"tls"` Public BindConfig `json:"public"` @@ -67,6 +68,11 @@ func NewServer(ctx context.Context, config ServerConfig) (*Server, error) { return nil, err } + ipdb, err := config.GeoIP.Open() + if err != nil { + return nil, err + } + private := mux.NewRouter() private.Handle("/metrics", promhttp.Handler()) @@ -79,16 +85,21 @@ func NewServer(ctx context.Context, config ServerConfig) (*Server, error) { public := mux.NewRouter() public.Use( func(next http.Handler) http.Handler { return headers.HTTP(next) }, - geoip.Middleware(geoip.Empty{}), + geoip.Middleware(ipdb), pageviews.Middleware( pageviews.Exclusions(exclusions...), ), - session.Middleware( - session.Exclusions(exclusions...), - session.JavaScriptPath(path.Join(config.Session.Prefix, "pages.js")), - ), ) + if config.Session.Enable { + public.Use( + session.Middleware( + session.Exclusions(exclusions...), + session.JavaScriptPath(path.Join(config.Session.Prefix, "pages.js")), + ), + ) + } + admin := public.PathPrefix(config.Admin.Prefix).Subrouter() if config.Admin.Password != "" { diff --git a/internal/session/config.go b/internal/session/config.go index a222b5c..72fd019 100644 --- a/internal/session/config.go +++ b/internal/session/config.go @@ -18,5 +18,6 @@ package session // Config encapsulates configuration for the session endpoints. type Config struct { + Enable bool `json:"enable" usage:"enables session tracking and script injection" default:"true"` Prefix string `json:"prefix" usage:"configure the prefix to use for recording sessions" default:"/_session" hidden:"true"` }