2020-01-08 22:46:43 +00:00
/ *
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"
2021-07-08 22:08:53 +00:00
"net"
2020-01-08 22:46:43 +00:00
"net/http"
2021-07-08 22:08:53 +00:00
"net/url"
2020-01-08 22:46:43 +00:00
"os"
"path"
"strings"
2021-07-08 22:08:53 +00:00
"syscall"
"time"
"k8s.io/apimachinery/pkg/util/wait"
klog "k8s.io/klog/v2"
2020-01-08 22:46:43 +00:00
)
// MaxmindLicenseKey maxmind license key to download databases
var MaxmindLicenseKey = ""
2020-03-16 07:26:33 +00:00
// MaxmindEditionIDs maxmind editions (GeoLite2-City, GeoLite2-Country, GeoIP2-ISP, etc)
var MaxmindEditionIDs = ""
2020-01-08 22:46:43 +00:00
2020-03-16 07:26:33 +00:00
// MaxmindEditionFiles maxmind databases on disk
var MaxmindEditionFiles [ ] string
2020-01-08 22:46:43 +00:00
2020-12-28 05:28:16 +00:00
// MaxmindMirror maxmind database mirror url (http://geoip.local)
var MaxmindMirror = ""
2021-07-08 22:08:53 +00:00
// MaxmindRetriesCount number of attempts to download the GeoIP DB
var MaxmindRetriesCount = 1
// MaxmindRetriesTimeout maxmind download retries timeout in seconds, 0 - do not retry to download if something went wrong
var MaxmindRetriesTimeout = time . Second * 0
// minimumRetriesCount minimum value of the MaxmindRetriesCount parameter. If MaxmindRetriesCount less than minimumRetriesCount, it will be set to minimumRetriesCount
const minimumRetriesCount = 1
2020-03-16 07:26:33 +00:00
const (
2023-11-03 13:46:32 +00:00
geoIPPath = "/etc/ingress-controller/geoip"
2020-01-08 22:46:43 +00:00
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
2021-07-09 00:16:53 +00:00
// and indexes the discovered databases for iteration in
// the config.
2020-01-08 22:46:43 +00:00
func GeoLite2DBExists ( ) bool {
2021-07-09 00:16:53 +00:00
files := [ ] string { }
2020-03-16 07:26:33 +00:00
for _ , dbName := range strings . Split ( MaxmindEditionIDs , "," ) {
2021-07-09 00:16:53 +00:00
filename := dbName + dbExtension
if ! fileExists ( path . Join ( geoIPPath , filename ) ) {
klog . Error ( filename , " not found" )
2020-03-16 07:26:33 +00:00
return false
}
2021-07-09 00:16:53 +00:00
files = append ( files , filename )
2020-01-08 22:46:43 +00:00
}
2021-07-09 00:16:53 +00:00
MaxmindEditionFiles = files
2020-01-08 22:46:43 +00:00
return true
}
// DownloadGeoLite2DB downloads the required databases by the
// GeoIP2 NGINX module using a license key from MaxMind.
2021-07-08 22:08:53 +00:00
func DownloadGeoLite2DB ( attempts int , period time . Duration ) error {
if attempts < minimumRetriesCount {
attempts = minimumRetriesCount
}
defaultRetry := wait . Backoff {
Steps : attempts ,
Duration : period ,
Factor : 1.5 ,
Jitter : 0.1 ,
2020-01-08 22:46:43 +00:00
}
2021-07-08 22:08:53 +00:00
if period == time . Duration ( 0 ) {
defaultRetry . Steps = minimumRetriesCount
}
var lastErr error
retries := 0
2023-08-31 07:36:48 +00:00
lastErr = wait . ExponentialBackoff ( defaultRetry , func ( ) ( bool , error ) {
2021-07-08 22:08:53 +00:00
var dlError error
for _ , dbName := range strings . Split ( MaxmindEditionIDs , "," ) {
dlError = downloadDatabase ( dbName )
if dlError != nil {
break
}
}
lastErr = dlError
if dlError == nil {
return true , nil
}
if e , ok := dlError . ( * url . Error ) ; ok {
if e , ok := e . Err . ( * net . OpError ) ; ok {
if e , ok := e . Err . ( * os . SyscallError ) ; ok {
if e . Err == syscall . ECONNREFUSED {
retries ++
klog . InfoS ( "download failed on attempt " + fmt . Sprint ( retries ) )
return false , nil
}
}
}
}
return true , nil
} )
return lastErr
2020-01-08 22:46:43 +00:00
}
2020-12-28 05:28:16 +00:00
func createURL ( mirror , licenseKey , dbName string ) string {
if len ( mirror ) > 0 {
return fmt . Sprintf ( "%s/%s.tar.gz" , mirror , dbName )
}
return fmt . Sprintf ( maxmindURL , licenseKey , dbName )
}
2020-01-08 22:46:43 +00:00
func downloadDatabase ( dbName string ) error {
2023-08-31 07:36:48 +00:00
newURL := createURL ( MaxmindMirror , MaxmindLicenseKey , dbName )
req , err := http . NewRequest ( http . MethodGet , newURL , http . NoBody )
2020-01-08 22:46:43 +00:00
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 )
2023-02-16 13:59:39 +00:00
for {
2020-01-08 22:46:43 +00:00
header , err := tarReader . Next ( )
if err == io . EOF {
break
}
if err != nil {
return err
}
2023-08-31 07:36:48 +00:00
if header . Typeflag == tar . TypeReg {
2020-01-08 22:46:43 +00:00
if ! strings . HasSuffix ( header . Name , mmdbFile ) {
continue
}
2023-09-10 13:48:10 +00:00
return func ( ) error {
outFile , err := os . Create ( path . Join ( geoIPPath , mmdbFile ) )
if err != nil {
return err
}
2020-01-08 22:46:43 +00:00
2023-09-10 13:48:10 +00:00
defer outFile . Close ( )
2020-01-08 22:46:43 +00:00
2023-09-10 13:48:10 +00:00
if _ , err := io . CopyN ( outFile , tarReader , header . Size ) ; err != nil {
return err
}
return nil
} ( )
2020-01-08 22:46:43 +00:00
}
}
return fmt . Errorf ( "the URL %v does not contains the database %v" ,
fmt . Sprintf ( maxmindURL , "XXXXXXX" , dbName ) , mmdbFile )
}
2020-03-16 07:26:33 +00:00
// 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
}
2021-07-09 00:16:53 +00:00
func _fileExists ( filePath string ) bool {
2020-01-08 22:46:43 +00:00
info , err := os . Stat ( filePath )
if os . IsNotExist ( err ) {
return false
}
return ! info . IsDir ( )
}
2021-07-09 00:16:53 +00:00
var fileExists = _fileExists