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

Add Locations endpoints #1516

Merged
merged 22 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f4dbb85
add locations endpoints
bcmmbaga Jan 30, 2024
81412a8
Merge branch 'feature/posture-checks' into locations-endpoints
bcmmbaga Feb 1, 2024
ec1fc81
Add sqlite3 check and database generation in geolite script
bcmmbaga Feb 1, 2024
d2d353a
Merge branch 'feature/posture-checks' into locations-endpoints
bcmmbaga Feb 1, 2024
52f02b3
Add SQLite storage for geolocation data
bcmmbaga Feb 1, 2024
dc3f2b5
Refactor file existence check into a separate function
bcmmbaga Feb 1, 2024
3d4c1fe
Integrate geolocation services into management application
bcmmbaga Feb 1, 2024
c9ef726
Refactoring
bcmmbaga Feb 2, 2024
dc97dae
Refactor city retrieval to include Geonames ID
bcmmbaga Feb 2, 2024
c7479f9
Add signature verification for GeoLite2 database download
bcmmbaga Feb 5, 2024
8eb158c
Change to in-memory database for geolocation store
bcmmbaga Feb 5, 2024
06b080f
Merge manager to geolocation
bcmmbaga Feb 5, 2024
3c4f45f
Update GetAllCountries to return Country name and iso code
bcmmbaga Feb 5, 2024
b59c426
fix tests
bcmmbaga Feb 5, 2024
b9bdfa9
Add reload to SqliteStore
bcmmbaga Feb 5, 2024
1bd4644
Add geoname indexes
bcmmbaga Feb 5, 2024
bb88dfa
move db file check to connectDB
bcmmbaga Feb 7, 2024
d61408e
Add concurrency safety to SQL queries and database reloading
bcmmbaga Feb 7, 2024
c7f7ee1
Merge branch 'feature/posture-checks' into locations-endpoints
bcmmbaga Feb 7, 2024
1e46774
Add sha256 sum check to geolocation store before reload
bcmmbaga Feb 7, 2024
a966322
Use read lock
surik Feb 7, 2024
20635ab
Check SHA256 twice when reload geonames db
surik Feb 7, 2024
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
124 changes: 89 additions & 35 deletions infrastructure_files/download-geolite2.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,41 +22,95 @@ then
exit 1
fi

DATABASE_URL="https://download.maxmind.com/geoip/databases/GeoLite2-City/download?suffix=tar.gz"
SIGNATURE_URL="https://download.maxmind.com/geoip/databases/GeoLite2-City/download?suffix=tar.gz.sha256"

# Download the database and signature files
echo "Downloading database file..."
DATABASE_FILE=$(curl -s -u "$MM_ACCOUNT_ID":"$MM_LICENSE_KEY" -L -O -J "$DATABASE_URL" -w "%{filename_effective}")
echo "Downloading signature file..."
SIGNATURE_FILE=$(curl -s -u "$MM_ACCOUNT_ID":"$MM_LICENSE_KEY" -L -O -J "$SIGNATURE_URL" -w "%{filename_effective}")

# Verify the signature
echo "Verifying signature..."
if sha256sum -c --status "$SIGNATURE_FILE"; then
echo "Signature is valid."
else
echo "Signature is invalid. Aborting."
if ! command -v sqlite3 &> /dev/null
then
echo "sqlite3 is not installed or not in PATH, please install with your package manager. e.g. sudo apt install sqlite3" > /dev/stderr
exit 1
fi

# Unpack the database file
EXTRACTION_DIR=$(basename "$DATABASE_FILE" .tar.gz)
echo "Unpacking $DATABASE_FILE..."
mkdir -p "$EXTRACTION_DIR"
tar -xzvf "$DATABASE_FILE"

# Create a SHA256 signature file
MMDB_FILE="GeoLite2-City.mmdb"
cd "$EXTRACTION_DIR"
sha256sum "$MMDB_FILE" > "$MMDB_FILE.sha256"
echo "SHA256 signature created for $MMDB_FILE."
cd -

# Remove downloaded files
rm "$DATABASE_FILE" "$SIGNATURE_FILE"

