Skip to content

Instantly share code, notes, and snippets.

@CallumCarmicheal
Last active March 14, 2023 01:42
Show Gist options
  • Save CallumCarmicheal/5fad0eacc7527ce17e16fd82db286741 to your computer and use it in GitHub Desktop.
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.
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;
}
}
}
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 }
},
};
}
}
{
"$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"
}
]
}
]
}
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; }
}
}
<?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>
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