Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable download of GeoLite2 databases #4896

Merged
merged 1 commit into from
Jan 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions cmd/nginx/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,9 @@ Takes the form "<host>: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)
Expand Down Expand Up @@ -297,5 +300,13 @@ Takes the form "<host>: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
}
7 changes: 7 additions & 0 deletions docs/user-guide/nginx-configuration/configmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,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
Expand Down
6 changes: 6 additions & 0 deletions internal/ingress/controller/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
}

Expand Down
143 changes: 143 additions & 0 deletions internal/nginx/maxmind.go
Original file line number Diff line number Diff line change
@@ -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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commercial GeoIP2 databases are more accurate than GeoLite2 ones. We use GeoIP2-ISP instead of GeoLite2-ASN. So with this design I had to create a placeholder GeoLite2-ASN.mmdb file to trick the controller into believing this file exist so that it does not force disable use-geoip2 setting.

I think instead of this the controller should get the comma separated list of DB file names as a command line arrgument and then check for existence of them.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should be more dramatic than that. Right now those files are present in the docker image and are thirteen months old. The old geoip module databases are not maintained anymore.
Before any change, I think we should consider drop support for the old geoip module.


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()
}