-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit ab62f51
Showing
2 changed files
with
130 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# ddns-cloudflare | ||
|
||
Self-hosted DDNS service for receiving public IP updates from a router or other local source | ||
and updating a DNS record in Cloudflare. | ||
|
||
The primary purposes for building yet another DDNS tool for updating Cloudflare records: | ||
- Many other solutions depend upon using a `Global` API token which is unacceptable for this singular purpose. This solution allows the use of a API token scoped with privileges to edit a single DNS zone. | ||
- Unforunately Cloudflare does not allow limiting the edit privileges to a single DNS record in a zone at this point. | ||
- Self-hosted option for receving DDNS updates from a router or other local source rather than depending on querying an external API for public IP address changes | ||
|
||
Valid response codes were gathered from here: | ||
https://github.com/troglobit/inadyn/blob/master/plugins/common.c | ||
|
||
This service requires a GET request in the following format | ||
|
||
https://<username>:<password>@ddns.example.com/update?hostname=<dns record to update>&ip=<public ip> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
#!/usr/bin/python3 | ||
|
||
import CloudFlare | ||
import os | ||
from flask import Flask, request | ||
from flask_httpauth import HTTPBasicAuth | ||
from werkzeug.security import generate_password_hash, check_password_hash | ||
|
||
app = Flask(__name__) | ||
auth = HTTPBasicAuth() | ||
|
||
# Set variables from environment variables | ||
auth_user = os.environ.get('AUTH_USER', None) | ||
auth_pass = os.environ.get('AUTH_PASS', None) | ||
api_token = os.environ.get('API_TOKEN', None) | ||
|
||
users = { | ||
auth_user: generate_password_hash(auth_pass) | ||
} | ||
|
||
@auth.verify_password | ||
def verify_password(username, password): | ||
if username in users and \ | ||
check_password_hash(users.get(username), password): | ||
return username | ||
|
||
@auth.error_handler | ||
def unauthorized(): | ||
return "badauth" | ||
|
||
@app.route('/update') | ||
@auth.login_required | ||
def main(): | ||
# Set hostname variable | ||
if 'hostname' in request.args: | ||
hostname = request.args.get('hostname') | ||
else: | ||
hostname = 'blank' | ||
|
||
# Set ip variable | ||
if 'ip' in request.args: | ||
ip = request.args.get('ip') | ||
else: | ||
ip = 'blank' | ||
|
||
# Test inputs to determine the next step | ||
if hostname == 'blank': | ||
return "nohost" | ||
elif ip == 'blank': | ||
return "noip" | ||
else: | ||
response = check_cloudflare(hostname, ip) | ||
return(response) | ||
|
||
# Cloudflare Functions | ||
def check_cloudflare(hostname, ip): | ||
record_name = hostname | ||
record_type = 'A' | ||
|
||
# Determine the DNS zone from the supplied hostname | ||
hostname_split = hostname.split('.') | ||
zone_name = '.'.join(hostname_split[1:]) | ||
|
||
# Initialize the Cloudflare API | ||
cf = CloudFlare.CloudFlare(token=api_token) | ||
|
||
# Get the DNS zone ID | ||
try: | ||
zones = cf.zones.get(params={'name': zone_name}) | ||
if len(zones) == 0: | ||
log_msg('Zone not found') | ||
zone_id = zones[0]['id'] | ||
except CloudFlare.exceptions.CloudFlareAPIError as e: | ||
log_msg('/zones.get %d %s' % (e, e)) | ||
|
||
# Get the DNS record ID | ||
try: | ||
dns_records = cf.zones.dns_records.get(zone_id, params={'name': record_name, 'type': record_type}) | ||
if len(dns_records) == 0: | ||
log_msg('DNS record not found') | ||
record_id = dns_records[0]['id'] | ||
record_content = dns_records[0]['content'] | ||
record_ttl = dns_records[0]['ttl'] | ||
except CloudFlare.exceptions.CloudFlareAPIError as e: | ||
log_msg('/zones.dns_records.get %d %s' % (e, e)) | ||
|
||
# Test if the record needs updating | ||
if record_content != ip: | ||
log_msg("A DNS record update is needed for " + record_name) | ||
|
||
# Update the DNS record | ||
dns_record = { | ||
'type': record_type, | ||
'name': record_name, | ||
'content': ip, | ||
'ttl': 60 | ||
} | ||
|
||
try: | ||
cf.zones.dns_records.put(zone_id, record_id, data=dns_record) | ||
log_msg('DNS record updated successfully: ' + record_name + "(" + ip + ")") | ||
except CloudFlare.exceptions.CloudFlareAPIError as e: | ||
log_msg('/zones.dns_records.put %d %s' % (e, e)) | ||
return "dnserr" | ||
|
||
return("good " + ip) | ||
else: | ||
return("nochg " + ip) | ||
|
||
def log_msg(msg): | ||
print(msg) | ||
|
||
if __name__ == '__main__': | ||
app.run(host='0.0.0.0', port=8080, debug=True) |