From b07bbea0aaf0be51dc45f2ed542af88943338a86 Mon Sep 17 00:00:00 2001 From: Mya Pitzeruse Date: Thu, 9 Jun 2022 10:39:44 -0500 Subject: [PATCH] feat(geoip): support maxmind This change adds maxmind support for translating client IP addresses to their country of origin. We decorate our metrics with this country code to improve our understanding of users. Resolves https://github.com/mjpitz/pages/issues/3 --- go.mod | 1 + go.sum | 2 + internal/commands/host.go | 12 +++--- internal/geoip/config.go | 76 ++++++++++++++++++++++++++++++++++++ internal/geoip/middleware.go | 14 ------- internal/server.go | 21 +++++++--- internal/session/config.go | 1 + 7 files changed, 102 insertions(+), 25 deletions(-) create mode 100644 internal/geoip/config.go 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"` }