aboutsummaryrefslogtreecommitdiff
path: root/cmd/dnsupdate
diff options
context:
space:
mode:
authorFranck Cuny <franck@fcuny.net>2022-05-25 19:39:32 -0700
committerFranck Cuny <franck@fcuny.net>2022-05-25 19:39:32 -0700
commita4c74ea0decb9c6e04b9abb5124b81438940d07f (patch)
tree1fc0893b7685b42363168ebcdcce0ba04760222f /cmd/dnsupdate
parentref(notes): drop flake.nix (diff)
downloadinfra-a4c74ea0decb9c6e04b9abb5124b81438940d07f.tar.gz
ref(dnsupdate): move under tools
Integrate properly the tool `dnsupdate` with flake.nix, by adding a default.nix inside its directory. Having all the tools under a directory named `tools` is easier to reason about. I don't need a go.mod at the top level directory either, each tool will have its own.
Diffstat (limited to 'cmd/dnsupdate')
-rw-r--r--cmd/dnsupdate/README.org7
-rw-r--r--cmd/dnsupdate/main.go126
-rw-r--r--cmd/dnsupdate/ts.go89
3 files changed, 0 insertions, 222 deletions
diff --git a/cmd/dnsupdate/README.org b/cmd/dnsupdate/README.org
deleted file mode 100644
index a80e407..0000000
--- a/cmd/dnsupdate/README.org
+++ /dev/null
@@ -1,7 +0,0 @@
-#+TITLE: dnsupdate
-
-Utility to update the managed zone for =fcuny.xyz= in Google Cloud.
-
-I use the domain =fcuny.xyz= to run a number of services on an IP provided by Tailscale. I don't want these domains to be visible on the web, but I also want to have a valid HTTPS certificate for them. By having a proper DNS I can use ACME to get the certificates, without making them available.
-
-Instead of updating the subdomains through the [[https://console.cloud.google.com/net-services/dns/zones/fcuny-xyz/details?project=fcuny-homelab][console]], I can now run this program.
diff --git a/cmd/dnsupdate/main.go b/cmd/dnsupdate/main.go
deleted file mode 100644
index 6748ee9..0000000
--- a/cmd/dnsupdate/main.go
+++ /dev/null
@@ -1,126 +0,0 @@
-package main
-
-import (
- "context"
- "fmt"
- "log"
-
- dns "google.golang.org/api/dns/v1"
-)
-
-const (
- GCP_PROJECT_NAME = "fcuny-homelab"
- GCP_MANAGED_ZONE = "fcuny-xyz"
- TS_DEVICE_NAME = "tahoe"
- TTL = 300
-)
-
-var desiredRecords = []string{
- "bt",
- "cs",
- "dash",
- "drone",
- "music",
- "unifi",
-}
-
-func main() {
- ctx := context.Background()
-
- // we only care about IPv4 for now
- tsIpV4Addresses, _, err := getTsIpsDevice(ctx, TS_DEVICE_NAME)
- if err != nil {
- log.Fatalf("failed to get the IP addresses for %s: %v", TS_DEVICE_NAME, err)
- }
-
- svc, err := dns.NewService(ctx)
- if err != nil {
- log.Fatalf("failed to create the client for Google Cloud DNS: %v", err)
- }
-
- zone, err := svc.ManagedZones.Get(GCP_PROJECT_NAME, GCP_MANAGED_ZONE).Context(ctx).Do()
- if err != nil {
- log.Fatalf("failed to get information about the managed zone %s: %+v", GCP_MANAGED_ZONE, err)
- }
-
- recordSets, err := svc.ResourceRecordSets.List(GCP_PROJECT_NAME, GCP_MANAGED_ZONE).Context(ctx).Do()
- if err != nil {
- log.Fatalf("failed to get the list of records: %+v", err)
- }
-
- var (
- existingRecordSets = []*dns.ResourceRecordSet{}
- recordSetsToAdd = []*dns.ResourceRecordSet{}
- recordSetsToDelete = []*dns.ResourceRecordSet{}
- )
-
- for _, record := range recordSets.Rrsets {
- if record.Type == "A" {
- existingRecordSets = append(existingRecordSets, record)
- }
- }
-
- // first pass: create what's missing
- for _, subdomain := range desiredRecords {
- found := false
- subdomain = fmt.Sprintf("%s.%s", subdomain, zone.DnsName)
- for _, r := range existingRecordSets {
- if subdomain == r.Name && r.Type == "A" {
- // check that the IP addresses are correct
- ipsFound := 0
- for _, rr := range r.Rrdatas {
- for _, ip := range tsIpV4Addresses {
- if rr == ip {
- ipsFound += 1
- continue
- }
- }
- }
- // while we found the subdomain with the correct type,
- // we also need to make sure the list of IPs is
- // correct. If they are not, we delete the record and
- // add it again with the correct values.
- if ipsFound == len(tsIpV4Addresses) {
- found = true
- continue
- } else {
- log.Printf("will delete %s (incorrect IPv4 addresses)\n", subdomain)
- recordSetsToDelete = append(recordSetsToDelete, r)
- }
- }
- }
- if !found {
- log.Printf("will add %s\n", subdomain)
- r := &dns.ResourceRecordSet{
- Name: subdomain,
- Type: "A",
- Ttl: TTL,
- Rrdatas: tsIpV4Addresses,
- }
- recordSetsToAdd = append(recordSetsToAdd, r)
- }
- }
-
- // second pass: delete what's not needed
- for _, r := range existingRecordSets {
- found := false
- for _, subdomain := range desiredRecords {
- subdomain = fmt.Sprintf("%s.%s", subdomain, zone.DnsName)
- if subdomain == r.Name && r.Type == "A" {
- found = true
- continue
- }
- }
- if !found {
- log.Printf("will delete %s\n", r.Name)
- recordSetsToDelete = append(recordSetsToDelete, r)
- }
- }
-
- if len(recordSetsToAdd) > 0 || len(recordSetsToDelete) > 0 {
- change := &dns.Change{Additions: recordSetsToAdd, Deletions: recordSetsToDelete}
- if _, err = svc.Changes.Create(GCP_PROJECT_NAME, GCP_MANAGED_ZONE, change).Context(ctx).Do(); err != nil {
- log.Fatalf("failed to apply the change: %+v", err)
- }
- }
-}
diff --git a/cmd/dnsupdate/ts.go b/cmd/dnsupdate/ts.go
deleted file mode 100644
index 4d3ebb3..0000000
--- a/cmd/dnsupdate/ts.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package main
-
-import (
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "net/http"
- "os"
-
- "inet.af/netaddr"
-)
-
-type device struct {
- Hostname string `json:"hostname"`
- ID string `json:"id"`
- Addresses []string `json:"addresses"`
-}
-
-const (
- TS_NAME = "franck.cuny@gmail.com"
- TS_API_DOMAIN = "api.tailscale.com"
-)
-
-func getTsDevice(ctx context.Context, deviceName string) (*device, error) {
- apiKey, found := os.LookupEnv("TS_API_KEY")
- if !found {
- return nil, errors.New("the environment variable TS_API_KEY is not set")
- }
-
- url := fmt.Sprintf("https://%s/api/v2/tailnet/%s/devices", TS_API_DOMAIN, TS_NAME)
- req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
- if err != nil {
- return nil, err
- }
-
- req.SetBasicAuth(apiKey, "")
- resp, err := http.DefaultClient.Do(req)
- if err != nil {
- return nil, err
- }
-
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return nil, fmt.Errorf("non-ok status code %d returned from tailscale api: %s", resp.StatusCode, resp.Status)
- }
- var buf struct {
- Devices []device `json:"devices"`
- }
- if err := json.NewDecoder(resp.Body).Decode(&buf); err != nil {
- return nil, err
- }
-
- for _, d := range buf.Devices {
- if d.Hostname == deviceName {
- return &d, nil
- }
- }
- return nil, fmt.Errorf("could not find the tailscale device named %s", deviceName)
-}
-
-// Get the Tailscale IPv4 and IPv6 addresses associated with the given device.
-func getTsIpsDevice(ctx context.Context, device string) ([]string, []string, error) {
- ts_device, err := getTsDevice(ctx, device)
- if err != nil {
- return nil, nil, fmt.Errorf("failed to get Tailscale device information: %v", err)
- }
-
- var (
- tsIpV4Addresses = []string{}
- tsIpV6Addresses = []string{}
- )
- for _, ipString := range ts_device.Addresses {
- // we convert the string to a netaddr.IP so we can check if
- // it's an IP v4 or v6. We need to know what's the version in
- // order to use it properly when creating/updating the
- // record. Then we convert it back as a string, since this is
- // what the DNS API expect.
- ip := netaddr.MustParseIP(ipString)
- if ip.Is4() {
- tsIpV4Addresses = append(tsIpV4Addresses, ip.String())
- } else {
- tsIpV6Addresses = append(tsIpV6Addresses, ip.String())
- }
- }
-
- return tsIpV4Addresses, tsIpV6Addresses, nil
-}