Last active
March 14, 2023 01:42
-
-
Save CallumCarmicheal/5fad0eacc7527ce17e16fd82db286741 to your computer and use it in GitHub Desktop.
Cloudflare DNS Updating tool, Simple C# app with json configuration to update cloudflare ip's to public ip. Just create a C# .Net Framework 4.8 console app, compile then create a Task Scheduler entry.
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
using Newtonsoft.Json; | |
using RestSharp; | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Security.Policy; | |
using System.Text; | |
using System.Threading.Tasks; | |
namespace UpdateDNSApp { | |
internal class CloudflareAPI { | |
public string ApiUrl { get; } | |
public string AuthEmail { get; } | |
public string AuthKey { get; } | |
public string ZoneId { get; } | |
private RestClient restClient; | |
public CloudflareAPI(string authEmail, string authKey, string zoneId, string apiUrl = "https://api.cloudflare.com/client/v4/") { | |
ApiUrl = apiUrl; | |
AuthEmail = authEmail; | |
AuthKey = authKey; | |
ZoneId = zoneId; | |
restClient = new RestClient(apiUrl); | |
} | |
public CloudflareDnsRecords GetAllRecords() { | |
var request = new RestRequest($"/zones/{ZoneId}/dns_records", Method.Get); | |
request.AddHeader("Authorization", $"Bearer {AuthKey}"); | |
var response = restClient.Execute(request); | |
var content = response.Content; | |
return JsonConvert.DeserializeObject<CloudflareDnsRecords>(content); | |
} | |
public bool UpdateDnsRecord(string dnsRecordId, string type, string name, string content) { | |
var putData = new { type, name, content }; | |
string jsonPostData = JsonConvert.SerializeObject(putData); | |
var request = new RestRequest($"/zones/{ZoneId}/dns_records/{dnsRecordId}", Method.Patch); | |
// request.AddHeader("Content-Type", "application/json"); | |
request.AddHeader("Authorization", $"Bearer {AuthKey}"); | |
request.AddParameter("application/json", jsonPostData, ParameterType.RequestBody); | |
request.RequestFormat = DataFormat.Json; | |
var response = restClient.Execute(request); | |
var jsonContent = response.Content; | |
dynamic results = JsonConvert.DeserializeObject<dynamic>(jsonContent); | |
return results.success == null ? false : (bool) results.success; | |
} | |
} | |
} |
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
using System; | |
using System.Globalization; | |
using Newtonsoft.Json; | |
using Newtonsoft.Json.Converters; | |
namespace UpdateDNSApp { | |
public partial class CloudflareDnsRecords { | |
[JsonProperty("result")] | |
public DnsRecordsResults[] Records { get; set; } | |
[JsonProperty("success")] | |
public bool Success { get; set; } | |
[JsonProperty("errors")] | |
public object[] Errors { get; set; } | |
[JsonProperty("messages")] | |
public object[] Messages { get; set; } | |
[JsonProperty("result_info")] | |
public ResultInfo ResultInfo { get; set; } | |
} | |
public partial class DnsRecordsResults { | |
[JsonProperty("id")] | |
public string Id { get; set; } | |
[JsonProperty("zone_id")] | |
public string ZoneId { get; set; } | |
[JsonProperty("zone_name")] | |
public string ZoneName { get; set; } | |
[JsonProperty("name")] | |
public string Name { get; set; } | |
[JsonProperty("type")] | |
public string Type { get; set; } | |
[JsonProperty("content")] | |
public string Content { get; set; } | |
[JsonProperty("proxiable")] | |
public bool Proxiable { get; set; } | |
[JsonProperty("proxied")] | |
public bool Proxied { get; set; } | |
[JsonProperty("ttl")] | |
public long Ttl { get; set; } | |
[JsonProperty("locked")] | |
public bool Locked { get; set; } | |
[JsonProperty("meta")] | |
public Meta Meta { get; set; } | |
[JsonProperty("comment")] | |
public object Comment { get; set; } | |
[JsonProperty("tags")] | |
public object[] Tags { get; set; } | |
[JsonProperty("created_on")] | |
public DateTimeOffset CreatedOn { get; set; } | |
[JsonProperty("modified_on")] | |
public DateTimeOffset ModifiedOn { get; set; } | |
[JsonProperty("priority", NullValueHandling = NullValueHandling.Ignore)] | |
public long? Priority { get; set; } | |
[JsonProperty("data", NullValueHandling = NullValueHandling.Ignore)] | |
public Data Data { get; set; } | |
} | |
public partial class Data { | |
[JsonProperty("name")] | |
public string Name { get; set; } | |
[JsonProperty("port")] | |
public long Port { get; set; } | |
[JsonProperty("priority")] | |
public long Priority { get; set; } | |
[JsonProperty("proto")] | |
public string Proto { get; set; } | |
[JsonProperty("service")] | |
public string Service { get; set; } | |
[JsonProperty("target")] | |
public string Target { get; set; } | |
[JsonProperty("weight")] | |
public long Weight { get; set; } | |
} | |
public partial class Meta { | |
[JsonProperty("auto_added")] | |
public bool AutoAdded { get; set; } | |
[JsonProperty("managed_by_apps")] | |
public bool ManagedByApps { get; set; } | |
[JsonProperty("managed_by_argo_tunnel")] | |
public bool ManagedByArgoTunnel { get; set; } | |
[JsonProperty("source")] | |
public string Source { get; set; } | |
} | |
public partial class ResultInfo { | |
[JsonProperty("page")] | |
public long Page { get; set; } | |
[JsonProperty("per_page")] | |
public long PerPage { get; set; } | |
[JsonProperty("count")] | |
public long Count { get; set; } | |
[JsonProperty("total_count")] | |
public long TotalCount { get; set; } | |
[JsonProperty("total_pages")] | |
public long TotalPages { get; set; } | |
} | |
internal static class Converter { | |
public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings { | |
MetadataPropertyHandling = MetadataPropertyHandling.Ignore, | |
DateParseHandling = DateParseHandling.None, | |
Converters = { | |
new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal } | |
}, | |
}; | |
} | |
} |
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
{ | |
"$VARIABLES": { | |
"_COMMENT_EXPLAIN": "These variables will be replaced in the configuration when loading inside the CloudflareZones key. They are not required but help with multiple dns zones.", | |
"CF_ApiUrl": "https://api.cloudflare.com/client/v4/", | |
"CF_AuthEmail": "api user's email address", | |
"CF_AuthKey": "enter your api key", | |
"CF_EXAMPLECOM_DNS_ZONE_ID": "your websites zone id." | |
}, | |
"CloudflareZones": [ | |
{ | |
"ApiUrl": "$CF_ApiUrl", | |
"AuthEmail": "$CF_AuthEmail", | |
"AuthKey": "$CF_AuthKey", | |
"ZoneId": "$CF_EXAMPLECOM_DNS_ZONE_ID", | |
"Updates": [ | |
{ | |
"Name": "extranet.website.com", | |
"Type": "A", | |
"IpAddress": "$IP_PUBLIC" | |
}, | |
{ | |
"Name": "docker.services.website.com", | |
"Type": "A", | |
"IpAddress": "$IP_PUBLIC" | |
}, | |
{ | |
"Name": "someapplication.services.website.com", | |
"Type": "A", | |
"IpAddress": "$IP_PUBLIC" | |
} | |
] | |
} | |
] | |
} | |
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
using System; | |
using System.Collections.Generic; | |
using System.Globalization; | |
using Newtonsoft.Json; | |
using Newtonsoft.Json.Converters; | |
namespace UpdateDNSApp { | |
public partial class Configuration { | |
[JsonProperty("$VARIABLES")] | |
public Dictionary<string, string> Variables { get; set; } | |
[JsonProperty("CloudflareZones")] | |
public CloudflareZone[] CloudflareZones { get; set; } | |
} | |
public partial class CloudflareZone { | |
[JsonProperty("ApiUrl")] | |
public string ApiUrl { get; set; } | |
[JsonProperty("AuthEmail")] | |
public string AuthEmail { get; set; } | |
[JsonProperty("AuthKey")] | |
public string AuthKey { get; set; } | |
[JsonProperty("ZoneId")] | |
public string ZoneId { get; set; } | |
[JsonProperty("Updates")] | |
public Update[] Updates { get; set; } | |
} | |
public partial class Update { | |
[JsonProperty("Name")] | |
public string Name { get; set; } | |
[JsonProperty("Type")] | |
public string Type { get; set; } | |
[JsonProperty("IpAddress")] | |
public string IpAddress { get; set; } | |
} | |
} |
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
<?xml version="1.0" encoding="utf-8" ?> | |
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" | |
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | |
<targets> | |
<target name="logfile" xsi:type="File" fileName="UpdateDNSApp.log" /> | |
<target name="logconsole" xsi:type="Console" /> | |
</targets> | |
<rules> | |
<logger name="*" minlevel="Info" writeTo="logconsole" /> | |
<logger name="*" minlevel="Debug" writeTo="logfile" /> | |
</rules> | |
</nlog> |
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
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Net; | |
using Newtonsoft.Json; | |
using RestSharp; | |
namespace UpdateDNSApp { | |
internal class Program { | |
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); | |
static void Main(string[] args) { | |
logger.Info("UpdateDNSApp: Update Cloudflare DNS by Callum Carmicheal."); | |
string publicIp = GetIPAddress().Trim(); | |
logger.Info("Recieved Public IP: " + publicIp); | |
logger.Info("Loading configuration file: Configuration.json"); | |
Configuration cfg = null; | |
string fJsonConfiguration = null; | |
try { | |
fJsonConfiguration = System.IO.File.ReadAllText("./Configuration.json").Replace("$IP_PUBLIC", publicIp); | |
cfg = JsonConvert.DeserializeObject<Configuration>(fJsonConfiguration); | |
} catch (Exception ex) { | |
logger.Error(ex, "Failed to read configuration file."); | |
return; | |
} | |
// Load configuration values | |
logger.Info("Applying configuration variables."); | |
foreach ( var kv in cfg.Variables ) | |
fJsonConfiguration = fJsonConfiguration.Replace("$" + kv.Key, kv.Value); | |
try { | |
Configuration temp = JsonConvert.DeserializeObject<Configuration>(fJsonConfiguration); | |
cfg.CloudflareZones = temp.CloudflareZones; | |
} catch (Exception ex) { | |
logger.Error(ex, "Failed to apply variables to configuration file."); | |
return; | |
} | |
// Loop our dns zones | |
logger.Info("Looping update zones."); | |
foreach (var zone in cfg.CloudflareZones) { | |
logger.Info($"[{zone.ZoneId.Truncate(4)}] Performing update actions for zone ({zone.ZoneId})."); | |
// Load the api for the zone | |
var cfapi = new CloudflareAPI(zone.AuthEmail, zone.AuthKey, zone.ZoneId, zone.ApiUrl); | |
logger.Info($"[{zone.ZoneId.Truncate(4)}] Retrieving existing records"); | |
CloudflareDnsRecords existingDnsRecords; | |
try { | |
existingDnsRecords = cfapi.GetAllRecords(); | |
if (existingDnsRecords.Success == false) { | |
logger.Info($"[{zone.ZoneId.Truncate(4)}] Failed to retrieve records, skipping zone. Cannot process records without recordId!"); | |
continue; | |
} | |
} catch (Exception ex) { | |
logger.Error(ex, $"[{zone.ZoneId.Truncate(4)}] Failed to retrieve records, skipping zone. Cannot process records without recordId!"); | |
continue; | |
} | |
foreach (var rec in zone.Updates) { | |
// Find the dns in the recoprds | |
var record = existingDnsRecords.Records.Where(cfrec => cfrec.Name == rec.Name).FirstOrDefault(); | |
if (record == null) { | |
logger.Error($"[{zone.ZoneId.Truncate(4)}] Failed to find record for {rec.Name}, skipping. Cannot process record without recordId!"); | |
continue; | |
} | |
// Update the dns | |
try { | |
logger.Info($"[{zone.ZoneId.Truncate(4)}] Updating dns record for {rec.Name} to {rec.IpAddress}."); | |
var success = cfapi.UpdateDnsRecord(record.Id, rec.Type, rec.Name, rec.IpAddress); | |
logger.Info($"[{zone.ZoneId.Truncate(4)}] Successfully updated record of {rec.Name}."); | |
} catch (Exception ex) { | |
logger.Error(ex, $"[{zone.ZoneId.Truncate(4)}] Failed to update dns record for {rec.Name}."); | |
continue; | |
} | |
} | |
} | |
} | |
public static string GetIPAddress() { | |
WebClient webClient = new WebClient(); | |
return webClient.DownloadString("https://icanhazip.com/"); | |
} | |
} | |
public static class Extensions { | |
public static string Truncate(this string value, int MaxLength) => value?.Length > MaxLength ? value.Substring(0, MaxLength) : value; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment