mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
There's a philosophy issue here around what is the Bind output meant to do. Since AFAIK we're not integrating into Bind's catalog zones or the like, we're just targeting the zonefiles, we're not in a position to do _anything_ relating to registrar options such as setting up DS glue. So at one level, enabling AutoDNSSEC for Bind is a lie. But without this, folks can't target a Bind zone as a secondary provider for their domain, to get debug dumps of the zone output, because the checks for "Can" block it. So I think this commit achieves a happy compromise: we write a comment into the Bind zonefile, indicating that DNSSEC was requested. Actually: we add support for arbitrary zone comments to be written into a zonefile via a slightly ugly "can be `nil`" parameter. We then write in a generation timestamp comment, and if AutoDNSSEC was requested we then write that in too.
211 lines
6.0 KiB
Go
211 lines
6.0 KiB
Go
package commands
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/StackExchange/dnscontrol/v2/models"
|
|
"github.com/StackExchange/dnscontrol/v2/pkg/prettyzone"
|
|
"github.com/StackExchange/dnscontrol/v2/providers"
|
|
"github.com/StackExchange/dnscontrol/v2/providers/config"
|
|
"github.com/urfave/cli/v2"
|
|
)
|
|
|
|
var _ = cmd(catUtils, func() *cli.Command {
|
|
var args GetZoneArgs
|
|
return &cli.Command{
|
|
Name: "get-zones",
|
|
Aliases: []string{"get-zone"},
|
|
Usage: "gets a zone from a provider (stand-alone)",
|
|
Action: func(ctx *cli.Context) error {
|
|
if ctx.NArg() < 3 {
|
|
return cli.NewExitError("Arguments should be: credskey providername zone(s) (Ex: r53 ROUTE53 example.com)", 1)
|
|
|
|
}
|
|
args.CredName = ctx.Args().Get(0)
|
|
args.ProviderName = ctx.Args().Get(1)
|
|
args.ZoneNames = ctx.Args().Slice()[2:]
|
|
return exit(GetZone(args))
|
|
},
|
|
Flags: args.flags(),
|
|
UsageText: "dnscontrol get-zones [command options] credkey provider zone [...]",
|
|
Description: `Download a zone from a provider. This is a stand-alone utility.
|
|
|
|
ARGUMENTS:
|
|
credkey: The name used in creds.json (first parameter to NewDnsProvider() in dnsconfig.js)
|
|
provider: The name of the provider (second parameter to NewDnsProvider() in dnsconfig.js)
|
|
zone: One or more zones (domains) to download; or "all".
|
|
|
|
FORMATS:
|
|
--format=dsl dnsconfig.js format (not perfect, but a decent first draft)
|
|
--format=nameonly Just print the zone names
|
|
--format=pretty BIND Zonefile format
|
|
--format=tsv TAB separated value (useful for AWK)
|
|
|
|
EXAMPLES:
|
|
dnscontrol get-zones myr53 ROUTE53 example.com
|
|
dnscontrol get-zones gmain GANDI_V5 example.comn other.com
|
|
dnscontrol get-zones cfmain CLOUDFLAREAPI all
|
|
dnscontrol get-zones -format=tsv bind BIND example.com
|
|
dnscontrol get-zones -format=dsl -out=draft.js glcoud GCLOUD example.com`,
|
|
}
|
|
}())
|
|
|
|
// GetZoneArgs args required for the create-domain subcommand.
|
|
type GetZoneArgs struct {
|
|
GetCredentialsArgs // Args related to creds.json
|
|
CredName string // key in creds.json
|
|
ProviderName string // provider name: BIND, GANDI_V5, etc or "-"
|
|
ZoneNames []string // The zones to get
|
|
OutputFormat string // Output format
|
|
OutputFile string // Filename to send output ("" means stdout)
|
|
DefaultTTL int // default TTL for providers where it is unknown
|
|
}
|
|
|
|
func (args *GetZoneArgs) flags() []cli.Flag {
|
|
flags := args.GetCredentialsArgs.flags()
|
|
flags = append(flags, &cli.StringFlag{
|
|
Name: "format",
|
|
Destination: &args.OutputFormat,
|
|
Value: "pretty",
|
|
Usage: `Output format: dsl pretty tsv nameonly`,
|
|
})
|
|
flags = append(flags, &cli.StringFlag{
|
|
Name: "out",
|
|
Destination: &args.OutputFile,
|
|
Usage: `Instead of stdout, write to this file`,
|
|
})
|
|
flags = append(flags, &cli.IntFlag{
|
|
Name: "ttl",
|
|
Destination: &args.DefaultTTL,
|
|
Usage: `Default TTL`,
|
|
Value: 300,
|
|
})
|
|
return flags
|
|
}
|
|
|
|
// GetZone contains all data/flags needed to run get-zones, independently of CLI.
|
|
func GetZone(args GetZoneArgs) error {
|
|
var providerConfigs map[string]map[string]string
|
|
var err error
|
|
|
|
// Read it in:
|
|
providerConfigs, err = config.LoadProviderConfigs(args.CredsFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
provider, err := providers.CreateDNSProvider(args.ProviderName, providerConfigs[args.CredName], nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// decide which zones we need to convert
|
|
zones := args.ZoneNames
|
|
if len(args.ZoneNames) == 1 && args.ZoneNames[0] == "all" {
|
|
lister, ok := provider.(providers.ZoneLister)
|
|
if !ok {
|
|
return fmt.Errorf("provider type %s cannot list zones to use the 'all' feature", args.ProviderName)
|
|
}
|
|
zones, err = lister.ListZones()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// first open output stream and print initial header (if applicable)
|
|
w := os.Stdout
|
|
if args.OutputFile != "" {
|
|
w, err = os.Create(args.OutputFile)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer w.Close()
|
|
|
|
if args.OutputFormat == "nameonly" {
|
|
for _, zone := range zones {
|
|
fmt.Fprintln(w, zone)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// actually fetch all of the records
|
|
zoneRecs := make([]models.Records, len(zones))
|
|
for i, zone := range zones {
|
|
recs, err := provider.GetZoneRecords(zone)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
zoneRecs[i] = recs
|
|
}
|
|
|
|
// Write it out:
|
|
|
|
if args.OutputFormat == "dsl" {
|
|
fmt.Fprintf(w, `var %s = NewDnsProvider("%s", "%s");`+"\n",
|
|
args.CredName, args.CredName, args.ProviderName)
|
|
}
|
|
|
|
// now print all zones
|
|
for i, recs := range zoneRecs {
|
|
zoneName := zones[i]
|
|
|
|
z := prettyzone.PrettySort(recs, zoneName, 0, nil)
|
|
switch args.OutputFormat {
|
|
case "pretty":
|
|
fmt.Fprintf(w, "$ORIGIN %s.\n", zoneName)
|
|
prettyzone.WriteZoneFileRC(w, z.Records, zoneName, nil)
|
|
fmt.Fprintln(w)
|
|
case "dsl":
|
|
|
|
fmt.Fprintf(w, `D("%s", REG_CHANGEME,`+"\n", zoneName)
|
|
fmt.Fprintf(w, "\tDnsProvider(%s)", args.CredName)
|
|
for _, rec := range recs {
|
|
fmt.Fprint(w, formatDsl(zoneName, rec, uint32(args.DefaultTTL)))
|
|
}
|
|
fmt.Fprint(w, "\n)\n")
|
|
case "tsv":
|
|
for _, rec := range recs {
|
|
fmt.Fprintf(w,
|
|
fmt.Sprintf("%s\t%s\t%d\tIN\t%s\t%s\n",
|
|
rec.NameFQDN, rec.Name, rec.TTL, rec.Type, rec.GetTargetCombined()))
|
|
}
|
|
default:
|
|
return fmt.Errorf("format %q unknown", args.OutputFile)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func formatDsl(zonename string, rec *models.RecordConfig, defaultTTL uint32) string {
|
|
|
|
target := rec.GetTargetCombined()
|
|
|
|
ttlop := ""
|
|
if rec.TTL != defaultTTL && rec.TTL != 0 {
|
|
ttlop = fmt.Sprintf(", TTL(%d)", rec.TTL)
|
|
}
|
|
|
|
switch rec.Type { // #rtype_variations
|
|
case "MX":
|
|
target = fmt.Sprintf("%d, '%s'", rec.MxPreference, rec.GetTargetField())
|
|
case "SOA":
|
|
case "TXT":
|
|
if len(rec.TxtStrings) == 1 {
|
|
target = `'` + rec.TxtStrings[0] + `'`
|
|
} else {
|
|
target = `['` + strings.Join(rec.TxtStrings, `', '`) + `']`
|
|
}
|
|
case "NS":
|
|
// NS records at the apex should be NAMESERVER() records.
|
|
if rec.Name == "@" {
|
|
return fmt.Sprintf(",\n\tNAMESERVER('%s'%s)", target, ttlop)
|
|
}
|
|
default:
|
|
target = "'" + target + "'"
|
|
}
|
|
|
|
return fmt.Sprintf(",\n\t%s('%s', %s%s)", rec.Type, rec.Name, target, ttlop)
|
|
}
|