From 78576a9bbc1fe2a7dc5486ad505b3fa10740a4b0 Mon Sep 17 00:00:00 2001 From: Maxim Pogozhiy Date: Mon, 16 Mar 2020 14:26:33 +0700 Subject: [PATCH] Add Maxmind Editions support --- cmd/nginx/flags.go | 10 +++- cmd/nginx/flags_test.go | 13 +++++ internal/ingress/controller/config/config.go | 1 + internal/ingress/controller/controller.go | 3 +- internal/ingress/controller/nginx.go | 12 ++-- internal/nginx/maxmind.go | 60 +++++++++++++------- rootfs/etc/nginx/template/nginx.tmpl | 41 +++++++++++++ 7 files changed, 110 insertions(+), 30 deletions(-) diff --git a/cmd/nginx/flags.go b/cmd/nginx/flags.go index 933678182..d2ead6b47 100644 --- a/cmd/nginx/flags.go +++ b/cmd/nginx/flags.go @@ -186,6 +186,7 @@ Takes the form ":port". If not provided, no admission controller is starte flags.StringVar(&nginx.MaxmindLicenseKey, "maxmind-license-key", "", `Maxmind license key to download GeoLite2 Databases. https://blog.maxmind.com/2019/12/18/significant-changes-to-accessing-and-using-geolite2-databases`) + flags.StringVar(&nginx.MaxmindEditionIDs, "maxmind-edition-ids", "GeoLite2-City,GeoLite2-ASN", `Maxmind edition ids to download GeoLite2 Databases.`) flag.Set("logtostderr", "true") @@ -310,12 +311,15 @@ https://blog.maxmind.com/2019/12/18/significant-changes-to-accessing-and-using-g config.RootCAFile = *rootCAFile } - if nginx.MaxmindLicenseKey != "" { + if nginx.MaxmindLicenseKey != "" && nginx.MaxmindEditionIDs != "" { + if err := nginx.ValidateGeoLite2DBEditions(); err != nil { + return false, nil, err + } klog.Info("downloading maxmind GeoIP2 databases...") - err := nginx.DownloadGeoLite2DB() - if err != nil { + if err := nginx.DownloadGeoLite2DB(); err != nil { klog.Errorf("unexpected error downloading GeoIP2 database: %v", err) } + config.MaxmindEditionFiles = nginx.MaxmindEditionFiles } return false, config, nil diff --git a/cmd/nginx/flags_test.go b/cmd/nginx/flags_test.go index 6e826b932..dba2f43c0 100644 --- a/cmd/nginx/flags_test.go +++ b/cmd/nginx/flags_test.go @@ -75,3 +75,16 @@ func TestFlagConflict(t *testing.T) { t.Fatalf("Expected an error parsing flags but none returned") } } + +func TestMaxmindEdition(t *testing.T) { + resetForTesting(func() { t.Fatal("Parsing failed") }) + + oldArgs := os.Args + defer func() { os.Args = oldArgs }() + os.Args = []string{"cmd", "--publish-service", "namespace/test", "--http-port", "0", "--https-port", "0", "--maxmind-license-key", "0000000", "--maxmind-edition-ids", "GeoLite2-City, TestCheck"} + + _, _, err := parseFlags() + if err == nil { + t.Fatalf("Expected an error parsing flags but none returned") + } +} diff --git a/internal/ingress/controller/config/config.go b/internal/ingress/controller/config/config.go index 88d1a5c90..b9aa93a47 100644 --- a/internal/ingress/controller/config/config.go +++ b/internal/ingress/controller/config/config.go @@ -826,6 +826,7 @@ type TemplateConfig struct { ListenPorts *ListenPorts PublishService *apiv1.Service EnableMetrics bool + MaxmindEditionFiles []string PID string StatusPath string diff --git a/internal/ingress/controller/controller.go b/internal/ingress/controller/controller.go index e943e5717..d7dd890d6 100644 --- a/internal/ingress/controller/controller.go +++ b/internal/ingress/controller/controller.go @@ -98,7 +98,8 @@ type Configuration struct { ValidationWebhookCertPath string ValidationWebhookKeyPath string - GlobalExternalAuth *ngx_config.GlobalExternalAuth + GlobalExternalAuth *ngx_config.GlobalExternalAuth + MaxmindEditionFiles []string } // GetPublishService returns the Service used to set the load-balancer status of Ingresses. diff --git a/internal/ingress/controller/nginx.go b/internal/ingress/controller/nginx.go index 0cc1bbe34..cec908065 100644 --- a/internal/ingress/controller/nginx.go +++ b/internal/ingress/controller/nginx.go @@ -615,12 +615,12 @@ func (n NGINXController) generateTemplate(cfg ngx_config.Configuration, ingressC ListenPorts: n.cfg.ListenPorts, PublishService: n.GetPublishService(), EnableMetrics: n.cfg.EnableMetrics, - - HealthzURI: nginx.HealthPath, - PID: nginx.PID, - StatusPath: nginx.StatusPath, - StatusPort: nginx.StatusPort, - StreamPort: nginx.StreamPort, + MaxmindEditionFiles: n.cfg.MaxmindEditionFiles, + HealthzURI: nginx.HealthPath, + PID: nginx.PID, + StatusPath: nginx.StatusPath, + StatusPort: nginx.StatusPort, + StreamPort: nginx.StreamPort, } tc.Cfg.Checksum = ingressCfg.ConfigurationChecksum diff --git a/internal/nginx/maxmind.go b/internal/nginx/maxmind.go index 1da8110a1..e6128eab5 100644 --- a/internal/nginx/maxmind.go +++ b/internal/nginx/maxmind.go @@ -30,12 +30,14 @@ import ( // MaxmindLicenseKey maxmind license key to download databases var MaxmindLicenseKey = "" +// MaxmindEditionIDs maxmind editions (GeoLite2-City, GeoLite2-Country, GeoIP2-ISP, etc) +var MaxmindEditionIDs = "" + +// MaxmindEditionFiles maxmind databases on disk +var MaxmindEditionFiles []string + const ( - geoIPPath = "/etc/nginx/geoip" - - geoLiteCityDB = "GeoLite2-City" - geoLiteASNDB = "GeoLite2-ASN" - + geoIPPath = "/etc/nginx/geoip" dbExtension = ".mmdb" maxmindURL = "https://download.maxmind.com/app/geoip_download?license_key=%v&edition_id=%v&suffix=tar.gz" @@ -44,12 +46,10 @@ const ( // GeoLite2DBExists checks if the required databases for // the GeoIP2 NGINX module are present in the filesystem func GeoLite2DBExists() bool { - if !fileExists(path.Join(geoIPPath, geoLiteASNDB+dbExtension)) { - return false - } - - if !fileExists(path.Join(geoIPPath, geoLiteCityDB+dbExtension)) { - return false + for _, dbName := range strings.Split(MaxmindEditionIDs, ",") { + if !fileExists(path.Join(geoIPPath, dbName+dbExtension)) { + return false + } } return true @@ -58,16 +58,13 @@ func GeoLite2DBExists() bool { // DownloadGeoLite2DB downloads the required databases by the // GeoIP2 NGINX module using a license key from MaxMind. func DownloadGeoLite2DB() error { - err := downloadDatabase(geoLiteCityDB) - if err != nil { - return err + for _, dbName := range strings.Split(MaxmindEditionIDs, ",") { + err := downloadDatabase(dbName) + if err != nil { + return err + } + MaxmindEditionFiles = append(MaxmindEditionFiles, dbName+dbExtension) } - - err = downloadDatabase(geoLiteASNDB) - if err != nil { - return err - } - return nil } @@ -133,6 +130,29 @@ func downloadDatabase(dbName string) error { fmt.Sprintf(maxmindURL, "XXXXXXX", dbName), mmdbFile) } +// ValidateGeoLite2DBEditions check provided Maxmind database editions names +func ValidateGeoLite2DBEditions() error { + allowedEditions := map[string]bool{ + "GeoIP2-Anonymous-IP": true, + "GeoIP2-Country": true, + "GeoIP2-City": true, + "GeoIP2-Connection-Type": true, + "GeoIP2-Domain": true, + "GeoIP2-ISP": true, + "GeoIP2-ASN": true, + "GeoLite2-ASN": true, + "GeoLite2-Country": true, + "GeoLite2-City": true, + } + + for _, edition := range strings.Split(MaxmindEditionIDs, ",") { + if !allowedEditions[edition] { + return fmt.Errorf("unknown Maxmind GeoIP2 edition name: '%s'", edition) + } + } + return nil +} + func fileExists(filePath string) bool { info, err := os.Stat(filePath) if os.IsNotExist(err) { diff --git a/rootfs/etc/nginx/template/nginx.tmpl b/rootfs/etc/nginx/template/nginx.tmpl index c0b690dd2..76856ee3f 100755 --- a/rootfs/etc/nginx/template/nginx.tmpl +++ b/rootfs/etc/nginx/template/nginx.tmpl @@ -159,6 +159,8 @@ http { {{ if $cfg.UseGeoIP2 }} # https://github.com/leev/ngx_http_geoip2_module#example-usage + {{ range $index, $file := $all.MaxmindEditionFiles }} + {{ if eq $file "GeoLite2-City.mmdb" }} geoip2 /etc/nginx/geoip/GeoLite2-City.mmdb { $geoip2_city_country_code source=$remote_addr country iso_code; $geoip2_city_country_name source=$remote_addr country names en; @@ -171,13 +173,52 @@ http { $geoip2_region_code source=$remote_addr subdivisions 0 iso_code; $geoip2_region_name source=$remote_addr subdivisions 0 names en; } + {{ end }} + {{ if eq $file "GeoIP2-City.mmdb" }} + geoip2 /etc/nginx/geoip/GeoIP2-City.mmdb { + $geoip2_city_country_code source=$remote_addr country iso_code; + $geoip2_city_country_name source=$remote_addr country names en; + $geoip2_city source=$remote_addr city names en; + $geoip2_postal_code source=$remote_addr postal code; + $geoip2_dma_code source=$remote_addr location metro_code; + $geoip2_latitude source=$remote_addr location latitude; + $geoip2_longitude source=$remote_addr location longitude; + $geoip2_time_zone source=$remote_addr location time_zone; + $geoip2_region_code source=$remote_addr subdivisions 0 iso_code; + $geoip2_region_name source=$remote_addr subdivisions 0 names en; + } + {{ end }} + + {{ if eq $file "GeoLite2-ASN.mmdb" }} geoip2 /etc/nginx/geoip/GeoLite2-ASN.mmdb { $geoip2_asn source=$remote_addr autonomous_system_number; $geoip2_org source=$remote_addr autonomous_system_organization; } {{ end }} + {{ if eq $file "GeoIP2-ASN.mmdb" }} + geoip2 /etc/nginx/geoip/GeoIP2-ASN.mmdb { + $geoip2_asn source=$remote_addr autonomous_system_number; + $geoip2_org source=$remote_addr autonomous_system_organization; + } + {{ end }} + + {{ if eq $file "GeoIP2-ISP.mmdb" }} + geoip2 /etc/nginx/geoip/GeoIP2-ISP.mmdb { + $geoip2_isp isp; + $geoip2_isp_org organization; + } + {{ end }} + {{ if eq $file "GeoIP2-Connection-Type.mmdb" }} + geoip2 /etc/nginx/geoip/GeoIP2-Connection-Type.mmdb { + $geoip2_connection_type connection_type; + } + {{ end }} + {{ end }} + + {{ end }} + aio threads; aio_write on;