diff options
Diffstat (limited to 'cmd/dnsupdate')
-rw-r--r-- | cmd/dnsupdate/README.org | 7 | ||||
-rw-r--r-- | cmd/dnsupdate/main.go | 126 | ||||
-rw-r--r-- | cmd/dnsupdate/ts.go | 89 |
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 -} |