Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 122 additions & 8 deletions cmd/algons/dnsCmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"bufio"
"context"
"fmt"
"io/ioutil"
"net"
"os"
"regexp"
Expand All @@ -40,13 +41,19 @@ var (
recordType string
noPrompt bool
excludePattern string
exportNetwork string
outputFilename string
)

func init() {
dnsCmd.AddCommand(checkCmd)
dnsCmd.AddCommand(addCmd)
dnsCmd.AddCommand(deleteCmd)
dnsCmd.AddCommand(listCmd)
dnsCmd.AddCommand(exportCmd)

listCmd.AddCommand(listRecordsCmd)
listCmd.AddCommand(listZonesCmd)

addCmd.Flags().StringVarP(&addFromName, "from", "f", "", "From name to add new DNS entry")
addCmd.MarkFlagRequired("from")
Expand All @@ -58,9 +65,13 @@ func init() {
deleteCmd.Flags().BoolVarP(&noPrompt, "no-prompt", "y", false, "No prompting for records deletion")
deleteCmd.Flags().StringVarP(&excludePattern, "exclude", "e", "", "name records exclude pattern")

listCmd.Flags().StringVarP(&listNetwork, "network", "n", "", "Domain name for records to list")
listCmd.Flags().StringVarP(&recordType, "recordType", "t", "", "DNS record type to list (A, CNAME, SRV)")
listCmd.MarkFlagRequired("network")
listRecordsCmd.Flags().StringVarP(&listNetwork, "network", "n", "", "Domain name for records to list")
listRecordsCmd.Flags().StringVarP(&recordType, "recordType", "t", "", "DNS record type to list (A, CNAME, SRV)")
listRecordsCmd.MarkFlagRequired("network")

exportCmd.Flags().StringVarP(&exportNetwork, "network", "n", "", "Domain name to export")
exportCmd.MarkFlagRequired("network")
exportCmd.Flags().StringVarP(&outputFilename, "zonefile", "z", "", "Output file for backup ( intead of outputing it to stdout ) ")
}

type byIP []net.IP
Expand All @@ -81,8 +92,17 @@ var dnsCmd = &cobra.Command{

var listCmd = &cobra.Command{
Use: "list",
Short: "List the DNS/SRV entries of the given network",
Long: "List the DNS/SRV entries of the given network",
Short: "List the A/SRV/Zones entries of the given network",
Long: "List the A/SRV/Zones entries of the given network",
Run: func(cmd *cobra.Command, args []string) {
cmd.HelpFunc()(cmd, args)
},
}

var listRecordsCmd = &cobra.Command{
Use: "records",
Short: "List the A/SRV entries of the given network",
Long: "List the A/SRV entries of the given network",
Run: func(cmd *cobra.Command, args []string) {
recordType = strings.ToUpper(recordType)
if recordType == "" || recordType == "A" || recordType == "CNAME" || recordType == "SRV" {
Expand All @@ -94,6 +114,17 @@ var listCmd = &cobra.Command{
},
}

var listZonesCmd = &cobra.Command{
Use: "zones",
Short: "List the zones",
Long: "List the zones",
Run: func(cmd *cobra.Command, args []string) {
if !doListZones() {
os.Exit(1)
}
},
}

var checkCmd = &cobra.Command{
Use: "check",
Short: "Check the status",
Expand Down Expand Up @@ -140,6 +171,16 @@ var deleteCmd = &cobra.Command{
},
}

var exportCmd = &cobra.Command{
Use: "export",
Short: "Export DNS record entries for a specified network",
Run: func(cmd *cobra.Command, args []string) {
if !doExportZone(exportNetwork, outputFilename) {
os.Exit(1)
}
},
}

func doAddDNS(from string, to string) (err error) {
cfZoneID, cfEmail, cfKey, err := getClouldflareCredentials()
if err != nil {
Expand All @@ -166,11 +207,23 @@ func doAddDNS(from string, to string) (err error) {
return
}

func getClouldflareCredentials() (zoneID string, email string, authKey string, err error) {
zoneID = os.Getenv("CLOUDFLARE_ZONE_ID")
func getClouldflareAuthCredentials() (email string, authKey string, err error) {
email = os.Getenv("CLOUDFLARE_EMAIL")
authKey = os.Getenv("CLOUDFLARE_AUTH_KEY")
if zoneID == "" || email == "" || authKey == "" {
if email == "" || authKey == "" {
err = fmt.Errorf("one or more credentials missing from ENV")
}
return
}

func getClouldflareCredentials() (zoneID string, email string, authKey string, err error) {
Comment thread
Karmastic marked this conversation as resolved.
email, authKey, err = getClouldflareAuthCredentials()
if err != nil {
return
}

zoneID = os.Getenv("CLOUDFLARE_ZONE_ID")
if zoneID == "" {
err = fmt.Errorf("one or more credentials missing from ENV")
}
return
Expand Down Expand Up @@ -333,3 +386,64 @@ func listEntries(listNetwork string, recordType string) {
}
}
}

func doExportZone(network string, outputFilename string) bool {
cfEmail, cfKey, err := getClouldflareAuthCredentials()
if err != nil {
fmt.Fprintf(os.Stderr, "error getting DNS credentials: %v", err)
return false
}
cloudflareCred := cloudflare.NewCred(cfEmail, cfKey)
zones, err := cloudflareCred.GetZones(context.Background())
if err != nil {
fmt.Fprintf(os.Stderr, "Error retrieving zones entries: %v\n", err)
return false
}
zoneID := ""
// find a zone that matches the requested network name.
for _, z := range zones {
if z.DomainName == network {
zoneID = z.ZoneID
break
}
fmt.Printf("%s : %s\n", z.DomainName, z.ZoneID)
}
if zoneID == "" {
fmt.Fprintf(os.Stderr, "No matching zoneID was found for %s\n", network)
return false
}
cloudflareDNS := cloudflare.NewDNS(zoneID, cfEmail, cfKey)
exportedZone, err := cloudflareDNS.ExportZone(context.Background())
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to export zone : %v\n", err)
return false
}
if outputFilename != "" {
err = ioutil.WriteFile(outputFilename, exportedZone, 0666)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to write exported zone file : %v\n", err)
return false
}
} else {
fmt.Fprint(os.Stdout, string(exportedZone))
}
return true
}

func doListZones() bool {
cfEmail, cfKey, err := getClouldflareAuthCredentials()
if err != nil {
fmt.Fprintf(os.Stderr, "error getting DNS credentials: %v", err)
return false
}
cloudflareCred := cloudflare.NewCred(cfEmail, cfKey)
zones, err := cloudflareCred.GetZones(context.Background())
if err != nil {
fmt.Fprintf(os.Stderr, "Error listing zones entries: %v\n", err)
return false
}
for _, z := range zones {
fmt.Printf("%s : %s\n", z.DomainName, z.ZoneID)
}
return true
}
84 changes: 78 additions & 6 deletions tools/network/cloudflare/cloudflare.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package cloudflare
import (
"context"
"fmt"
"io/ioutil"
"net/http"
"strings"
)
Expand All @@ -29,19 +30,34 @@ const (
AutomaticTTL = 1
)

// DNS is the cloudflare package main access class. Initiate an instance of this class to access the clouldflare APIs.
type DNS struct {
zoneID string
// Cred contains the credentials used to authenticate with the cloudflare API.
type Cred struct {
authEmail string
authKey string
}

// DNS is the cloudflare package main access class. Initiate an instance of this class to access the clouldflare APIs.
type DNS struct {
zoneID string
Cred
}

// NewCred creates a new credential structure used to authenticate with the cloudflare service.
func NewCred(authEmail string, authKey string) *Cred {
return &Cred{
authEmail: authEmail,
authKey: authKey,
}
}

// NewDNS create a new instance of clouldflare DNS services class
func NewDNS(zoneID string, authEmail string, authKey string) *DNS {
return &DNS{
zoneID: zoneID,
authEmail: authEmail,
authKey: authKey,
zoneID: zoneID,
Cred: Cred{
authEmail: authEmail,
authKey: authKey,
},
}
}

Expand Down Expand Up @@ -241,3 +257,59 @@ func (d *DNS) UpdateSRVRecord(ctx context.Context, recordID string, name string,
}
return nil
}

// Zone represent a single zone on the cloudflare API.
type Zone struct {
DomainName string
ZoneID string
}

// GetZones returns a list of zones that are associated with cloudflare.
func (c *Cred) GetZones(ctx context.Context) (zones []Zone, err error) {
request, err := getZonesRequest(c.authEmail, c.authKey)
if err != nil {
return nil, err
}
client := &http.Client{}
response, err := client.Do(request.WithContext(ctx))
if err != nil {
return nil, err
}

parsedResponse, err := parseGetZonesResponse(response)
if err != nil {
return nil, err
}
if parsedResponse.Success == false {
return nil, fmt.Errorf("failed to retrieve zone records : %v", parsedResponse)
}

for _, z := range parsedResponse.Result {
zones = append(zones,
Zone{
DomainName: z.Name,
ZoneID: z.ID,
},
)
}
return zones, err
}

// ExportZone exports the zone into a BIND config bytes array
func (d *DNS) ExportZone(ctx context.Context) (exportedZoneBytes []byte, err error) {
request, err := exportZoneRequest(d.zoneID, d.authEmail, d.authKey)
if err != nil {
return nil, err
}
client := &http.Client{}
response, err := client.Do(request.WithContext(ctx))
if err != nil {
return nil, err
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
return body, nil
}
97 changes: 97 additions & 0 deletions tools/network/cloudflare/zones.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright (C) 2019 Algorand, Inc.
// This file is part of go-algorand
//
// go-algorand is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// go-algorand is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand. If not, see <https://www.gnu.org/licenses/>.

package cloudflare

import (
"encoding/json"
"io/ioutil"
"net/http"
"net/url"
)

func getZonesRequest(authEmail, authKey string) (*http.Request, error) {
// construct the query
requestURI, err := url.Parse(cloudFlareURI)
if err != nil {
return nil, err
}
requestURI.Path = requestURI.Path + "zones"
request, err := http.NewRequest("GET", requestURI.String(), nil)
if err != nil {
return nil, err
}
addHeaders(request, authEmail, authKey)
return request, nil
}

// GetZonesResult is the JSON response for a DNS create request
type GetZonesResult struct {
Success bool `json:"success"`
Errors []interface{} `json:"errors"`
Messages []interface{} `json:"messages"`
Result []GetZonesResultItem `json:"result"`
ResultInfo GetZonesResultPage `json:"result_info"`
}

// GetZonesResultPage is the result of the response for the DNS create request
type GetZonesResultPage struct {
Page int `json:"page"`
PerPage int `json:"per_page"`
TotalPages int `json:"total_pages"`
Count int `json:"count"`
TotalCount int `json:"total_count"`
}

// GetZonesResultItem is the result of the response for the DNS create request
type GetZonesResultItem struct {
ID string `json:"id"`
Name string `json:"name"`
Status string `json:"status"`
Paused bool `json:"paused"`
Type string `json:"type"`
DevelopmentMode int `json:"development_mode"`
NameServers []string `json:"name_servers"`
OriginalNameServers []string `json:"original_name_servers"`
}

func parseGetZonesResponse(response *http.Response) (*GetZonesResult, error) {
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
var parsedReponse GetZonesResult
if err := json.Unmarshal(body, &parsedReponse); err != nil {
return nil, err
}
return &parsedReponse, nil
}

func exportZoneRequest(zoneID, authEmail, authKey string) (*http.Request, error) {
// construct the query
requestURI, err := url.Parse(cloudFlareURI)
if err != nil {
return nil, err
}
requestURI.Path = requestURI.Path + "zones/" + zoneID + "/dns_records/export"
request, err := http.NewRequest("GET", requestURI.String(), nil)
if err != nil {
return nil, err
}
addHeaders(request, authEmail, authKey)
return request, nil
}