Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@colinl
Last active May 9, 2023 20:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save colinl/514685740c0d68ac0421210cc81ad573 to your computer and use it in GitHub Desktop.
Save colinl/514685740c0d68ac0421210cc81ad573 to your computer and use it in GitHub Desktop.
Flow to Update Cloudflare DNS records

For those who use Cloudflare for managing their DNS records, this flow attempts to update their DNS A records for a given set of domain names.
It uses http://ipinfo.io/ip to determine the current external IP address. Before running the flow you might like to run
curl ipinfo.io/ip
to check that the correct IP address is returned.

The flow expects an array of domain names in msg.payload. Fill in the subflow properties with your Cloudflare Zone ID, Cloudflare logon email and Global API key and whether the DNS records should be marked as proxied or not. Currently it does not allow different settings for Proxied for different domains. If you needed this you could use two instances of the subflow, one for proxied domains and one for non-proxied.

The subflow gets the current external IP address, then fetches the Cloudflare records for the complete Zone. For each of the domains given it compares the current value in the Cloudflare record with that optained from ipinfo.io, and updates only those that need to be updated.

When complete, output 1 sends, in msg.payload, an array of strings indicating, for each domain, whether it was already correct or whether it was updated. If there is any output from output 2 this indicates an error of some sort. Feed the message into a debug node showing the Complete Message to see what the problem was. There may be further information In msg.payload.

For my use I configure the Inject node to run ten seconds after startup, then every 5 minutes. I use output 2 to send me a notification that there is a problem.

For any problems with this flow please ask on the Node Red forum.