# Done. Print next steps
echo "Process completed successfully."
echo "Now you can place $EXTRACTION_DIR/$MMDB_FILE to 'datadir' of management service."
echo -e "Example:\n\tdocker compose cp $EXTRACTION_DIR/$MMDB_FILE management:/var/lib/netbird/"
download_geolite_mmdb() {
DATABASE_URL="https://download.maxmind.com/geoip/databases/GeoLite2-City/download?suffix=tar.gz"
SIGNATURE_URL="https://download.maxmind.com/geoip/databases/GeoLite2-City/download?suffix=tar.gz.sha256"

# Download the database and signature files
echo "Downloading database file..."
DATABASE_FILE=$(curl -s -u "$MM_ACCOUNT_ID":"$MM_LICENSE_KEY" -L -O -J "$DATABASE_URL" -w "%{filename_effective}")
echo "Downloading signature file..."
SIGNATURE_FILE=$(curl -s -u "$MM_ACCOUNT_ID":"$MM_LICENSE_KEY" -L -O -J "$SIGNATURE_URL" -w "%{filename_effective}")

# Verify the signature
echo "Verifying signature..."
if sha256sum -c --status "$SIGNATURE_FILE"; then
echo "Signature is valid."
else
echo "Signature is invalid. Aborting."
exit 1
fi

# Unpack the database file
EXTRACTION_DIR=$(basename "$DATABASE_FILE" .tar.gz)
echo "Unpacking $DATABASE_FILE..."
mkdir -p "$EXTRACTION_DIR"
tar -xzvf "$DATABASE_FILE"

# Create a SHA256 signature file
MMDB_FILE="GeoLite2-City.mmdb"
cd "$EXTRACTION_DIR"
sha256sum "$MMDB_FILE" > "$MMDB_FILE.sha256"
echo "SHA256 signature created for $MMDB_FILE."
cd -

# Remove downloaded files
rm "$DATABASE_FILE" "$SIGNATURE_FILE"

# Done. Print next steps
echo "Process completed successfully."
echo "Now you can place $EXTRACTION_DIR/$MMDB_FILE to 'datadir' of management service."
echo -e "Example:\n\tdocker compose cp $EXTRACTION_DIR/$MMDB_FILE management:/var/lib/netbird/"
}


download_geolite_csv_and_create_sqlite_db() {
DATABASE_URL="https://download.maxmind.com/geoip/databases/GeoLite2-City-CSV/download?suffix=zip"
SIGNATURE_URL="https://download.maxmind.com/geoip/databases/GeoLite2-City-CSV/download?suffix=zip.sha256"


# Download the database file
echo "Downloading database file..."
DATABASE_FILE=$(curl -s -u "$MM_ACCOUNT_ID":"$MM_LICENSE_KEY" -L -O -J "$DATABASE_URL" -w "%{filename_effective}")
echo "Downloading signature file..."
SIGNATURE_FILE=$(curl -s -u "$MM_ACCOUNT_ID":"$MM_LICENSE_KEY" -L -O -J "$SIGNATURE_URL" -w "%{filename_effective}")

# Verify the signature
echo "Verifying signature..."
if sha256sum -c --status "$SIGNATURE_FILE"; then
echo "Signature is valid."
else
echo "Signature is invalid. Aborting."
exit 1
fi

# Unpack the database file
EXTRACTION_DIR=$(basename "$DATABASE_FILE" .zip)
DB_NAME="geonames.db"

unzip "$DATABASE_FILE"

# Create SQLite database and import data from CSV
sqlite3 "$DB_NAME" <<EOF
.mode csv
.import "$EXTRACTION_DIR/GeoLite2-City-Locations-en.csv" geonames
EOF


# Remove downloaded and extracted files
rm -r -r "$EXTRACTION_DIR"
rm "$DATABASE_FILE" "$SIGNATURE_FILE"

echo "SQLite database '$DB_NAME' created successfully."
echo "Now you can place $DB_NAME to 'datadir' of management service."
echo -e "Example:\n\tdocker compose cp $DB_NAME management:/var/lib/netbird/"
bcmmbaga marked this conversation as resolved.
Show resolved Hide resolved
}

download_geolite_mmdb
download_geolite_csv_and_create_sqlite_db
3 changes: 2 additions & 1 deletion management/cmd/management.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (

"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/realip"
"github.com/netbirdio/management-integrations/integrations"

"github.com/netbirdio/netbird/encryption"
mgmtProto "github.com/netbirdio/netbird/management/proto"
"github.com/netbirdio/netbird/management/server"
Expand Down Expand Up @@ -230,7 +231,7 @@ var (
UserIDClaim: config.HttpConfig.AuthUserIDClaim,
KeysLocation: config.HttpConfig.AuthKeysLocation,
}
httpAPIHandler, err := httpapi.APIHandler(accountManager, *jwtValidator, appMetrics, httpAPIAuthCfg)
httpAPIHandler, err := httpapi.APIHandler(accountManager, geo, *jwtValidator, appMetrics, httpAPIAuthCfg)
if err != nil {
return fmt.Errorf("failed creating HTTP API handler: %v", err)
}
Expand Down
66 changes: 65 additions & 1 deletion management/server/geolocation/geolocation.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Geolocation struct {
mux *sync.RWMutex
sha256sum []byte
db *maxminddb.Reader
locationDB *SqliteStore
stopCh chan struct{}
reloadCheckInterval time.Duration
}
Expand All @@ -43,6 +44,16 @@ type Record struct {
} `maxminddb:"country"`
}

type City struct {
GeoNameID int `gorm:"column:geoname_id"`
CityName string
}

type Country struct {
CountryISOCode string `gorm:"column:country_iso_code"`
CountryName string
}

func NewGeolocation(datadir string) (*Geolocation, error) {
mmdbPath := path.Join(datadir, mmdbFileName)

Expand All @@ -56,11 +67,17 @@ func NewGeolocation(datadir string) (*Geolocation, error) {
return nil, err
}

locationDB, err := NewSqliteStore(datadir)
if err != nil {
return nil, err
}

geo := &Geolocation{
mmdbPath: mmdbPath,
mux: &sync.RWMutex{},
sha256sum: sha256sum,
db: db,
locationDB: locationDB,
reloadCheckInterval: 60 * time.Second, // TODO: make configurable
stopCh: make(chan struct{}),
}
Expand Down Expand Up @@ -115,6 +132,38 @@ func (gl *Geolocation) Lookup(ip net.IP) (*Record, error) {
return &record, nil
}

// GetAllCountries retrieves a list of all countries.
func (gl *Geolocation) GetAllCountries() ([]Country, error) {
allCountries, err := gl.locationDB.GetAllCountries()
if err != nil {
return nil, err
}

countries := make([]Country, 0)
for _, country := range allCountries {
if country.CountryName != "" {
countries = append(countries, country)
}
}
return countries, nil
}

// GetCitiesByCountry retrieves a list of cities in a specific country based on the country's ISO code.
func (gl *Geolocation) GetCitiesByCountry(countryISOCode string) ([]City, error) {
allCities, err := gl.locationDB.GetCitiesByCountry(countryISOCode)
if err != nil {
return nil, err
}

cities := make([]City, 0)
for _, city := range allCities {
if city.CityName != "" {
cities = append(cities, city)
}
}
return cities, nil
}

func (gl *Geolocation) Stop() error {
close(gl.stopCh)
if gl.db != nil {
Expand All @@ -129,6 +178,10 @@ func (gl *Geolocation) reloader() {
case <-gl.stopCh:
return
case <-time.After(gl.reloadCheckInterval):
if err := gl.locationDB.reload(); err != nil {
log.Errorf("geonames db reload failed: %s", err)
}

newSha256sum1, err := getSha256sum(gl.mmdbPath)
if err != nil {
log.Errorf("failed to calculate sha256 sum for '%s': %s", gl.mmdbPath, err)
Expand All @@ -149,7 +202,7 @@ func (gl *Geolocation) reloader() {
}
err = gl.reload(newSha256sum2)
if err != nil {
log.Errorf("reload failed: %s", err)
log.Errorf("mmdb reload failed: %s", err)
}
} else {
log.Debugf("No changes in '%s', no need to reload. Next check is in %.0f seconds.",
Expand Down Expand Up @@ -182,3 +235,14 @@ func (gl *Geolocation) reload(newSha256sum []byte) error {

return nil
}

func fileExists(filePath string) (bool, error) {
_, err := os.Stat(filePath)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, fmt.Errorf("%v does not exist", filePath)
}
return false, err
}
12 changes: 10 additions & 2 deletions management/server/geolocation/geolocation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import (
"net"
"os"
"path"
"sync"
"testing"

"github.com/netbirdio/netbird/util"
"github.com/stretchr/testify/assert"

"github.com/netbirdio/netbird/util"
)

// from https://github.com/maxmind/MaxMind-DB/blob/main/test-data/GeoLite2-City-Test.mmdb
Expand All @@ -25,8 +27,14 @@ func TestGeoLite_Lookup(t *testing.T) {
}
}()

geo, err := NewGeolocation(tempDir)
db, err := openDB(mmdbPath)
assert.NoError(t, err)

geo := &Geolocation{
mux: &sync.RWMutex{},
db: db,
stopCh: make(chan struct{}),
}
assert.NotNil(t, geo)
defer func() {
err = geo.Stop()
Expand Down
Loading
Loading