From 74944b99e98f894054f4dc2e4e7f287f52719a91 Mon Sep 17 00:00:00 2001 From: Manuel Alejandro de Brito Fontes Date: Wed, 8 Jan 2020 19:46:43 -0300 Subject: [PATCH] Enable download of GeoLite2 databases (#4896) --- cmd/nginx/flags.go | 11 ++ .../nginx-configuration/configmap.md | 7 + internal/ingress/controller/store/store.go | 6 + internal/nginx/maxmind.go | 143 ++++++++++++++++++ 4 files changed, 167 insertions(+) create mode 100644 internal/nginx/maxmind.go diff --git a/cmd/nginx/flags.go b/cmd/nginx/flags.go index 73aaeecc32..54a52abe07 100644 --- a/cmd/nginx/flags.go +++ b/cmd/nginx/flags.go @@ -181,6 +181,9 @@ Takes the form ":port". If not provided, no admission controller is starte flags.MarkDeprecated("enable-dynamic-certificates", `Only dynamic mode is supported`) + 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`) + flag.Set("logtostderr", "true") flags.AddGoFlagSet(flag.CommandLine) @@ -297,5 +300,13 @@ Takes the form ":port". If not provided, no admission controller is starte config.RootCAFile = *rootCAFile } + if nginx.MaxmindLicenseKey != "" { + klog.Info("downloading maxmind GeoIP2 databases...") + err := nginx.DownloadGeoLite2DB() + if err != nil { + klog.Errorf("unexpected error downloading GeoIP2 database: %v", err) + } + } + return false, config, nil } diff --git a/docs/user-guide/nginx-configuration/configmap.md b/docs/user-guide/nginx-configuration/configmap.md index 200a09c00b..e374a7f699 100755 --- a/docs/user-guide/nginx-configuration/configmap.md +++ b/docs/user-guide/nginx-configuration/configmap.md @@ -563,6 +563,13 @@ _**default:**_ true ## use-geoip2 Enables the [geoip2 module](https://github.com/leev/ngx_http_geoip2_module) for NGINX. +Since `0.27.0` and due to a [change in the MaxMind databases](https://blog.maxmind.com/2019/12/18/significant-changes-to-accessing-and-using-geolite2-databases) a license is required to have access to the databases. +For this reason, it is required to define a new flag `--maxmind-license-key` in the ingress controller deployment to download the databases needed during the initialization of the ingress controller. +Alternatively, it is possible to use a volume to mount the files `/etc/nginx/geoip/GeoLite2-City.mmdb` and `/etc/nginx/geoip/GeoLite2-ASN.mmdb`, avoiding the overhead of the download. + +!!! Important + If the feature is enabled but the files are missing, GeoIP2 will not be enabled. + _**default:**_ false ## enable-brotli diff --git a/internal/ingress/controller/store/store.go b/internal/ingress/controller/store/store.go index c595b9f11c..f2a944cbf9 100644 --- a/internal/ingress/controller/store/store.go +++ b/internal/ingress/controller/store/store.go @@ -54,6 +54,7 @@ import ( "k8s.io/ingress-nginx/internal/ingress/errors" "k8s.io/ingress-nginx/internal/ingress/resolver" "k8s.io/ingress-nginx/internal/k8s" + "k8s.io/ingress-nginx/internal/nginx" ) // IngressFilterFunc decides if an Ingress should be omitted or not @@ -896,6 +897,11 @@ func (s *k8sStore) setConfig(cmap *corev1.ConfigMap) { } s.backendConfig = ngx_template.ReadConfig(cmap.Data) + if s.backendConfig.UseGeoIP2 && !nginx.GeoLite2DBExists() { + klog.Warning("The GeoIP2 feature is enabled but the databases are missing. Disabling.") + s.backendConfig.UseGeoIP2 = false + } + s.writeSSLSessionTicketKey(cmap, "/etc/nginx/tickets.key") } diff --git a/internal/nginx/maxmind.go b/internal/nginx/maxmind.go new file mode 100644 index 0000000000..1da8110a1d --- /dev/null +++ b/internal/nginx/maxmind.go @@ -0,0 +1,143 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package nginx + +import ( + "archive/tar" + "compress/gzip" + "fmt" + "io" + "net/http" + "os" + "path" + "strings" +) + +// MaxmindLicenseKey maxmind license key to download databases +var MaxmindLicenseKey = "" + +const ( + geoIPPath = "/etc/nginx/geoip" + + geoLiteCityDB = "GeoLite2-City" + geoLiteASNDB = "GeoLite2-ASN" + + dbExtension = ".mmdb" + + maxmindURL = "https://download.maxmind.com/app/geoip_download?license_key=%v&edition_id=%v&suffix=tar.gz" +) + +// 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 + } + + return true +} + +// 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 + } + + err = downloadDatabase(geoLiteASNDB) + if err != nil { + return err + } + + return nil +} + +func downloadDatabase(dbName string) error { + url := fmt.Sprintf(maxmindURL, MaxmindLicenseKey, dbName) + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return err + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("HTTP status %v", resp.Status) + } + + archive, err := gzip.NewReader(resp.Body) + if err != nil { + return err + } + defer archive.Close() + + mmdbFile := dbName + dbExtension + + tarReader := tar.NewReader(archive) + for true { + header, err := tarReader.Next() + if err == io.EOF { + break + } + + if err != nil { + return err + } + + switch header.Typeflag { + case tar.TypeReg: + if !strings.HasSuffix(header.Name, mmdbFile) { + continue + } + + outFile, err := os.Create(path.Join(geoIPPath, mmdbFile)) + if err != nil { + return err + } + + defer outFile.Close() + + if _, err := io.Copy(outFile, tarReader); err != nil { + return err + } + + return nil + } + } + + return fmt.Errorf("the URL %v does not contains the database %v", + fmt.Sprintf(maxmindURL, "XXXXXXX", dbName), mmdbFile) +} + +func fileExists(filePath string) bool { + info, err := os.Stat(filePath) + if os.IsNotExist(err) { + return false + } + + return !info.IsDir() +}