[{"id":"b2605c103b3c10bd","type":"subflow","name":"Update Cloudflare DNS","info":"","category":"","in":[{"x":20,"y":160,"wires":[{"id":"56c00aa5bbf32b95"}]}],"out":[{"x":960,"y":740,"wires":[{"id":"2ba9a52f9f444496","port":0},{"id":"f4e2275fe98ba7c4","port":0}]},{"x":920,"y":1040,"wires":[{"id":"320213502d7cd81b","port":0},{"id":"631528af6996430d","port":0}]}],"env":[{"name":"ZoneID","type":"str","value":"","ui":{"label":{"en-US":"Zone ID"},"type":"input","opts":{"types":["str","env"]}}},{"name":"Email","type":"str","value":"","ui":{"type":"input","opts":{"types":["str","env"]}}},{"name":"Key","type":"cred","ui":{"label":{"en-US":"API key"},"type":"input","opts":{"types":["cred"]}}},{"name":"Proxied","type":"bool","value":"false","ui":{"type":"input","opts":{"types":["bool","env"]}}}],"meta":{},"credentials":{"Key":""},"color":"#DDAA99","status":{"x":520,"y":1040,"wires":[{"id":"e7d67646cd91b5de","port":0},{"id":"5f0ca8668bb282e0","port":0}]}},{"id":"d155437807140393","type":"comment","z":"b2605c103b3c10bd","name":"Given an array of domain names, updates cloudflare dns records","info":"","x":330,"y":40,"wires":[]},{"id":"56c00aa5bbf32b95","type":"change","z":"b2605c103b3c10bd","g":"eeb017401050907c","name":"Move domains to msg.domains","rules":[{"t":"move","p":"payload","pt":"msg","to":"domains","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":210,"y":160,"wires":[["27a26faaeaddf108","5ca6f91adb8a1873"]]},{"id":"27a26faaeaddf108","type":"http request","z":"b2605c103b3c10bd","g":"eeb017401050907c","name":"Get our IP","method":"GET","ret":"txt","paytoqs":"ignore","url":"http://ipinfo.io/ip","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":430,"y":160,"wires":[["337770b779737664"]]},{"id":"320213502d7cd81b","type":"catch","z":"b2605c103b3c10bd","name":"","scope":null,"uncaught":false,"x":720,"y":1040,"wires":[[]]},{"id":"337770b779737664","type":"switch","z":"b2605c103b3c10bd","g":"eeb017401050907c","name":"status 200?","property":"statusCode","propertyType":"msg","rules":[{"t":"eq","v":"200","vt":"num"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":590,"y":160,"wires":[["b133ab72bfb0f0bc"],["41341f10167041c5","d4a77c1dff11fdd4"]]},{"id":"41341f10167041c5","type":"link out","z":"b2605c103b3c10bd","g":"eeb017401050907c","name":"link out 1","mode":"link","links":["631528af6996430d"],"x":615,"y":200,"wires":[]},{"id":"631528af6996430d","type":"link in","z":"b2605c103b3c10bd","name":"link in 1","links":["41341f10167041c5","315f6996631ee864","97a03b41453e681a","5af9ab018c92680d"],"x":835,"y":1000,"wires":[[]]},{"id":"b133ab72bfb0f0bc","type":"change","z":"b2605c103b3c10bd","g":"eeb017401050907c","name":"IP to msg.ip","rules":[{"t":"move","p":"payload","pt":"msg","to":"ip","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":810,"y":160,"wires":[["8fb0166cc4a85bf2"]]},{"id":"8fb0166cc4a85bf2","type":"function","z":"b2605c103b3c10bd","g":"0d186e43bfe6c8d1","name":"Prepare request","func":"msg.url = `https://api.cloudflare.com/client/v4/zones/${env.get(\"ZoneID\")}/dns_records/`\nmsg.headers = { \n \"X-Auth-Email\": env.get(\"Email\"),\n \"X-Auth-Key\": env.get(\"Key\"),\n \"Content-Type\": \"application/json\"\n}\nnode.status({text: \"Fetching DNS records from cloudflare\"})\nreturn msg;\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":120,"y":360,"wires":[["d002a11e550e568f"]]},{"id":"d002a11e550e568f","type":"http request","z":"b2605c103b3c10bd","g":"0d186e43bfe6c8d1","name":"","method":"GET","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":290,"y":360,"wires":[["b7281df6ef9ea0c4"]]},{"id":"b7281df6ef9ea0c4","type":"switch","z":"b2605c103b3c10bd","g":"0d186e43bfe6c8d1","name":"status 200?","property":"statusCode","propertyType":"msg","rules":[{"t":"eq","v":"200","vt":"num"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":450,"y":360,"wires":[["b2c3b7ed632da273"],["315f6996631ee864","35e5bcc0290e2bf2"]]},{"id":"315f6996631ee864","type":"link out","z":"b2605c103b3c10bd","g":"0d186e43bfe6c8d1","name":"link out 2","mode":"link","links":["631528af6996430d"],"x":755,"y":360,"wires":[]},{"id":"b2c3b7ed632da273","type":"change","z":"b2605c103b3c10bd","g":"0d186e43bfe6c8d1","name":"dns records to msg.dnsRecords","rules":[{"t":"move","p":"payload.result","pt":"msg","to":"dnsRecords","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":290,"y":440,"wires":[["84f21f90c7fb7ad9"]]},{"id":"185c0de8add48677","type":"split","z":"b2605c103b3c10bd","g":"66cb617f2a1e06bb","name":"split domains","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":370,"y":580,"wires":[["093ef87d1423fd0b"]]},{"id":"84f21f90c7fb7ad9","type":"change","z":"b2605c103b3c10bd","g":"66cb617f2a1e06bb","name":"Set payload to domains","rules":[{"t":"set","p":"payload","pt":"msg","to":"domains","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":150,"y":580,"wires":[["185c0de8add48677"]]},{"id":"30a90ff9536913ba","type":"function","z":"b2605c103b3c10bd","g":"66cb617f2a1e06bb","name":"Check current registered IP, \\n Prepare request","func":"// save current domain for use later in flow\nmsg.domain = msg.payload\n// find existing record for the domain\nconst record = msg.dnsRecords.find(element => element.name == msg.payload);\nif (record) {\n if (msg.ip != record.content) {\n msg.url = `https://api.cloudflare.com/client/v4/zones/${env.get(\"ZoneID\")}/dns_records/${record.id}`\n msg.headers = {\n \"X-Auth-Email\": env.get(\"Email\"),\n \"X-Auth-Key\": env.get(\"Key\"),\n \"Content-Type\": \"application/json\"\n }\n node.status({ fill: \"green\", text: `Updating ${msg.payload}` });\n msg.payload = { \"type\": \"A\", \"name\": msg.payload, \"content\": msg.ip, \"ttl\": 1, \"proxied\": env.get(\"Proxied\") }\n } else {\n // IP does not need changing\n node.status({ fill: \"green\",text: `${msg.payload} Unchanged` });\n let msg2 = RED.util.cloneMessage(msg);\n msg2.payload = `${msg.payload} Unchanged`\n node.send([null, msg2, null])\n msg = null\n }\n} else {\n let msg2 = RED.util.cloneMessage(msg);\n msg2.payload = `Record for ${msg.payload} not found`\n node.send([null, null, msg2])\n node.status({ fill: \"red\", text: `Record for ${msg.payload} not found` });\n\n msg = null\n}\nreturn msg;","outputs":3,"noerr":0,"initialize":"","finalize":"","libs":[],"x":420,"y":640,"wires":[["60441a47a703c104"],["2ba9a52f9f444496"],["97a03b41453e681a"]]},{"id":"97a03b41453e681a","type":"link out","z":"b2605c103b3c10bd","g":"66cb617f2a1e06bb","name":"link out 3","mode":"link","links":["631528af6996430d"],"x":425,"y":700,"wires":[]},{"id":"60441a47a703c104","type":"http request","z":"b2605c103b3c10bd","g":"66cb617f2a1e06bb","name":"Update record","method":"PUT","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":180,"y":760,"wires":[["6c7e843c8161757e"]]},{"id":"6c7e843c8161757e","type":"switch","z":"b2605c103b3c10bd","g":"66cb617f2a1e06bb","name":"status 200?","property":"statusCode","propertyType":"msg","rules":[{"t":"eq","v":"200","vt":"num"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":390,"y":760,"wires":[["9483a955d6f12538"],["5af9ab018c92680d","05a88e7ff38ea77c"]]},{"id":"5af9ab018c92680d","type":"link out","z":"b2605c103b3c10bd","g":"66cb617f2a1e06bb","name":"link out 4","mode":"link","links":["631528af6996430d"],"x":565,"y":840,"wires":[]},{"id":"093ef87d1423fd0b","type":"delay","z":"b2605c103b3c10bd","g":"66cb617f2a1e06bb","name":"One every two seconds","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"2","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":150,"y":640,"wires":[["30a90ff9536913ba"]]},{"id":"e7d67646cd91b5de","type":"status","z":"b2605c103b3c10bd","name":"","scope":["8fb0166cc4a85bf2","30a90ff9536913ba","9483a955d6f12538"],"x":280,"y":1040,"wires":[[]]},{"id":"2ba9a52f9f444496","type":"join","z":"b2605c103b3c10bd","g":"66cb617f2a1e06bb","name":"","mode":"auto","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":"false","timeout":"","count":"","reduceRight":false,"x":810,"y":740,"wires":[[]]},{"id":"4651b729ca6d1a38","type":"link out","z":"b2605c103b3c10bd","g":"eeb017401050907c","name":"link out 7","mode":"link","links":["f4e2275fe98ba7c4"],"x":535,"y":200,"wires":[]},{"id":"9483a955d6f12538","type":"function","z":"b2605c103b3c10bd","g":"66cb617f2a1e06bb","name":"IP updated","func":"msg.payload = `${msg.domain} Updated`\nnode.status({ fill: \"green\", text: `${msg.domain} updated` });\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":590,"y":740,"wires":[["2ba9a52f9f444496"]]},{"id":"d4a77c1dff11fdd4","type":"change","z":"b2605c103b3c10bd","g":"eeb017401050907c","name":"Error fetching IP address","rules":[{"t":"set","p":"payload","pt":"msg","to":"[\"Error fetching IP address from http://ipinfo.io/ip\"]","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":330,"y":200,"wires":[["4651b729ca6d1a38"]]},{"id":"f4e2275fe98ba7c4","type":"link in","z":"b2605c103b3c10bd","name":"link in 3","links":["4651b729ca6d1a38","da6bc08f85b2b276"],"x":945,"y":660,"wires":[[]]},{"id":"35e5bcc0290e2bf2","type":"change","z":"b2605c103b3c10bd","g":"0d186e43bfe6c8d1","name":"Error fetching DNS records","rules":[{"t":"set","p":"payload","pt":"msg","to":"[\"Error fetching DNS records from cloudflare\"]","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":680,"y":400,"wires":[["da6bc08f85b2b276","0f445f09c61a7be5"]]},{"id":"da6bc08f85b2b276","type":"link out","z":"b2605c103b3c10bd","g":"0d186e43bfe6c8d1","name":"link out 8","mode":"link","links":["f4e2275fe98ba7c4"],"x":905,"y":400,"wires":[]},{"id":"05a88e7ff38ea77c","type":"function","z":"b2605c103b3c10bd","g":"66cb617f2a1e06bb","name":"Error updating domain","func":"msg.payload = `${msg.domain} Error during update`\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":500,"y":900,"wires":[["2ba9a52f9f444496"]]},{"id":"5ca6f91adb8a1873","type":"change","z":"b2605c103b3c10bd","g":"eeb017401050907c","name":"Status: fetching IP","rules":[{"t":"set","p":"status.text","pt":"msg","to":"Fetching current IP address","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":450,"y":120,"wires":[["1ce90f9f100db058"]]},{"id":"1ce90f9f100db058","type":"link out","z":"b2605c103b3c10bd","g":"eeb017401050907c","name":"link out 10","mode":"link","links":["5f0ca8668bb282e0"],"x":575,"y":120,"wires":[]},{"id":"5f0ca8668bb282e0","type":"link in","z":"b2605c103b3c10bd","name":"link in 4","links":["1ce90f9f100db058","d9d6bf93b01ade66"],"x":425,"y":1000,"wires":[[]]},{"id":"d9d6bf93b01ade66","type":"link out","z":"b2605c103b3c10bd","g":"0d186e43bfe6c8d1","name":"link out 11","mode":"link","links":["5f0ca8668bb282e0"],"x":915,"y":460,"wires":[]},{"id":"0f445f09c61a7be5","type":"change","z":"b2605c103b3c10bd","g":"0d186e43bfe6c8d1","name":"Status: Error fetching DNS records","rules":[{"t":"delete","p":"payload","pt":"msg"},{"t":"set","p":"status.text","pt":"msg","to":"Error fetching DNS records","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":720,"y":460,"wires":[["d9d6bf93b01ade66"]]},{"id":"eeb017401050907c","type":"group","z":"b2605c103b3c10bd","name":"Get Current IP Address","style":{"label":true},"nodes":["56c00aa5bbf32b95","27a26faaeaddf108","337770b779737664","41341f10167041c5","b133ab72bfb0f0bc","4651b729ca6d1a38","d4a77c1dff11fdd4","5ca6f91adb8a1873","1ce90f9f100db058"],"x":54,"y":79,"w":852,"h":162},{"id":"0d186e43bfe6c8d1","type":"group","z":"b2605c103b3c10bd","name":"Get DNS records from Cloudflare \\n ","style":{"label":true,"label-position":"n"},"nodes":["8fb0166cc4a85bf2","d002a11e550e568f","b7281df6ef9ea0c4","315f6996631ee864","b2c3b7ed632da273","35e5bcc0290e2bf2","da6bc08f85b2b276","d9d6bf93b01ade66","0f445f09c61a7be5"],"x":14,"y":303,"w":942,"h":198},{"id":"66cb617f2a1e06bb","type":"group","z":"b2605c103b3c10bd","name":"Check/Update records \\n ","style":{"label":true,"label-position":"n"},"nodes":["84f21f90c7fb7ad9","185c0de8add48677","30a90ff9536913ba","97a03b41453e681a","60441a47a703c104","6c7e843c8161757e","5af9ab018c92680d","093ef87d1423fd0b","2ba9a52f9f444496","9483a955d6f12538","05a88e7ff38ea77c"],"x":14,"y":523,"w":872,"h":418},{"id":"e7c7eef22ae43dae","type":"inject","z":"dd2ba972fa7bbf9b","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"10","topic":"","payload":"[\"test.mydomain.com\",\"grafana.mydomain.com\",\"nodered.mydomain.com\"]","payloadType":"json","x":150,"y":400,"wires":[["c6834533eccd6189"]]},{"id":"c6834533eccd6189","type":"subflow:b2605c103b3c10bd","z":"dd2ba972fa7bbf9b","name":"","env":[{"name":"ZoneID","value":"ebbee93a8964cb9b852","type":"str"},{"name":"Email","value":"my_email@mydomain.com","type":"str"},{"name":"Key","type":"cred"}],"x":350,"y":400,"wires":[["62d43b7f7603534f"],["90e7e841f7a5c5f6"]]},{"id":"62d43b7f7603534f","type":"debug","z":"dd2ba972fa7bbf9b","name":"OP","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":570,"y":380,"wires":[]},{"id":"90e7e841f7a5c5f6","type":"debug","z":"dd2ba972fa7bbf9b","name":"ERROR","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":580,"y":440,"wires":[]}]
@kinser06
Copy link

kinser06 commented May 9, 2023

I just tried setting this up and it failed. I think Cloudflare tweaked its API a little. I needed to change your function block prior to the GET and PUT calls to add in a Authorization header and to remove the X-Auth-Key header.

Replace "X-Auth-Key": env.get("Key"), with "Authorization": "Bearer " + env.get("Key"),

Works great, thanks for the sub-flow!

@colinl
Copy link
Author

colinl commented May 9, 2023

Thanks for the info. The original is still working for me, but I now know what to do if it stops.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment