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

Implement GeoIP-based DNS resolution. #72

Merged
merged 2 commits into from
Feb 27, 2018
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ services:

# run builds for all the trains (and more)
rust:
- stable
- nightly

env:
global:
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ iron-cors = { git = "https://github.com/mozilla-iot/iron-cors.git" }
lettre = "0.7"
lettre_email = "0.7"
log = "0.4"
maxminddb = "0.8.1"
mount = "0.4"
params = "0.8"
r2d2 = "0.8"
Expand Down
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,28 @@ FLAGS:
-V, --version Prints version information

OPTIONS:
--api-ttl <ttl> TTL of the DNS records for the api subdomain, in seconds.
--caa-record <record> The CAA record the PowerDNS server should return.
--config-file <path> Path to a toml configuration file.
--confirmation-body <s> The body of the confirmation email.
--confirmation-title <s> The title of the confirmation email.
--db-path <path> The database path: file path, postgres://..., mysql://...
--dns-ttl <ttl> TTL of the DNS records, in seconds.
--dns-ttl <ttl> TTL of the SOA/MX/TXT/CAA DNS records, in seconds.
--domain <domain> The domain that will be tied to this registration server.
--email-password <pass> The password for this email account.
--email-sender <email> The email identity to use as a sender.
--email-server <name> The name of the SMTP server.
--email-user <username> The username to authenticate with.
--error-page <s> HTML content of the email confirmation error page.
--geoip-default <ip> The IP address of the default tunnel endpoint.
--geoip-database <path> Path to the GeoIP2/GeoLite2 database.
--geoip-continent-af <ip> The IP address of the tunnel endpoint for Africa.
--geoip-continent-an <ip> The IP address of the tunnel endpoint for Antarctica.
--geoip-continent-as <ip> The IP address of the tunnel endpoint for Asia.
--geoip-continent-eu <ip> The IP address of the tunnel endpoint for Europe.
--geoip-continent-na <ip> The IP address of the tunnel endpoint for North America.
--geoip-continent-oc <ip> The IP address of the tunnel endpoint for Oceania.
--geoip-continent-sa <ip> The IP address of the tunnel endpoint for South America.
--host <host> Set local hostname.
--http-port <port> Set port to listen on for HTTP connections (0 to turn off).
--https-port <port> Set port to listen on for TLS connections (0 to turn off).
Expand All @@ -41,7 +51,7 @@ OPTIONS:
--soa-content <dns> The content of the SOA record for this tunnel.
--socket-path <path> The path to the socket used to communicate with PowerDNS.
--success-page <s> HTML content of the email confirmation success page.
--tunnel-ip <ip> The IP address of the tunnel endpoint.
--tunnel-ttl <ttl> TTL of the DNS records for tunnels, in seconds.
--txt-record <record> The TXT record the PowerDNS server should return.
```

Expand Down
18 changes: 16 additions & 2 deletions config/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,31 @@ domain = "mydomain.org"
db_path = "/tmp/domains.sqlite"
identity_directory = "/tmp/certs"
identity_password = "mypassword"
tunnel_ip = "1.2.3.4"

[pdns]
dns_ttl = 89
api_ttl = 10
dns_ttl = 600
tunnel_ttl = 60
soa_content = "a.dns.gandi.net hostmaster.gandi.net 1476196782 10800 3600 604800 10800"
socket_path = "/tmp/powerdns_tunnel.sock"
mx_record = ""
caa_record = "0 issue \"letsencrypt.org\""
txt_record = ""
psl_record = "https://github.com/publicsuffix/list/pull/XYZ"

[pdns.geoip]
default = "5.6.7.8"
database = "./test-data/GeoLite2-Country_20180206/GeoLite2-Country.mmdb"

[pdns.geoip.continent]
AF = "1.2.3.4"
AN = "2.3.4.5"
AS = "3.4.5.6"
EU = "4.5.6.7"
NA = "5.6.7.8"
OC = "6.7.8.9"
SA = "9.8.7.6"

[email]
server = "mail.gandi.net"
user = "accounts@mydomain.org"
Expand Down
21 changes: 19 additions & 2 deletions doc/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ log-dns-details=yes
log-dns-queries=yes
loglevel=5

# If using geoip in the registration server, uncomment the following:
#query-cache-ttl=0
#cache-ttl=0
```

* The `CONFIG_DIR/config.toml` file holds the registration server configuration. Here's a sample consistent with the `pdns.conf` shown above:
Expand All @@ -130,10 +133,11 @@ db_path = "/home/user/data/domains.sqlite"
# Uncomment to use TLS (recommended)
# identity_directory = "/home/user/config"
# identity_password = "mypassword"
tunnel_ip = "1.2.3.4"

