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
This commit is contained in:
parent
5b1ee690f9
commit
b07bbea0aa
1
go.mod
1
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
|
||||
|
2
go.sum
2
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=
|
||||
|
@ -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)
|
||||
|
76
internal/geoip/config.go
Normal file
76
internal/geoip/config.go
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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 != "" {
|
||||
|
@ -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"`
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user