From ec5cbbe203596327d42ac626dcc9c55c604b9139 Mon Sep 17 00:00:00 2001 From: Jeff Carr Date: Fri, 29 Dec 2023 21:14:36 -0600 Subject: [PATCH] added a real IPv6 record first time deleting cloudflare AAAA record can find wit.com on cloudflare ready to add cloudflare.Delete() remove code since --gui-debug works Signed-off-by: Jeff Carr --- Makefile | 2 +- cloudflare/api.go | 4 +- cloudflare/create.go | 57 +++++++++++++++++++++++ cloudflare/delete.go | 64 ++++++++++++++++++++++++++ cloudflare/http.go | 85 +++++++++++++++++++++++++++++++++- gui.go | 11 ----- hostnameStatus.go | 107 +++++++++++++++++++++++++++++++++++++++---- 7 files changed, 306 insertions(+), 24 deletions(-) create mode 100644 cloudflare/create.go create mode 100644 cloudflare/delete.go diff --git a/Makefile b/Makefile index 4d3db8e..40a1d91 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ run: build # ./control-panel-dns >/tmp/witgui.log.stderr 2>&1 - ./control-panel-dns + ./control-panel-dns --gui-debug install: go install -v go.wit.com/control-panel-dns@latest diff --git a/cloudflare/api.go b/cloudflare/api.go index 293857f..41ddae6 100644 --- a/cloudflare/api.go +++ b/cloudflare/api.go @@ -99,7 +99,7 @@ func SetRow(dnsRow *RRT) { } func GetZonefile(c *ConfigT) *DNSRecords { - var url = cloudflareURL + c.ZoneID + "/dns_records/" + var url = cloudflareURL + c.ZoneID + "/dns_records/?per_page=100" log.Println("getZonefile()", c.Domain, url) req, err := http.NewRequest("GET", url, nil) if err != nil { @@ -162,7 +162,7 @@ func makeJSON(dnsRow *RRT) string { // https://api.cloudflare.com/client/v4/zones func GetZones(auth, email string) *DNSRecords { - var url = "https://api.cloudflare.com/client/v4/zones" + var url = "https://api.cloudflare.com/client/v4/zones?per_page=100" log.Println("getZones()", url) req, err := http.NewRequest("GET", url, nil) if err != nil { diff --git a/cloudflare/create.go b/cloudflare/create.go new file mode 100644 index 0000000..5d75b0c --- /dev/null +++ b/cloudflare/create.go @@ -0,0 +1,57 @@ +/* + This will attempt to create a RR in a DNS zone file. + + Create("wit.com", "test.wit.com", "1.1.1.1" +*/ + +package cloudflare + +import ( + "os" + + "go.wit.com/log" +) + +func Create(zone string, hostname string, value string) bool { + log.Info("cloudflare.Create() START", zone, hostname, value) + key := os.Getenv("CF_API_KEY") + email := os.Getenv("CF_API_EMAIL") + + if (key == "") { + log.Warn("cloudflare.Create() MISSING environment variable CF_API_KEY") + return false + } + if (email == "") { + log.Warn("cloudflare.Create() MISSING environment variable CF_API_EMAIL") + return false + } + + GetZones(key, email) + var z *ConfigT + for d, v := range Config { + log.Info("cloudflare.Create() zone =", d, "value =", v) + if (zone == d) { + z = Config[zone] + log.Info("cloudflare.Create() FOUND ZONE", zone, "ID =", z.ZoneID) + } + } + if (z == nil) { + log.Warn("cloudflare.Create() COULD NOT FIND ZONE", zone) + return false + } + log.Info("cloudflare.Create() FOUND ZONE", z) + + // make a json record to send on port 80 to cloudflare + var data string + data = `{"content": "` + value + `", ` + data += `"name": "` + hostname + `", ` + data += `"type": "AAAA", ` + data += `"ttl": "1", ` + data += `"comment": "WIT DNS Control Panel"` + data += `}` + + result := doCurlCreate(key, email, z.ZoneID, data) + pretty, _ := FormatJSON(result) + log.Info("cloudflare.Create() result =", pretty) + return true +} diff --git a/cloudflare/delete.go b/cloudflare/delete.go new file mode 100644 index 0000000..aa59105 --- /dev/null +++ b/cloudflare/delete.go @@ -0,0 +1,64 @@ +/* + This will attempt to delete a RR in a DNS zone file. + + Delete("wit.com", "test.wit.com", "1.1.1.1" +*/ + +package cloudflare + +import ( + "os" + + "go.wit.com/log" +) + +func Delete(zone string, hostname string, value string) bool { + // CFdialog.emailNode.SetText(os.Getenv("CF_API_EMAIL")) + // CFdialog.apiNode.SetText(os.Getenv("CF_API_KEY")) + + log.Info("cloudflare.Delete() START", zone, hostname, value) + key := os.Getenv("CF_API_KEY") + email := os.Getenv("CF_API_EMAIL") + + if (key == "") { + log.Warn("cloudflare.Delete() MISSING environment variable CF_API_KEY") + return false + } + if (email == "") { + log.Warn("cloudflare.Delete() MISSING environment variable CF_API_EMAIL") + return false + } + + GetZones(key, email) + var z *ConfigT + for d, v := range Config { + log.Info("cloudflare.Delete() zone =", d, "value =", v) + if (zone == d) { + z = Config[zone] + log.Info("cloudflare.Delete() FOUND ZONE", zone, "ID =", z.ZoneID) + } + } + if (z == nil) { + log.Warn("cloudflare.Delete() COULD NOT FIND ZONE", zone) + return false + } + log.Info("cloudflare.Delete() FOUND ZONE", z) + + records := GetZonefile(z) + for i, record := range records.Result { + if (record.Name == hostname) { + log.Info("cloudflare.Delete() FOUND hostname:", i, record.ID, record.Type, record.Name, record.Content) + } + if (record.Content == value) { + log.Info("cloudflare.Delete() FOUND CONTENT:", i, record.ID, record.Type, record.Name, record.Content) + log.Info("cloudflare.Delete() DO THE ACTUAL cloudflare DELETE here") + result := doCurlDelete(key, email, z.ZoneID, record.ID) + pretty, _ := FormatJSON(result) + log.Info("cloudflare.Delete() result =", pretty) + return true + } + } + + log.Info("cloudflare.Delete() NEVER FOUND cloudflare value:", value) + return false +} diff --git a/cloudflare/http.go b/cloudflare/http.go index 1917c8b..f976b96 100644 --- a/cloudflare/http.go +++ b/cloudflare/http.go @@ -2,10 +2,11 @@ package cloudflare import ( - "log" "io/ioutil" "net/http" "bytes" + + "go.wit.com/log" ) /* @@ -26,6 +27,88 @@ curl --request POST \ }' */ +func doCurlDelete(auth string, email string, zoneId string, rrId string) string { + var err error + var req *http.Request + + if zoneId == "" { + log.Warn("doCurlDelete() zoneId == nil") + return "" + } + + if rrId == "" { + log.Warn("doCurlDelete() rrId == nil") + return "" + } + + data := []byte("") + + url := "https://api.cloudflare.com/client/v4/zones/" + zoneId + "/dns_records/" + rrId + + req, err = http.NewRequest(http.MethodDelete, url, bytes.NewBuffer(data)) + + // Set headers + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Auth-Key", auth) + req.Header.Set("X-Auth-Email", email) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Println(err) + return "" + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Println(err) + return "" + } + + return string(body) +} + +func doCurlCreate(auth string, email string, zoneId string, data string) string { + var err error + var req *http.Request + + if zoneId == "" { + log.Warn("doCurlDelete() zoneId == nil") + return "" + } + + url := "https://api.cloudflare.com/client/v4/zones/" + zoneId + "/dns_records/" + + log.Info("doCurlCreate() POST url =", url) + log.Info("doCurlCreate() POST Auth =", auth) + log.Info("doCurlCreate() POST Email =", email) + log.Info("doCurlCreate() POST data =", data) + + req, err = http.NewRequest(http.MethodPost, url, bytes.NewBuffer( []byte(data) )) + + // Set headers + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Auth-Key", auth) + req.Header.Set("X-Auth-Email", email) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Println(err) + return "" + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Println(err) + return "" + } + + return string(body) +} + func doCurl(method string, rr *RRT) string { var err error var req *http.Request diff --git a/gui.go b/gui.go index abc0485..18ab3dc 100644 --- a/gui.go +++ b/gui.go @@ -2,7 +2,6 @@ package main import ( - "fmt" "time" "os" "os/user" @@ -85,16 +84,6 @@ func debugTab(title string) { g2 = tab.NewGroup("Real Stuff") - g2.NewButton("gui.DebugWindow()", func () { - gui.DebugWindow() - }) - - g2.NewButton("Load 'gocui'", func () { - // this set the xterm and mate-terminal window title. maybe works generally? - fmt.Println("\033]0;" + title + "blah \007") - myGui.LoadToolkit("gocui") - }) - g2.NewButton("Network Interfaces", func () { for i, t := range me.ifmap { log.Println("name =", t.iface.Name) diff --git a/hostnameStatus.go b/hostnameStatus.go index 8afa9a2..f8ada99 100644 --- a/hostnameStatus.go +++ b/hostnameStatus.go @@ -15,6 +15,7 @@ import ( "go.wit.com/log" "go.wit.com/gui" "go.wit.com/gui/gadgets" + "go.wit.com/control-panel-dns/cloudflare" ) type hostnameStatus struct { @@ -25,21 +26,31 @@ type hostnameStatus struct { window *gui.Node + // Primary Directives status *gadgets.OneLiner summary *gadgets.OneLiner - speed *gadgets.OneLiner - speedActual *gadgets.OneLiner - - dnsA *gadgets.OneLiner - dnsAAAA *gadgets.OneLiner - dnsAPI *gadgets.OneLiner - statusIPv4 *gadgets.OneLiner statusIPv6 *gadgets.OneLiner // Details Group + hostShort *gadgets.OneLiner + domainname *gadgets.OneLiner + + // what the current IP address your network has given you currentIPv4 *gadgets.OneLiner currentIPv6 *gadgets.OneLiner + + // what the DNS servers have + dnsA *gadgets.OneLiner + dnsAAAA *gadgets.OneLiner + dnsAPI *gadgets.OneLiner + + speed *gadgets.OneLiner + speedActual *gadgets.OneLiner + + // Actions + dnsValue *gui.Node + dnsAction *gui.Node } func NewHostnameStatusWindow(p *gui.Node) *hostnameStatus { @@ -69,23 +80,79 @@ func NewHostnameStatusWindow(p *gui.Node) *hostnameStatus { group = box.NewGroup("Details") grid = group.NewGrid("LookupDetails", 2, 2) + hs.hostShort = gadgets.NewOneLiner(grid, "hostname -s") + hs.domainname = gadgets.NewOneLiner(grid, "domain name") hs.currentIPv4 = gadgets.NewOneLiner(grid, "Current IPv4") hs.currentIPv6 = gadgets.NewOneLiner(grid, "Current IPv6") hs.dnsAPI = gadgets.NewOneLiner(grid, "dns API provider").Set("unknown") hs.dnsA = gadgets.NewOneLiner(grid, "dns IPv4 resource records").Set("unknown") hs.dnsAAAA = gadgets.NewOneLiner(grid, "dns IPv6 resource records").Set("unknown") + hs.speed = gadgets.NewOneLiner(grid, "speed").Set("unknown") hs.speedActual = gadgets.NewOneLiner(grid, "actual").Set("unknown") group.Pad() grid.Pad() + group = box.NewGroup("Actions") + grid = group.NewGrid("LookupDetails", 2, 2) + + hs.dnsValue = grid.NewLabel("3.4.5.6") + hs.dnsAction = grid.NewButton("CHECK", func () { + log.Info("should", hs.dnsAction.S, "here for", hs.dnsValue.S) + if (hs.dnsAction.S == "DELETE") { + hs.deleteDNSrecord(hs.dnsValue.S) + } + if (hs.dnsAction.S == "CREATE") { + hs.createDNSrecord(hs.dnsValue.S) + } + }) + + group.Pad() + grid.Pad() + hs.hidden = false hs.ready = true return hs } +func (hs *hostnameStatus) Domain() string { + if ! hs.Ready() {return ""} + return hs.domainname.Get() +} + +func (hs *hostnameStatus) API() string { + if ! hs.Ready() {return ""} + return hs.dnsAPI.Get() +} + +func (hs *hostnameStatus) deleteDNSrecord(value string) bool { + log.Info("deleteDNSrecord() START for", value) + log.Info("deleteDNSrecord() hostname =", me.hostname) + log.Info("deleteDNSrecord() domain =", hs.Domain()) + log.Info("deleteDNSrecord() DNS API Provider =", hs.API()) + + if (hs.API() == "cloudflare") { + log.Info("deleteDNSrecord() Try to delete via cloudflare") + return cloudflare.Delete(hs.Domain(), me.hostname, value) + } + return false +} + +func (hs *hostnameStatus) createDNSrecord(value string) bool { + log.Info("createDNSrecord() START for", value) + log.Info("createDNSrecord() hostname =", me.hostname) + log.Info("createDNSrecord() domain =", hs.Domain()) + log.Info("createDNSrecord() DNS API Provider =", hs.API()) + + if (hs.API() == "cloudflare") { + log.Info("createDNSrecord() Try to delete via cloudflare") + return cloudflare.Create(hs.Domain(), me.hostname, value) + } + return false +} + func (hs *hostnameStatus) Update() { log.Info("hostnameStatus() Update() START") if hs == nil { @@ -174,23 +241,45 @@ func (hs *hostnameStatus) set(a any, s string) { os.Exit(0) } +// figure out if I'm missing any IPv6 address in DNS +func (hs *hostnameStatus) missingAAAA() bool { + var aaaa []string + aaaa = dhcpAAAA() + for _, s := range aaaa { + debug(LogNet, "my actual AAAA = ",s) + hs.dnsValue.SetText(s) + hs.dnsAction.SetText("CREATE") + return true + } + + return false +} + func (hs *hostnameStatus) updateStatus() { var s string var vals []string log.Info("updateStatus() START") if ! hs.Ready() { return } + hs.hostShort.Set(me.hostshort.S) + hs.domainname.Set(me.domainname.S) + vals = lookupDoH(hs.hostname, "AAAA") - log.Println("IPv6 Addresses for ", hs.hostname, "=", vals) + log.Println("DNS IPv6 Addresses for ", hs.hostname, "=", vals) if len(vals) == 0 { s = "(none)" - hs.setIPv6("NEED VPN") + hs.setIPv6("Check for real IPv6 addresses here") + if hs.missingAAAA() { + hs.setIPv6("Add the missing IPv6 address") + } } else { for _, addr := range vals { log.Println(addr) s += addr + " (DELETE)" hs.setIPv6("NEEDS DELETE") + hs.dnsValue.SetText(addr) + hs.dnsAction.SetText("DELETE") } } hs.set(hs.dnsAAAA, s)