[pdns]
dns_ttl = 1203
api_ttl = 10
dns_ttl = 60
tunnel_ttl = 600
# Check your DNS configuration to fill in this field.
soa_content = "a.dns.gandi.net hostmaster.gandi.net 1476196782 10800 3600 604800 10800"
socket_path = "/tmp/powerdns_tunnel.sock"
Expand All @@ -143,6 +147,19 @@ txt_record = ""
# Uncomment to set a PSL authentication record
# psl_record = "https://github.com/publicsuffix/list/pull/XYZ"

[pdns.geoip]
default = "5.6.7.8"
database = "/home/user/geoip/GeoLite2-Country.mmdb"

[pdns.geoip.continent]
AF = "1.2.3.4"
AN = "2.3.4.5"
AS = "3.4.5.6"
EU = "4.5.6.7"
NA = "5.6.7.8"
OC = "6.7.8.9"
SA = "9.8.7.6"

[email]
server = "mail.gandi.net"
user = "accounts@mydomain.org"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE domains DROP COLUMN continent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE domains ADD COLUMN continent VARCHAR(2) NOT NULL DEFAULT '';
UPDATE domains SET continent = 'NA' WHERE continent = '';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE domains DROP COLUMN continent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE domains ADD COLUMN continent VARCHAR(2) NOT NULL DEFAULT '';
UPDATE domains SET continent = 'NA' WHERE continent = '';
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
CREATE TABLE domains_new AS SELECT
id,
name,
account_id,
token,
description,
timestamp,
dns_challenge,
reclamation_token,
verification_token,
verified FROM domains;
DROP TABLE domains;
ALTER TABLE domains_new RENAME TO domains;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE domains ADD COLUMN continent VARCHAR(2) NOT NULL DEFAULT '';
UPDATE domains SET continent = 'NA' WHERE continent = '';
109 changes: 92 additions & 17 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use clap::{App, ArgMatches};
use config::{Args, EmailOptions, GeneralOptions, PdnsOptions};
use config::{Args, Continent, EmailOptions, GeneralOptions, GeoIp, PdnsOptions};
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;
Expand All @@ -17,14 +17,24 @@ const USAGE: &str = "--config-file=[path] 'Path to a toml configuration file
--db-path=[path] 'The database path: file path, postgres://..., mysql://...'
--identity-directory=[dir] 'Identity directory.'
--identity-password=[password] 'Identity password.'
--tunnel-ip=[ip] 'The IP address of the tunnel endpoint.'
--dns-ttl=[ttl] 'TTL of the DNS records, in seconds.'
--dns-ttl=[ttl] 'TTL of the SOA/MX/TXT/CAA DNS records, in seconds.'
--api-ttl=[ttl] 'TTL of the DNS records for the api subdomain, in seconds.'
--tunnel-ttl=[ttl] 'TTL of the DNS records for tunnels, in seconds.'
--soa-content=[dns] 'The content of the SOA record for this tunnel.'
--socket-path=[path] 'The path to the socket used to communicate with PowerDNS.'
--mx-record=[record] 'The MX record the PowerDNS server should return.'
--caa-record=[record] 'The CAA record the PowerDNS server should return.'
--txt-record=[record] 'The TXT record the PowerDNS server should return.'
--psl-record=[record] 'The TXT record used to authenticate against the Public Suffix List.'
--geoip-default=[ip] 'The IP address of the default tunnel endpoint.'
--geoip-database=[path] 'Path to the GeoIP2/GeoLite2 database.'
--geoip-continent-af=[ip] 'The IP address of the tunnel endpoint for Africa.'
--geoip-continent-an=[ip] 'The IP address of the tunnel endpoint for Antarctica.'
--geoip-continent-as=[ip] 'The IP address of the tunnel endpoint for Asia.'
--geoip-continent-eu=[ip] 'The IP address of the tunnel endpoint for Europe.'
--geoip-continent-na=[ip] 'The IP address of the tunnel endpoint for North America.'
--geoip-continent-oc=[ip] 'The IP address of the tunnel endpoint for Oceania.'
--geoip-continent-sa=[ip] 'The IP address of the tunnel endpoint for South America.'
--email-server=[name] 'The name of the SMTP server.'
--email-user=[username] 'The username to authenticate with.'
--email-password=[pass] 'The password for this email account.'
Expand Down Expand Up @@ -80,6 +90,14 @@ impl ArgsParser {
optional!(success_page, "success-page");
optional!(error_page, "error-page");
optional!(psl_record, "psl-record");
optional!(geoip_database, "geoip-database");
optional!(geoip_continent_af, "geoip-continent-af");
optional!(geoip_continent_an, "geoip-continent-an");
optional!(geoip_continent_as, "geoip-continent-as");
optional!(geoip_continent_eu, "geoip-continent-eu");
optional!(geoip_continent_na, "geoip-continent-na");
optional!(geoip_continent_oc, "geoip-continent-oc");
optional!(geoip_continent_sa, "geoip-continent-sa");

Args {
general: GeneralOptions {
Expand All @@ -93,13 +111,11 @@ impl ArgsParser {
db_path: String::from(matches.value_of("db-path").unwrap_or("./domains.sqlite")),
identity_directory: identity_directory,
identity_password: identity_password,
tunnel_ip: matches
.value_of("tunnel-ip")
.unwrap_or("0.0.0.0")
.to_owned(),
},
pdns: PdnsOptions {
dns_ttl: value_t!(matches, "dns-ttl", u32).unwrap_or(60),
api_ttl: value_t!(matches, "api-ttl", u32).unwrap_or(10),
dns_ttl: value_t!(matches, "dns-ttl", u32).unwrap_or(600),
tunnel_ttl: value_t!(matches, "tunnel-ttl", u32).unwrap_or(60),
soa_content: matches
.value_of("soa-content")
.unwrap_or("_soa_not_configured_")
Expand All @@ -118,6 +134,22 @@ impl ArgsParser {
.unwrap_or("_txt_not_configured_")
.to_owned(),
psl_record: psl_record,
geoip: GeoIp {
default: matches
.value_of("geoip-default")
.unwrap_or("0.0.0.0")
.to_owned(),
database: geoip_database,
continent: Continent {
AF: geoip_continent_af,
AN: geoip_continent_an,
AS: geoip_continent_as,
EU: geoip_continent_eu,
NA: geoip_continent_na,
OC: geoip_continent_oc,
SA: geoip_continent_sa,
},
},
},
email: EmailOptions {
server: email_server,
Expand Down Expand Up @@ -152,7 +184,7 @@ impl ArgsParser {

#[test]
fn test_args() {
let args = ArgsParser::from_vec(vec!["registration_server", "--tunnel-ip=1.2.3.4"]);
let args = ArgsParser::from_vec(vec!["registration_server", "--geoip-default=1.2.3.4"]);

assert_eq!(args.general.host, "0.0.0.0");
assert_eq!(args.general.http_port, 4242);
Expand All @@ -161,14 +193,24 @@ fn test_args() {
assert_eq!(args.general.db_path, "./domains.sqlite");
assert_eq!(args.general.identity_directory, None);
assert_eq!(args.general.identity_password, None);
assert_eq!(args.general.tunnel_ip, "1.2.3.4");
assert_eq!(args.pdns.dns_ttl, 60);
assert_eq!(args.pdns.api_ttl, 10);
assert_eq!(args.pdns.dns_ttl, 600);
assert_eq!(args.pdns.tunnel_ttl, 60);
assert_eq!(args.pdns.soa_content, "_soa_not_configured_");
assert_eq!(args.pdns.socket_path, None);
assert_eq!(args.pdns.mx_record, "_mx_not_configured_");
assert_eq!(args.pdns.caa_record, "_caa_not_configured_");
assert_eq!(args.pdns.txt_record, "_txt_not_configured_");
assert_eq!(args.pdns.psl_record, None);
assert_eq!(args.pdns.geoip.default, "1.2.3.4");
assert_eq!(args.pdns.geoip.database, None);
assert_eq!(args.pdns.geoip.continent.AF, None);
assert_eq!(args.pdns.geoip.continent.AN, None);
assert_eq!(args.pdns.geoip.continent.AS, None);
assert_eq!(args.pdns.geoip.continent.EU, None);
assert_eq!(args.pdns.geoip.continent.NA, None);
assert_eq!(args.pdns.geoip.continent.OC, None);
assert_eq!(args.pdns.geoip.continent.SA, None);
assert_eq!(args.email.server, None);
assert_eq!(args.email.user, None);
assert_eq!(args.email.password, None);
Expand All @@ -189,8 +231,18 @@ fn test_args() {
"--db-path=/tmp/mydata/domains.sqlite",
"--identity-directory=/tmp/mycerts",
"--identity-password=mypass",
"--tunnel-ip=1.2.3.4",
"--dns-ttl=120",
"--geoip-default=1.2.3.4",
"--geoip-database=/path/to/mmdb",
"--geoip-continent-af=1.1.1.1",
"--geoip-continent-an=2.2.2.2",
"--geoip-continent-as=3.3.3.3",
"--geoip-continent-eu=4.4.4.4",
"--geoip-continent-na=5.5.5.5",
"--geoip-continent-oc=6.6.6.6",
"--geoip-continent-sa=7.7.7.7",
"--api-ttl=120",
"--dns-ttl=140",
"--tunnel-ttl=160",
"--soa-content=_my_soa",
"--socket-path=/tmp/socket",
"--mx-record=_my_mx",
Expand Down Expand Up @@ -219,14 +271,24 @@ fn test_args() {
Some(PathBuf::from("/tmp/mycerts"))
);
assert_eq!(args.general.identity_password, Some("mypass".to_owned()));
assert_eq!(args.general.tunnel_ip, "1.2.3.4");
assert_eq!(args.pdns.dns_ttl, 120);
assert_eq!(args.pdns.api_ttl, 120);
assert_eq!(args.pdns.dns_ttl, 140);
assert_eq!(args.pdns.tunnel_ttl, 160);
assert_eq!(args.pdns.soa_content, "_my_soa");
assert_eq!(args.pdns.socket_path, Some("/tmp/socket".to_owned()));
assert_eq!(args.pdns.mx_record, "_my_mx");
assert_eq!(args.pdns.caa_record, "_my_caa");
assert_eq!(args.pdns.txt_record, "_my_txt");
assert_eq!(args.pdns.psl_record, Some("_my_psl".to_owned()));
assert_eq!(args.pdns.geoip.default, "1.2.3.4");
assert_eq!(args.pdns.geoip.database, Some("/path/to/mmdb".to_owned()));
assert_eq!(args.pdns.geoip.continent.AF, Some("1.1.1.1".to_owned()));
assert_eq!(args.pdns.geoip.continent.AN, Some("2.2.2.2".to_owned()));
assert_eq!(args.pdns.geoip.continent.AS, Some("3.3.3.3".to_owned()));
assert_eq!(args.pdns.geoip.continent.EU, Some("4.4.4.4".to_owned()));
assert_eq!(args.pdns.geoip.continent.NA, Some("5.5.5.5".to_owned()));
assert_eq!(args.pdns.geoip.continent.OC, Some("6.6.6.6".to_owned()));
assert_eq!(args.pdns.geoip.continent.SA, Some("7.7.7.7".to_owned()));
assert_eq!(args.email.server, Some("test.email.com".to_owned()));
assert_eq!(args.email.user, Some("my_email_user".to_owned()));
assert_eq!(args.email.password, Some("my_password".to_owned()));
Expand Down Expand Up @@ -293,8 +355,9 @@ fn test_args() {
args.general.identity_password,
Some("mypassword".to_owned())
);
assert_eq!(args.general.tunnel_ip, "1.2.3.4");
assert_eq!(args.pdns.dns_ttl, 89);
assert_eq!(args.pdns.api_ttl, 10);
assert_eq!(args.pdns.dns_ttl, 600);
assert_eq!(args.pdns.tunnel_ttl, 60);
assert_eq!(args.pdns.soa_content, soa);
assert_eq!(
args.pdns.socket_path,
Expand All @@ -307,6 +370,18 @@ fn test_args() {
args.pdns.psl_record,
Some("https://github.com/publicsuffix/list/pull/XYZ".to_owned())
);
assert_eq!(args.pdns.geoip.default, "5.6.7.8");
assert_eq!(
args.pdns.geoip.database,
Some("./test-data/GeoLite2-Country_20180206/GeoLite2-Country.mmdb".to_owned())
);
assert_eq!(args.pdns.geoip.continent.AF, Some("1.2.3.4".to_owned()));
assert_eq!(args.pdns.geoip.continent.AN, Some("2.3.4.5".to_owned()));
assert_eq!(args.pdns.geoip.continent.AS, Some("3.4.5.6".to_owned()));
assert_eq!(args.pdns.geoip.continent.EU, Some("4.5.6.7".to_owned()));
assert_eq!(args.pdns.geoip.continent.NA, Some("5.6.7.8".to_owned()));
assert_eq!(args.pdns.geoip.continent.OC, Some("6.7.8.9".to_owned()));
assert_eq!(args.pdns.geoip.continent.SA, Some("9.8.7.6".to_owned()));
assert_eq!(args.email.server, Some("mail.gandi.net".to_owned()));
assert_eq!(args.email.user, Some("accounts@mydomain.org".to_owned()));
assert_eq!(args.email.password, Some("******".to_owned()));
Expand Down