From fcf9ff4f4a212d826d0230150235120d867c5710 Mon Sep 17 00:00:00 2001 From: Johan Stenstam Date: Fri, 22 Nov 2024 18:01:55 +0100 Subject: [PATCH 1/5] * more MUSIC / TDNS integration work * changed the config validation logic to not just fatal error on missing DB files but instead specifu what file is missing and what CLI command to use to create it. * implemented CLI commands to create DB files --- agent/Makefile | 3 ++ cli/Makefile | 4 +++ cli/cmd/shared_cmds.go | 25 +++++++++-------- dog/Makefile | 3 ++ music/cmd/db_cmds.go | 52 +++++++++++++++++++++++++++++++++++ music/cmd/root.go | 23 +++++++++++++++- music/cmd/tdns_cmds.go.foo | 35 ----------------------- music/config.go | 11 +++++++- music/desec_mgr.go | 20 ++++++++++++-- server/Makefile | 3 ++ sidecar-cli/Makefile | 4 +++ sidecar-cli/cmd/music_cmds.go | 15 ++++++---- sidecar-cli/cmd/tdns_cmds.go | 21 ++++++++------ sidecar-cli/go.mod | 4 +-- sidecar/Makefile | 3 ++ tdns/cli/db_cmds.go | 52 +++++++++++++++++++++++++++++++++++ tdns/dsync_rr.go | 3 ++ tdns/parseconfig.go | 16 ++++++++++- tdns/queryresponder.go | 7 +++-- utils/Makefile.common | 9 +++--- 20 files changed, 237 insertions(+), 76 deletions(-) create mode 100644 music/cmd/db_cmds.go delete mode 100644 music/cmd/tdns_cmds.go.foo create mode 100644 tdns/cli/db_cmds.go diff --git a/agent/Makefile b/agent/Makefile index 8843e69..1c79fc3 100644 --- a/agent/Makefile +++ b/agent/Makefile @@ -7,3 +7,6 @@ include ../utils/Makefile.common install: install -s -b -c ${PROG} /usr/local/libexec/ +clean: + rm -f $(PROG) + rm -f *~ */*~ diff --git a/cli/Makefile b/cli/Makefile index ef3600e..b4cdc48 100644 --- a/cli/Makefile +++ b/cli/Makefile @@ -9,4 +9,8 @@ include ../utils/Makefile.common install: install -s -b -c ${PROG} /usr/local/bin/ +clean: + rm -f $(PROG) + rm -f *~ */*~ + diff --git a/cli/cmd/shared_cmds.go b/cli/cmd/shared_cmds.go index 9d54003..2a4f0bf 100644 --- a/cli/cmd/shared_cmds.go +++ b/cli/cmd/shared_cmds.go @@ -8,37 +8,40 @@ import ( ) func init() { - // From ../libcli/start_cmds.go: + // From ../tdns/cli/db_cmds.go: + rootCmd.AddCommand(cli.DbCmd) + + // From ../tdns/cli/start_cmds.go: rootCmd.AddCommand(cli.PingCmd) rootCmd.AddCommand(cli.DaemonCmd) - // From ../libcli/zone_cmds.go: + // From ../tdns/cli/zone_cmds.go: rootCmd.AddCommand(cli.ZoneCmd) - // From ../libcli/ddns_cmds.go: + // From ../tdns/cli/ddns_cmds.go: rootCmd.AddCommand(cli.DdnsCmd, cli.DelCmd) - // From ../libcli/debug_cmds.go: + // From ../tdns/cli/debug_cmds.go: rootCmd.AddCommand(cli.DebugCmd) - // From ../libcli/keystore_cmds.go: + // From ../tdns/cli/keystore_cmds.go: rootCmd.AddCommand(cli.KeystoreCmd) - // From ../libcli/truststore_cmds.go: + // From ../tdns/cli/truststore_cmds.go: rootCmd.AddCommand(cli.TruststoreCmd) - // From ../libcli/dsync_cmds.go: + // From ../tdns/cli/dsync_cmds.go: rootCmd.AddCommand(cli.DsyncDiscoveryCmd) - // From ../libcli/config_cmds.go: + // From ../tdns/cli/config_cmds.go: rootCmd.AddCommand(cli.ConfigCmd) - // From ../libcli/rfc3597.go: + // From ../tdns/cli/rfc3597.go: rootCmd.AddCommand(cli.ToRFC3597Cmd) - // From ../libcli/notify_cmds.go: + // From ../tdns/cli/notify_cmds.go: rootCmd.AddCommand(cli.NotifyCmd) - // From ../libcli/commands.go: + // From ../tdns/cli/commands.go: rootCmd.AddCommand(cli.StopCmd) } diff --git a/dog/Makefile b/dog/Makefile index a2c1e2a..48fe868 100644 --- a/dog/Makefile +++ b/dog/Makefile @@ -8,3 +8,6 @@ include ../utils/Makefile.common install: install -s -b -c ${PROG} /usr/local/bin/ +clean: + rm -f $(PROG) + rm -f *~ */*~ diff --git a/music/cmd/db_cmds.go b/music/cmd/db_cmds.go new file mode 100644 index 0000000..a79e126 --- /dev/null +++ b/music/cmd/db_cmds.go @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024 Johan Stenstam, johan.stenstam@internetstiftelsen.se + */ + +package mcmd + +import ( + "fmt" + "log" + "os" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var musicDbFile string +var DbCmd = &cobra.Command{ + Use: "db", + Short: "MUSIC DB commands", +} + +var dbInitCmd = &cobra.Command{ + Use: "init", + Short: "Initialize MUSIC DB", + Run: func(cmd *cobra.Command, args []string) { + if musicDbFile == "" { + musicDbFile = viper.GetString("db.file") + } + if musicDbFile == "" { + log.Fatalf("Error: MUSIC DB file not specified in config nor on command line") + } + + if _, err := os.Stat(musicDbFile); err == nil { + fmt.Printf("Warning: MUSIC DB file '%s' already exists.\n", musicDbFile) + } else if os.IsNotExist(err) { + file, err := os.Create(musicDbFile) + if err != nil { + log.Fatalf("Error creating MUSIC DB file '%s': %v", musicDbFile, err) + } + defer file.Close() + fmt.Printf("MUSIC DB file '%s' created successfully.\n", musicDbFile) + } else { + log.Fatalf("Error checking MUSIC DB file '%s': %v", musicDbFile, err) + } + }, +} + +func init() { + DbCmd.AddCommand(dbInitCmd) + + dbInitCmd.Flags().StringVarP(&musicDbFile, "file", "f", "", "MUSIC DB file") +} diff --git a/music/cmd/root.go b/music/cmd/root.go index 4c9f5d3..e48cd91 100644 --- a/music/cmd/root.go +++ b/music/cmd/root.go @@ -5,6 +5,8 @@ package mcmd import ( + "bytes" + "encoding/json" "fmt" "log" @@ -66,6 +68,25 @@ func InitApi() { tdns.Globals.Api = tdns.NewClient("sidecar-cli", baseurl, apikey, authmethod, "insecure", tdns.Globals.Verbose, tdns.Globals.Debug) if tdns.Globals.Debug { - fmt.Printf("initApi: api connection to %s initialized (%s)\n:\napi: %+v\n", baseurl, apikey, tdns.Globals.Api) + tmpapi := tdns.ApiClient{ + Name: tdns.Globals.Api.Name, + BaseUrl: tdns.Globals.Api.BaseUrl, + AuthMethod: tdns.Globals.Api.AuthMethod, + Client: nil, + Verbose: tdns.Globals.Verbose, + Debug: tdns.Globals.Debug, + UseTLS: tdns.Globals.Api.UseTLS, + } + bytebuf := new(bytes.Buffer) + err := json.NewEncoder(bytebuf).Encode(tmpapi) + if err != nil { + log.Fatalf("Error from json.NewEncoder: %v", err) + } + var prettyJSON bytes.Buffer + error := json.Indent(&prettyJSON, bytebuf.Bytes(), "", " ") + if error != nil { + log.Println("JSON parse error: ", error) + } + fmt.Printf("initApi: api connection to %s initialized (%s)\n:\napi: %s\n", baseurl, apikey, prettyJSON.String()) } } diff --git a/music/cmd/tdns_cmds.go.foo b/music/cmd/tdns_cmds.go.foo deleted file mode 100644 index a8904cb..0000000 --- a/music/cmd/tdns_cmds.go.foo +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2024 Johan Stenstam, johan.stenstam@internetstiftelsen.se - */ -package mcmd - -import ( - cli "github.com/johanix/tdns/libcli" -) - -func init() { - // From ../libcli/start_cmds.go: - rootCmd.AddCommand(cli.PingCmd) - rootCmd.AddCommand(cli.DaemonCmd) - - // From ../libcli/zone_cmds.go: - rootCmd.AddCommand(cli.ZoneCmd) - - // From ../libcli/ddns_cmds.go: - rootCmd.AddCommand(cli.DdnsCmd, cli.DelCmd) - - // From ../libcli/debug_cmds.go: - rootCmd.AddCommand(cli.DebugCmd) - - // From ../libcli/keystore_cmds.go: - rootCmd.AddCommand(cli.KeystoreCmd) - - // From ../libcli/truststore_cmds.go: - rootCmd.AddCommand(cli.TruststoreCmd) - - // From ../libcli/dsync_cmds.go: - rootCmd.AddCommand(cli.DsyncDiscoveryCmd) - - // From ../libcli/config_cmds.go: - rootCmd.AddCommand(cli.ConfigCmd) -} diff --git a/music/config.go b/music/config.go index 213109a..e239e39 100644 --- a/music/config.go +++ b/music/config.go @@ -87,7 +87,7 @@ type TsigConf struct { } type DbConf struct { - File string `validate:"file,required"` + File string // `validate:"file"` // not required, will be checked later Mode string `validate:"required"` } @@ -162,6 +162,15 @@ func ValidateConfig(v *viper.Viper, cfgfile, appMode string, safemode bool) erro } // fmt.Printf("config: %v\n", config) } + + if appMode != "sidecar-cli" && appMode != "tdns-cli" { + // Verify that we have a MUSIC DB file. + if _, err := os.Stat(config.Db.File); os.IsNotExist(err) { + log.Printf("ValidateConfig: MUSIC DB file '%s' does not exist.", config.Db.File) + log.Printf("Please initialize MUSIC DB using 'sidecar-cli music db init -f %s'.", config.Db.File) + return errors.New("ValidateConfig: MUSIC DB file does not exist") + } + } return nil } diff --git a/music/desec_mgr.go b/music/desec_mgr.go index d3108e5..a23fb82 100644 --- a/music/desec_mgr.go +++ b/music/desec_mgr.go @@ -8,12 +8,10 @@ import ( "fmt" "log" - // "net/http" "time" "github.com/miekg/dns" "github.com/spf13/viper" - // "github.com/DNSSEC-Provisioning/music/music" ) // According to https://desec.readthedocs.io/en/latest/rate-limits.html @@ -27,6 +25,24 @@ func DeSECmgr(mconf *Config, done <-chan struct{}) { desecfetch := mconf.Internal.DesecFetch desecupdate := mconf.Internal.DesecUpdate + desecActive := viper.GetBool("signers.desec.active") + if !desecActive { + log.Println("deSEC Manager: deSEC is not active. Will just poll the queues to avoid blocking the client.") + go func() { + for { + select { + case <-desecfetch: + log.Println("deSEC Manager: fetch request received. Ignoring.") + case <-desecupdate: + log.Println("deSEC Manager: update request received. Ignoring.") + case <-done: + return + } + } + }() + return + } + // we use the limit per minute var fetch_limit = viper.GetInt("signers.desec.limits.fetch") // per second var update_limit = viper.GetInt("signers.desec.limits.update") // per second diff --git a/server/Makefile b/server/Makefile index 75bff48..4ef53a6 100644 --- a/server/Makefile +++ b/server/Makefile @@ -8,3 +8,6 @@ include ../utils/Makefile.common install: install -s -b -c ${PROG} /usr/local/libexec/ +clean: + rm -f $(PROG) + rm -f *~ */*~ diff --git a/sidecar-cli/Makefile b/sidecar-cli/Makefile index cc10c51..39ab95b 100644 --- a/sidecar-cli/Makefile +++ b/sidecar-cli/Makefile @@ -12,4 +12,8 @@ install: # install -s -b ${PROG} ../sbin/ if [ ! -e ../etc/${CONFIG} ] ; then install -c ${CONFIG}.sample ../etc/${CONFIG}; fi +clean: + rm -f $(PROG) + rm -f *~ */*~ + diff --git a/sidecar-cli/cmd/music_cmds.go b/sidecar-cli/cmd/music_cmds.go index 90be5d1..e3ecc54 100644 --- a/sidecar-cli/cmd/music_cmds.go +++ b/sidecar-cli/cmd/music_cmds.go @@ -5,22 +5,25 @@ package cmd import ( - "github.com/spf13/cobra" mcmd "github.com/johanix/tdns/music/cmd" + "github.com/spf13/cobra" ) func init() { var MusicCmd = &cobra.Command{ - Use: "music", - Short: "prefix cmd to reach all MUSIC sub-commands", + Use: "music", + Short: "prefix cmd to reach all MUSIC sub-commands", } rootCmd.AddCommand(MusicCmd) - // from ../music/cmd/status.go: + // from ../music/cmd/db_cmds.go: + MusicCmd.AddCommand(mcmd.DbCmd) + + // from ../music/cmd/status.go: MusicCmd.AddCommand(mcmd.StatusCmd) - // from ../music/cmd/deseclogin.go: + // from ../music/cmd/deseclogin.go: MusicCmd.AddCommand(mcmd.DesecCmd) // from ../music/cmd/process.go: @@ -38,6 +41,6 @@ func init() { // from ../music/cmd/test.go: MusicCmd.AddCommand(mcmd.TestCmd) - // from ../music/cmd/zone.go: + // from ../music/cmd/zone.go: MusicCmd.AddCommand(mcmd.ZoneCmd) } diff --git a/sidecar-cli/cmd/tdns_cmds.go b/sidecar-cli/cmd/tdns_cmds.go index 7bc1271..d8ba917 100644 --- a/sidecar-cli/cmd/tdns_cmds.go +++ b/sidecar-cli/cmd/tdns_cmds.go @@ -4,32 +4,35 @@ package cmd import ( - cli "github.com/johanix/tdns/libcli" + cli "github.com/johanix/tdns/tdns/cli" ) func init() { - // From ../libcli/start_cmds.go: + // From ../tdns/cli/db_cmds.go: + rootCmd.AddCommand(cli.DbCmd) + + // From ../tdns/cli/start_cmds.go: rootCmd.AddCommand(cli.PingCmd) rootCmd.AddCommand(cli.DaemonCmd) - // From ../libcli/zone_cmds.go: + // From ../tdns/cli/zone_cmds.go: rootCmd.AddCommand(cli.ZoneCmd) - // From ../libcli/ddns_cmds.go: + // From ../tdns/cli/ddns_cmds.go: rootCmd.AddCommand(cli.DdnsCmd, cli.DelCmd) - // From ../libcli/debug_cmds.go: + // From ../tdns/cli/debug_cmds.go: rootCmd.AddCommand(cli.DebugCmd) - // From ../libcli/keystore_cmds.go: + // From ../tdns/cli/keystore_cmds.go: rootCmd.AddCommand(cli.KeystoreCmd) - // From ../libcli/truststore_cmds.go: + // From ../tdns/cli/truststore_cmds.go: rootCmd.AddCommand(cli.TruststoreCmd) - // From ../libcli/dsync_cmds.go: + // From ../tdns/cli/dsync_cmds.go: rootCmd.AddCommand(cli.DsyncDiscoveryCmd) - // From ../libcli/config_cmds.go: + // From ../tdns/cli/config_cmds.go: rootCmd.AddCommand(cli.ConfigCmd) } diff --git a/sidecar-cli/go.mod b/sidecar-cli/go.mod index c6c7e30..a8c3caf 100644 --- a/sidecar-cli/go.mod +++ b/sidecar-cli/go.mod @@ -3,16 +3,16 @@ module sidecar-cli go 1.23.2 replace ( - github.com/johanix/tdns/libcli => ../libcli github.com/johanix/tdns/music => ../music github.com/johanix/tdns/music/cmd => ../music/cmd github.com/johanix/tdns/tdns => ../tdns + github.com/johanix/tdns/tdns/cli => ../tdns/cli ) require ( - github.com/johanix/tdns/libcli v0.0.0-00010101000000-000000000000 github.com/johanix/tdns/music/cmd v0.0.0-00010101000000-000000000000 github.com/johanix/tdns/tdns v0.0.0-00010101000000-000000000000 + github.com/johanix/tdns/tdns/cli v0.0.0-00010101000000-000000000000 github.com/spf13/cobra v1.8.1 ) diff --git a/sidecar/Makefile b/sidecar/Makefile index b1e576c..899af15 100644 --- a/sidecar/Makefile +++ b/sidecar/Makefile @@ -7,3 +7,6 @@ include ../utils/Makefile.common install: install -s -b -c ${PROG} /usr/local/libexec/ +clean: + rm -f $(PROG) + rm -f *~ */*~ diff --git a/tdns/cli/db_cmds.go b/tdns/cli/db_cmds.go new file mode 100644 index 0000000..54185e1 --- /dev/null +++ b/tdns/cli/db_cmds.go @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024 Johan Stenstam, johani@johani.org + */ +package cli + +import ( + "fmt" + "log" + "os" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var tdnsDbFile string + +var DbCmd = &cobra.Command{ + Use: "db", + Short: "TDNS DB commands", +} + +var dbInitCmd = &cobra.Command{ + Use: "init", + Short: "Initialize TDNS DB", + Run: func(cmd *cobra.Command, args []string) { + if tdnsDbFile == "" { + tdnsDbFile = viper.GetString("db.file") + } + if tdnsDbFile == "" { + log.Fatalf("Error: TDNS DB file not specified in config nor on command line") + } + + if _, err := os.Stat(tdnsDbFile); err == nil { + fmt.Printf("Warning: TDNS DB file '%s' already exists.\n", tdnsDbFile) + } else if os.IsNotExist(err) { + file, err := os.Create(tdnsDbFile) + if err != nil { + log.Fatalf("Error creating TDNS DB file '%s': %v", tdnsDbFile, err) + } + defer file.Close() + fmt.Printf("TDNS DB file '%s' created successfully.\n", tdnsDbFile) + } else { + log.Fatalf("Error checking TDNS DB file '%s': %v", tdnsDbFile, err) + } + }, +} + +func init() { + DbCmd.AddCommand(dbInitCmd) + + dbInitCmd.Flags().StringVarP(&tdnsDbFile, "file", "f", "", "TDNS DB file") +} diff --git a/tdns/dsync_rr.go b/tdns/dsync_rr.go index 2a93fbd..70babdc 100644 --- a/tdns/dsync_rr.go +++ b/tdns/dsync_rr.go @@ -43,6 +43,9 @@ var StringToScheme = map[string]DsyncScheme{ "NOTIFY": SchemeNotify, "UPDATE": SchemeUpdate, "API": SchemeAPI, + "1": SchemeNotify, + "2": SchemeUpdate, + "3": SchemeAPI, } func NewDSYNC() dns.PrivateRdata { return new(DSYNC) } diff --git a/tdns/parseconfig.go b/tdns/parseconfig.go index 6ccfe83..c7edaf2 100644 --- a/tdns/parseconfig.go +++ b/tdns/parseconfig.go @@ -6,6 +6,7 @@ package tdns import ( // "flag" + "errors" "fmt" "log" "net" @@ -194,8 +195,21 @@ func ParseConfig(conf *Config, reload bool) error { fmt.Println() kdb := conf.Internal.KeyDB + // fmt.Printf("DEBUG: conf.AppName: %s AppMode: %s\n", conf.AppName, conf.AppMode) if !reload || kdb == nil { - kdb, err := NewKeyDB(viper.GetString("db.file"), false) + + dbFile := viper.GetString("db.file") + if conf.AppName != "sidecar-cli" && conf.AppName != "tdns-cli" { + // Verify that we have a MUSIC DB file. + fmt.Printf("Verifying existence of TDNS DB file: %s\n", dbFile) + if _, err := os.Stat(dbFile); os.IsNotExist(err) { + log.Printf("ParseConfig: TDNS DB file '%s' does not exist.", dbFile) + log.Printf("Please initialize TDNS DB using 'tdns-cli|sidecar-cli db init -f %s'.", dbFile) + return errors.New("ParseConfig: TDNS DB file does not exist") + } + } + + kdb, err := NewKeyDB(dbFile, false) if err != nil { log.Fatalf("Error from NewKeyDB: %v", err) } diff --git a/tdns/queryresponder.go b/tdns/queryresponder.go index 61e0968..9e4945f 100644 --- a/tdns/queryresponder.go +++ b/tdns/queryresponder.go @@ -88,7 +88,7 @@ func (zd *ZoneData) ApexResponder(w dns.ResponseWriter, r *dns.Msg, qname string case TypeDSYNC, TypeNOTIFY, TypeMSIGNER, dns.TypeMX, dns.TypeTLSA, dns.TypeSRV, dns.TypeA, dns.TypeAAAA, dns.TypeNS, dns.TypeTXT, dns.TypeZONEMD, dns.TypeKEY, dns.TypeNSEC, dns.TypeNSEC3, dns.TypeNSEC3PARAM, dns.TypeRRSIG, - dns.TypeDNSKEY, dns.TypeCSYNC, dns.TypeCDS, dns.TypeCDNSKEY: + dns.TypeDNSKEY, dns.TypeCSYNC, dns.TypeCDS, dns.TypeCDNSKEY, dns.TypeSVCB: if rrset, ok := apex.RRtypes.Get(qtype); ok { if len(rrset.RRs) > 0 { m.Answer = append(m.Answer, rrset.RRs...) @@ -347,7 +347,8 @@ func (zd *ZoneData) QueryResponder(w dns.ResponseWriter, r *dns.Msg, qname strin log.Printf("---> Checking for exact match qname+qtype %s %s in zone %s", qname, dns.TypeToString[qtype], zd.ZoneName) switch qtype { case dns.TypeTXT, dns.TypeMX, dns.TypeA, dns.TypeAAAA, dns.TypeSRV, TypeNOTIFY, TypeDSYNC, - TypeDELEG, dns.TypeDS, dns.TypeNSEC, dns.TypeNSEC3, dns.TypeRRSIG: + TypeDELEG, dns.TypeDS, dns.TypeNSEC, dns.TypeNSEC3, dns.TypeRRSIG, dns.TypeSVCB, dns.TypeHTTPS, dns.TypeTLSA, dns.TypeCAA, + dns.TypeCDS, dns.TypeCSYNC, dns.TypeCDNSKEY: if rrset, ok := owner.RRtypes.Get(qtype); ok && len(owner.RRtypes.GetOnlyRRSet(qtype).RRs) > 0 { if qname == origqname { // zd.Logger.Printf("Exact match qname %s %s", qname, dns.TypeToString[qtype]) @@ -401,7 +402,7 @@ func (zd *ZoneData) QueryResponder(w dns.ResponseWriter, r *dns.Msg, qname strin return nil } - // Final catcheverything we don't want to deal with + // Final catch everything we don't want to deal with m.MsgHdr.Rcode = dns.RcodeRefused m.Ns = append(m.Ns, apex.RRtypes.GetOnlyRRSet(dns.TypeNS).RRs...) v4glue, v6glue = zd.FindGlue(apex.RRtypes.GetOnlyRRSet(dns.TypeNS), dnssec_ok) diff --git a/utils/Makefile.common b/utils/Makefile.common index ec5a7f1..eb46b42 100644 --- a/utils/Makefile.common +++ b/utils/Makefile.common @@ -21,8 +21,10 @@ netbsd: version.go # johaniaws: Sync local tdns source to remote AWS environment. # Requires: sshfs mount at ~/sshfs/msigner1 -TDNS_SRC ?= ~/src/git/tdns -TDNS_AWS_DIR ?= ~/sshfs/msigner1/src/tdns +# TDNS_SRC ?= ~/src/git/tdns +# TDNS_AWS_DIR ?= ~/sshfs/msigner1/src/tdns +TDNS_SRC ?= /Users/johani/src/git/tdns +TDNS_AWS_DIR ?= /Users/johani/sshfs/msigner1/src/tdns johaniaws: rsync --exclude=.git --exclude-from=${TDNS_SRC}/.gitignore -avx ${TDNS_SRC}/ ${TDNS_AWS_DIR}/ @@ -30,9 +32,6 @@ johaniaws: test: $(GO) test -v -cover -clean: - @rm -f $(PROG) - # install: # install -s -b -c ${PROG} /usr/local/bin/ From 5402b708b429a51f401aad6790f14fc006829c5e Mon Sep 17 00:00:00 2001 From: Johan Stenstam Date: Sat, 23 Nov 2024 00:44:55 +0100 Subject: [PATCH 2/5] * lots of work on figuring out the sidecar config structure. Now we have the sidecar identity separate per sync mechanism. Note: This forces a sidecar that supports both API and DNS to use *separate* identities for these two services. Also note that while the identities must be separate, they mey refer to the same IP addresses (but not port, for obvious reasons). * mostly done with an implementation that synthesizes a new SVCB for the sidecar API (or DNS) identity. * not quite done with implementing the check SIG(0) key pair, generate key pair, publish public key, stuff. We have all that already, for the delegation sync. Question is whether to refactor or duplicate, as the requirements are slighly different. --- music/config.go | 318 +++++++++++++++++++------- music/syncengine.go | 167 ++++++++------ tdns/delegation_utils.go | 4 +- tdns/dnsutils.go | 2 - tdns/{csync_ops.go => ops_csync.go} | 0 tdns/{dnskey_ops.go => ops_dnskey.go} | 0 tdns/{dsync_ops.go => ops_dsync.go} | 0 tdns/{key_ops.go => ops_key.go} | 0 tdns/ops_svcb.go | 113 +++++++++ tdns/{tlsa_ops.go => ops_tlsa.go} | 0 tdns/{deleg_rr.go => rr_deleg.go} | 0 tdns/{dsync_rr.go => rr_dsync.go} | 0 tdns/{msigner_rr.go => rr_msigner.go} | 48 ++-- tdns/{notify_rr.go => rr_notify.go} | 0 14 files changed, 469 insertions(+), 183 deletions(-) rename tdns/{csync_ops.go => ops_csync.go} (100%) rename tdns/{dnskey_ops.go => ops_dnskey.go} (100%) rename tdns/{dsync_ops.go => ops_dsync.go} (100%) rename tdns/{key_ops.go => ops_key.go} (100%) create mode 100644 tdns/ops_svcb.go rename tdns/{tlsa_ops.go => ops_tlsa.go} (100%) rename tdns/{deleg_rr.go => rr_deleg.go} (100%) rename tdns/{dsync_rr.go => rr_dsync.go} (100%) rename tdns/{msigner_rr.go => rr_msigner.go} (75%) rename tdns/{notify_rr.go => rr_notify.go} (100%) diff --git a/music/config.go b/music/config.go index e239e39..b1d9747 100644 --- a/music/config.go +++ b/music/config.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "log" + "net" "os" "github.com/go-playground/validator/v10" @@ -35,10 +36,22 @@ type Config struct { } type SidecarConf struct { - Identity string - Port uint16 - Cert string - Key string + Api SidecarApiConf + Dns SidecarDnsConf +} + +type SidecarApiConf struct { + Identity string + Addresses []string + Port uint16 + Cert string + Key string +} + +type SidecarDnsConf struct { + Identity string + Addresses []string + Port uint16 } type ZonesConf struct { @@ -281,107 +294,240 @@ func LoadMusicConfig(mconf *Config, appMode string, safemode bool) error { func LoadSidecarConfig(mconf *Config, all_zones []string) error { log.Printf("loadSidecarConfig: enter") - mconf.Sidecar.Identity = viper.GetString("music.sidecar.identity") - if mconf.Sidecar.Identity == "" { - return errors.New("LoadSidecarConfig: sidecar identity not set in config file") - } - mconf.Sidecar.Identity = dns.Fqdn(mconf.Sidecar.Identity) - mconf.Sidecar.Port = uint16(viper.GetInt("music.sidecar.port")) - if mconf.Sidecar.Port == 0 { - return errors.New("LoadSidecarConfig: sidecar port not set in config file") - } + // mconf.Sidecar.Identity = viper.GetString("music.sidecar.identity") + // if mconf.Sidecar.Identity == "" { + // return errors.New("LoadSidecarConfig: sidecar identity not set in config file") + // } + mconf.Sidecar.Api.Identity = dns.Fqdn(mconf.Sidecar.Api.Identity) + mconf.Sidecar.Dns.Identity = dns.Fqdn(mconf.Sidecar.Dns.Identity) - certFile := viper.GetString("music.sidecar.cert") - keyFile := viper.GetString("music.sidecar.key") + mconf.Sidecar.Api.Addresses = viper.GetStringSlice("music.sidecar.api.addresses") + mconf.Sidecar.Dns.Addresses = viper.GetStringSlice("music.sidecar.dns.addresses") - if certFile == "" || keyFile == "" { - return errors.New("LoadSidecarConfig: cert or key file not set in config file") - } + mconf.Sidecar.Api.Port = uint16(viper.GetInt("music.sidecar.api.port")) + mconf.Sidecar.Dns.Port = uint16(viper.GetInt("music.sidecar.dns.port")) - certPEM, err := os.ReadFile(certFile) - if err != nil { - return fmt.Errorf("LoadSidecarConfig: error reading cert file: %v", err) + if len(mconf.Sidecar.Api.Addresses) == 0 && len(mconf.Sidecar.Dns.Addresses) == 0 { + return errors.New("LoadSidecarConfig: neither sidecar syncapi nor syncdns addresses set in config file") } - keyPEM, err := os.ReadFile(keyFile) - if err != nil { - return fmt.Errorf("LoadSidecarConfig: error reading key file: %v", err) - } + certFile := viper.GetString("music.sidecar.api.cert") + keyFile := viper.GetString("music.sidecar.api.key") - mconf.Sidecar.Cert = string(certPEM) - mconf.Sidecar.Key = string(keyPEM) + if mconf.Sidecar.Api.Identity != "" { + if certFile == "" || keyFile == "" { + return errors.New("LoadSidecarConfig: Sidecar API identity defined, but cert or key file not set in config file") + } - block, _ := pem.Decode(certPEM) - if block == nil { - return fmt.Errorf("LoadSidecarConfig: failed to parse certificate PEM") - } + certPEM, err := os.ReadFile(certFile) + if err != nil { + return fmt.Errorf("LoadSidecarConfig: error reading cert file: %v", err) + } - // Parse the certificate - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return fmt.Errorf("LoadSidecarConfig: failed to parse certificate: %v", err) - } + keyPEM, err := os.ReadFile(keyFile) + if err != nil { + return fmt.Errorf("LoadSidecarConfig: error reading key file: %v", err) + } - // Extract the CN from the certificate - certCN := cert.Subject.CommonName + mconf.Sidecar.Api.Cert = string(certPEM) + mconf.Sidecar.Api.Key = string(keyPEM) - // Compare the CN with the expected CN - if certCN != mconf.Sidecar.Identity { - return fmt.Errorf("LoadSidecarConfig: Error: Sidecar certificate CN '%s' does not match sidecar identity '%s'", certCN, mconf.Internal.SidecarId) - } + block, _ := pem.Decode(certPEM) + if block == nil { + return fmt.Errorf("LoadSidecarConfig: failed to parse certificate PEM") + } - log.Printf("LoadSidecarConfig: cert CN '%s' matches sidecar identity '%s'", certCN, mconf.Sidecar.Identity) + // Parse the certificate + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return fmt.Errorf("LoadSidecarConfig: failed to parse certificate: %v", err) + } - ur := tdns.UpdateRequest{ - Cmd: "DEFERRED-UPDATE", - ZoneName: mconf.Sidecar.Identity, - PreCondition: func() bool { - _, ok := tdns.Zones.Get(mconf.Sidecar.Identity) - if !ok { - log.Printf("LoadSidecarConfig: zone data for sidecar identity '%s' still not found", mconf.Sidecar.Identity) - } - return ok - }, - Action: func() error { - zd, ok := tdns.Zones.Get(mconf.Sidecar.Identity) - if !ok { - return fmt.Errorf("LoadSidecarConfig: Action: zone data for sidecar identity '%s' still not found", mconf.Sidecar.Identity) - } - zd.Options[tdns.OptAllowUpdates] = true + // Extract the CN from the certificate + certCN := cert.Subject.CommonName - log.Printf("LoadSidecarConfig: sending PING command to sidecar '%s'", mconf.Sidecar.Identity) - zd.KeyDB.UpdateQ <- tdns.UpdateRequest{ - Cmd: "PING", - } + // Compare the CN with the expected CN + if certCN != mconf.Sidecar.Api.Identity { + return fmt.Errorf("LoadSidecarConfig: Error: Sidecar certificate CN '%s' does not match sidecar identity '%s'", certCN, mconf.Sidecar.Api.Identity) + } - log.Printf("LoadSidecarConfig: publishing TLSA RR for sidecar identity '%s'", mconf.Sidecar.Identity) + log.Printf("LoadSidecarConfig: cert CN '%s' matches sidecar identity '%s'", certCN, mconf.Sidecar.Api.Identity) + + ur := tdns.UpdateRequest{ + Cmd: "DEFERRED-UPDATE", + ZoneName: mconf.Sidecar.Api.Identity, + PreCondition: func() bool { + _, ok := tdns.Zones.Get(mconf.Sidecar.Api.Identity) + if !ok { + log.Printf("LoadSidecarConfig: zone data for sidecar identity '%s' still not found", mconf.Sidecar.Api.Identity) + } + return ok + }, + Action: func() error { + zd, ok := tdns.Zones.Get(mconf.Sidecar.Api.Identity) + if !ok { + return fmt.Errorf("LoadSidecarConfig: Action: zone data for sidecar identity '%s' still not found", mconf.Sidecar.Api.Identity) + } + zd.Options[tdns.OptAllowUpdates] = true + + log.Printf("LoadSidecarConfig: sending PING command to sidecar '%s'", mconf.Sidecar.Api.Identity) + zd.KeyDB.UpdateQ <- tdns.UpdateRequest{ + Cmd: "PING", + } + + log.Printf("LoadSidecarConfig: publishing TLSA RR for sidecar identity '%s'", mconf.Sidecar.Api.Identity) + + err = zd.PublishTLSARR(string(certPEM), mconf.Sidecar.Api.Port) + if err != nil { + return fmt.Errorf("LoadSidecarConfig: failed to publish TLSA RR: %v", err) + } + + log.Printf("LoadSidecarConfig: Successfully published TLSA RR for sidecar identity '%s'\n", mconf.Sidecar.Api.Identity) + + var ipv4hint, ipv6hint []net.IP + if len(mconf.Sidecar.Api.Addresses) > 0 { + for _, addr := range mconf.Sidecar.Api.Addresses { + ip := net.ParseIP(addr) + if ip.To4() != nil { + ipv4hint = append(ipv4hint, ip) + } else { + ipv6hint = append(ipv6hint, ip) + } + } + } + var value []dns.SVCBKeyValue + + if mconf.Sidecar.Api.Port != 0 { + value = append(value, &dns.SVCBPort{Port: mconf.Sidecar.Api.Port}) + } + + if len(ipv4hint) > 0 { + value = append(value, &dns.SVCBIPv4Hint{Hint: ipv4hint}) + } + + if len(ipv6hint) > 0 { + value = append(value, &dns.SVCBIPv6Hint{Hint: ipv6hint}) + } + + err = zd.PublishSvcbRR(mconf.Sidecar.Api.Identity, mconf.Sidecar.Api.Port, value) + if err != nil { + return fmt.Errorf("LoadSidecarConfig: failed to publish SVCB RR: %v", err) + } + + log.Printf("LoadSidecarConfig: Successfully published SVCB RR for sidecar API identity '%s'\n", mconf.Sidecar.Api.Identity) + + apex, _ := zd.Data.Get(zd.ZoneName) + if err != nil { + return fmt.Errorf("LoadSidecarConfig: Error: failed to get zone apex %s: %v", zd.ZoneName, err) + } + + tlsarr_rrset := apex.RRtypes.GetOnlyRRSet(dns.TypeTLSA) + var tlsarr *dns.TLSA + if len(tlsarr_rrset.RRs) > 0 { + tlsarr = tlsarr_rrset.RRs[0].(*dns.TLSA) + log.Printf("LoadSidecarConfig: TLSA RR: %s", tlsarr.String()) + } else { + log.Printf("LoadSidecarConfig: Error: TLSA: %v", tlsarr_rrset) + return fmt.Errorf("LoadSidecarConfig: Error: TLSA: %v", tlsarr_rrset) + } + return nil + }, + } - err = zd.PublishTLSARR(string(certPEM), mconf.Sidecar.Port) - if err != nil { - return fmt.Errorf("LoadSidecarConfig: failed to publish TLSA RR: %v", err) - } + mconf.Internal.UpdateQ <- ur - log.Printf("LoadSidecarConfig: Successfully published TLSA RR for sidecar identity '%s'\n", mconf.Sidecar.Identity) + } - apex, _ := zd.Data.Get(zd.ZoneName) - if err != nil { - return fmt.Errorf("LoadSidecarConfig: Error: failed to get zone apex %s: %v", zd.ZoneName, err) - } + if mconf.Sidecar.Dns.Identity != "" { + + // 1. Check whether we already have a SIG(0) key pair for this sidecar. See tdns.DelegationSyncSetup() + + ur := tdns.UpdateRequest{ + Cmd: "DEFERRED-UPDATE", + ZoneName: mconf.Sidecar.Dns.Identity, + PreCondition: func() bool { + _, ok := tdns.Zones.Get(mconf.Sidecar.Dns.Identity) + if !ok { + log.Printf("LoadSidecarConfig: zone data for sidecar identity '%s' still not found", mconf.Sidecar.Dns.Identity) + } + return ok + }, + Action: func() error { + var err error + zd, ok := tdns.Zones.Get(mconf.Sidecar.Dns.Identity) + if !ok { + return fmt.Errorf("LoadSidecarConfig: Action: zone data for sidecar identity '%s' still not found", mconf.Sidecar.Dns.Identity) + } + zd.Options[tdns.OptAllowUpdates] = true + + log.Printf("LoadSidecarConfig: sending PING command to sidecar '%s'", mconf.Sidecar.Dns.Identity) + zd.KeyDB.UpdateQ <- tdns.UpdateRequest{ + Cmd: "PING", + } + + log.Printf("LoadSidecarConfig: publishing KEY RR for sidecar identity '%s' SIG(0) public key", mconf.Sidecar.Dns.Identity) + + // err = zd.PublishKeyRRs(mconf.Sidecar.Dns.Identity, 0, mconf.Sidecar.Dns.Identity) + // if err != nil { + // return fmt.Errorf("LoadSidecarConfig: failed to publish TLSA RR: %v", err) + // } + + log.Printf("LoadSidecarConfig: Successfully published KEY RR for sidecar DNS identity '%s' SIG(0) public key\n", mconf.Sidecar.Dns.Identity) + + var ipv4hint, ipv6hint []net.IP + if len(mconf.Sidecar.Dns.Addresses) > 0 { + for _, addr := range mconf.Sidecar.Dns.Addresses { + ip := net.ParseIP(addr) + if ip.To4() != nil { + ipv4hint = append(ipv4hint, ip) + } else { + ipv6hint = append(ipv6hint, ip) + } + } + } + var value []dns.SVCBKeyValue + + if mconf.Sidecar.Dns.Port != 0 { + value = append(value, &dns.SVCBPort{Port: mconf.Sidecar.Dns.Port}) + } + + if len(ipv4hint) > 0 { + value = append(value, &dns.SVCBIPv4Hint{Hint: ipv4hint}) + } + + if len(ipv6hint) > 0 { + value = append(value, &dns.SVCBIPv6Hint{Hint: ipv6hint}) + } + + log.Printf("LoadSidecarConfig: publishing SVCB RR for sidecar DNS identity '%s'", mconf.Sidecar.Dns.Identity) + + err = zd.PublishSvcbRR(mconf.Sidecar.Dns.Identity, mconf.Sidecar.Dns.Port, value) + if err != nil { + return fmt.Errorf("LoadSidecarConfig: failed to publish SVCB RR: %v", err) + } + + log.Printf("LoadSidecarConfig: Successfully published SVCB RR for sidecar identity '%s'\n", mconf.Sidecar.Dns.Identity) + + apex, _ := zd.Data.Get(zd.ZoneName) + if err != nil { + return fmt.Errorf("LoadSidecarConfig: Error: failed to get zone apex %s: %v", zd.ZoneName, err) + } + + tlsarr_rrset := apex.RRtypes.GetOnlyRRSet(dns.TypeTLSA) + var tlsarr *dns.TLSA + if len(tlsarr_rrset.RRs) > 0 { + tlsarr = tlsarr_rrset.RRs[0].(*dns.TLSA) + log.Printf("LoadSidecarConfig: TLSA RR: %s", tlsarr.String()) + } else { + log.Printf("LoadSidecarConfig: Error: TLSA: %v", tlsarr_rrset) + return fmt.Errorf("LoadSidecarConfig: Error: TLSA: %v", tlsarr_rrset) + } + return nil + }, + } - tlsarr_rrset := apex.RRtypes.GetOnlyRRSet(dns.TypeTLSA) - var tlsarr *dns.TLSA - if len(tlsarr_rrset.RRs) > 0 { - tlsarr = tlsarr_rrset.RRs[0].(*dns.TLSA) - log.Printf("LoadSidecarConfig: TLSA RR: %s", tlsarr.String()) - } else { - log.Printf("LoadSidecarConfig: Error: TLSA: %v", tlsarr_rrset) - return fmt.Errorf("LoadSidecarConfig: Error: TLSA: %v", tlsarr_rrset) - } - return nil - }, + mconf.Internal.UpdateQ <- ur } - mconf.Internal.UpdateQ <- ur - return nil } diff --git a/music/syncengine.go b/music/syncengine.go index 75fcb7d..401e91a 100644 --- a/music/syncengine.go +++ b/music/syncengine.go @@ -23,6 +23,7 @@ import ( type Sidecar struct { Identity string + Mechanism tdns.MsignerMechanism // either API or DNS Addresses []string Port uint16 LastHB time.Time @@ -53,16 +54,25 @@ type SidecarBeatResponse struct { } func MusicSyncEngine(mconf *Config, stopch chan struct{}) { - sidecarId := mconf.Sidecar.Identity + sidecarId := map[tdns.MsignerMechanism]string{ + tdns.MSignMechanismAPI: mconf.Sidecar.Api.Identity, + tdns.MSignMechanismDNS: mconf.Sidecar.Dns.Identity, + } // sidecars is a map of known "remote" sidecars that we // have received HELLO messages from. - sidecars := map[string]*Sidecar{} + sidecars := map[tdns.MsignerMechanism]map[string]*Sidecar{ + tdns.MSignMechanismAPI: map[string]*Sidecar{}, + tdns.MSignMechanismDNS: map[string]*Sidecar{}, + } // wannabe_sidecars is a map of sidecars that we have received // a HELLO message from, but have not yet verified that they are // correct - wannabe_sidecars := map[string]*Sidecar{} + wannabe_sidecars := map[tdns.MsignerMechanism]map[string]*Sidecar{ + tdns.MSignMechanismAPI: map[string]*Sidecar{}, + tdns.MSignMechanismDNS: map[string]*Sidecar{}, + } // zones is a map of zones and the remote sidecars that share them with us zones := map[string][]*Sidecar{} @@ -127,9 +137,9 @@ func MusicSyncEngine(mconf *Config, stopch chan struct{}) { ReportProgress := func() { allok := true if allok { - log.Printf("MusicSyncEngine: received heartbeats from these sidecars: %v (the expected result)", sidecars) + log.Printf("MusicSyncEngine: received heartbeats from these sidecars: %+v (the expected result)", sidecars) } else { - log.Printf("MusicSyncEngine: received heartbeats from these sidecars: %v (missing some sidecars: %v)", + log.Printf("MusicSyncEngine: received heartbeats from these sidecars: %+v (missing some sidecars: %+v)", sidecars, missing) } } @@ -141,7 +151,7 @@ func MusicSyncEngine(mconf *Config, stopch chan struct{}) { zonename = syncitem.ZoneName switch cmd { case "RESET-MSIGNER-GROUP": - log.Printf("MusicSyncEngine: Zone %s MSIGNER RRset has changed. Resetting MSIGNER group.", zonename) + log.Printf("MusicSyncEngine: Zone %s MSIGNER RRset has changed. Resetting MSIGNER group. Removed MSIGNER RRs:\n", zonename) // log.Printf("MusicSyncEngine: Removed MSIGNER RRs:\n") for _, rr := range syncitem.MsignerSyncStatus.MsignerRemoves { log.Printf(" %s", rr.String()) @@ -203,23 +213,26 @@ func MusicSyncEngine(mconf *Config, stopch chan struct{}) { } } -func MaybeSendHello(sidecarId string, sidecars, wannabe_sidecars map[string]*Sidecar, syncitem tdns.MultiSignerSyncRequest, zones map[string][]*Sidecar, zonename string) error { +func MaybeSendHello(sidecarId map[tdns.MsignerMechanism]string, sidecars, wannabe_sidecars map[tdns.MsignerMechanism]map[string]*Sidecar, syncitem tdns.MultiSignerSyncRequest, zones map[string][]*Sidecar, zonename string) error { for _, remoteSidecarRR := range syncitem.MsignerSyncStatus.MsignerAdds { if prr, ok := remoteSidecarRR.(*dns.PrivateRR); ok { if msrr, ok := prr.Data.(*tdns.MSIGNER); ok { + remoteMechanism := msrr.Mechanism remoteSidecar := msrr.Target - if remoteSidecar == sidecarId { + if remoteSidecar == sidecarId[remoteMechanism] { // we don't need to send a hello to ourselves continue } - if _, exists := sidecars[remoteSidecar]; !exists { - sidecars[remoteSidecar] = &Sidecar{ - Identity: remoteSidecar, + if _, exists := sidecars[remoteMechanism][remoteSidecar]; !exists { + sidecars[remoteMechanism][remoteSidecar] = &Sidecar{ + Identity: remoteSidecar, + Mechanism: remoteMechanism, // Here we should also look up the SVCB to get the port and addresses } } // Schedule sending an HELLO message to the new sidecar - log.Printf("MaybeSendHello: Scheduling HELLO message to sidecar %s", remoteSidecar) + log.Printf("MaybeSendHello: Scheduling HELLO message to remote %s sidecar %s", + tdns.MsignerMechanismToString[remoteMechanism], remoteSidecar) // Add code to send HELLO message here continue } @@ -230,16 +243,22 @@ func MaybeSendHello(sidecarId string, sidecars, wannabe_sidecars map[string]*Sid return nil } -func EvaluateSidecarHello(sidecars, wannabe_sidecars map[string]*Sidecar, zones map[string][]*Sidecar) error { +func EvaluateSidecarHello(sidecars, wannabe_sidecars map[tdns.MsignerMechanism]map[string]*Sidecar, zones map[string][]*Sidecar) error { // for each sidecar in wannabe_sidecars, check if it is already in sidecars // if it is, add it to sidecars and remove it from wannabe_sidecars // if it is not, check whether it should be - for _, sidecar := range wannabe_sidecars { - if _, ok := sidecars[sidecar.Identity]; ok { - // already in sidecars - delete(wannabe_sidecars, sidecar.Identity) + for _, ws := range wannabe_sidecars { + for _, s := range ws { + if _, ok := sidecars[s.Mechanism][s.Identity]; ok { + // already in sidecars + delete(wannabe_sidecars[s.Mechanism], s.Identity) + } else { + // check if it should be in the set of known sidecars + log.Printf("EvaluateSidecarHello: Sidecar %s (%s) is not in sidecars, but claims to share zones with us.", + s.Identity, tdns.MsignerMechanismToString[s.Mechanism]) + } } } @@ -247,68 +266,76 @@ func EvaluateSidecarHello(sidecars, wannabe_sidecars map[string]*Sidecar, zones } func (s *Sidecar) SendHello() error { - // Create the SidecarHelloPost struct - helloPost := SidecarHelloPost{ - SidecarId: s.Identity, - Addresses: s.Addresses, - Port: s.Port, - } - // Encode the struct as JSON - jsonData, err := json.Marshal(helloPost) - if err != nil { - return fmt.Errorf("failed to marshal SidecarHelloPost: %v", err) - } + switch s.Mechanism { + case tdns.MSignMechanismAPI: + // Create the SidecarHelloPost struct + helloPost := SidecarHelloPost{ + SidecarId: s.Identity, + Addresses: s.Addresses, + Port: s.Port, + } - // Lookup the TLSA record for the target sidecar - tlsarrset, err := tdns.LookupTLSA(s.Identity) - if err != nil { - return fmt.Errorf("failed to lookup TLSA record: %v", err) - } + // Encode the struct as JSON + jsonData, err := json.Marshal(helloPost) + if err != nil { + return fmt.Errorf("failed to marshal SidecarHelloPost: %v", err) + } - // Use the TLSA record to authenticate the remote end securely - // (This is a simplified example, in a real implementation you would need to configure the TLS client with the TLSA record) - tlsConfig := &tls.Config{ - VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { - for _, rawCert := range rawCerts { - cert, err := x509.ParseCertificate(rawCert) - if err != nil { - return fmt.Errorf("failed to parse certificate: %v", err) - } - if cert.Subject.CommonName != s.Identity { - return fmt.Errorf("unexpected certificate common name (should have been %s)", s.Identity) - } + // Lookup the TLSA record for the target sidecar + tlsarrset, err := tdns.LookupTLSA(s.Identity) + if err != nil { + return fmt.Errorf("failed to lookup TLSA record: %v", err) + } - err = tdns.VerifyCertAgainstTLSA(tlsarrset, rawCert) - if err != nil { - return fmt.Errorf("failed to verify certificate against TLSA record: %v", err) + // Use the TLSA record to authenticate the remote end securely + // (This is a simplified example, in a real implementation you would need to configure the TLS client with the TLSA record) + tlsConfig := &tls.Config{ + VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + for _, rawCert := range rawCerts { + cert, err := x509.ParseCertificate(rawCert) + if err != nil { + return fmt.Errorf("failed to parse certificate: %v", err) + } + if cert.Subject.CommonName != s.Identity { + return fmt.Errorf("unexpected certificate common name (should have been %s)", s.Identity) + } + + err = tdns.VerifyCertAgainstTLSA(tlsarrset, rawCert) + if err != nil { + return fmt.Errorf("failed to verify certificate against TLSA record: %v", err) + } } - } - return nil - }, - } + return nil + }, + } - // Create the HTTPS client - client := &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: tlsConfig, - }, - } + // Create the HTTPS client + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: tlsConfig, + }, + } - // Send the HTTPS POST request - url := fmt.Sprintf("https://%s:%d/hello", s.Identity, s.Port) - resp, err := client.Post(url, "application/json", bytes.NewBuffer(jsonData)) - if err != nil { - return fmt.Errorf("failed to send HTTPS POST request: %v", err) - } - defer resp.Body.Close() + // Send the HTTPS POST request + url := fmt.Sprintf("https://%s:%d/hello", s.Identity, s.Port) + resp, err := client.Post(url, "application/json", bytes.NewBuffer(jsonData)) + if err != nil { + return fmt.Errorf("failed to send HTTPS POST request: %v", err) + } + defer resp.Body.Close() + + // Print the response to stdout + body, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed to read response body: %v", err) + } + fmt.Printf("Received response: %s\n", string(body)) - // Print the response to stdout - body, err := io.ReadAll(resp.Body) - if err != nil { - return fmt.Errorf("failed to read response body: %v", err) + case tdns.MSignMechanismDNS: + // TODO: implement DNS-based hello + return fmt.Errorf("DNS-based hello not implemented") } - fmt.Printf("Received response: %s\n", string(body)) return nil } diff --git a/tdns/delegation_utils.go b/tdns/delegation_utils.go index 2c5feab..616db01 100644 --- a/tdns/delegation_utils.go +++ b/tdns/delegation_utils.go @@ -566,7 +566,7 @@ func (zd *ZoneData) DnskeysChangedNG(newzd *ZoneData) (bool, error) { } if oldapex == nil { - log.Printf("DDCNG: Zone %s old apexdata was nil. This is the initial zone load.", zd.ZoneName) + log.Printf("DnskeysChanged: Zone %s old apexdata was nil. This is the initial zone load.", zd.ZoneName) return true, nil // on initial load, we always return false, nil, nil as we don't know that the DNSKEYs have changed } @@ -616,7 +616,7 @@ func (zd *ZoneData) MsignerChanged(newzd *ZoneData) (bool, *MultiSignerSyncStatu } if oldapex == nil { - log.Printf("DDCNG: Zone %s old apexdata was nil. This is the initial zone load.", zd.ZoneName) + log.Printf("MsignerChanged: Zone %s old apexdata was nil. This is the initial zone load.", zd.ZoneName) mss.MsignerAdds = newmsigner.RRs return true, &mss, nil // on initial load, we always return true, nil, nil to force a reset of the MSIGNER group } diff --git a/tdns/dnsutils.go b/tdns/dnsutils.go index 66ce1ce..9e18424 100644 --- a/tdns/dnsutils.go +++ b/tdns/dnsutils.go @@ -337,8 +337,6 @@ func (zd *ZoneData) ParseZoneFromReader(r io.Reader, force bool) (bool, uint32, var err error - // log.Printf("ReadZoneData: all RRs parsed") - if err = zp.Err(); err != nil { zd.Logger.Printf("ParseZoneFromReader: Error from ZoneParser: %v", err) return false, 0, err diff --git a/tdns/csync_ops.go b/tdns/ops_csync.go similarity index 100% rename from tdns/csync_ops.go rename to tdns/ops_csync.go diff --git a/tdns/dnskey_ops.go b/tdns/ops_dnskey.go similarity index 100% rename from tdns/dnskey_ops.go rename to tdns/ops_dnskey.go diff --git a/tdns/dsync_ops.go b/tdns/ops_dsync.go similarity index 100% rename from tdns/dsync_ops.go rename to tdns/ops_dsync.go diff --git a/tdns/key_ops.go b/tdns/ops_key.go similarity index 100% rename from tdns/key_ops.go rename to tdns/ops_key.go diff --git a/tdns/ops_svcb.go b/tdns/ops_svcb.go new file mode 100644 index 0000000..8882fed --- /dev/null +++ b/tdns/ops_svcb.go @@ -0,0 +1,113 @@ +/* + * Copyright (c) Johan Stenstam, johan.stenstam@internetstiftelsen.se + */ +package tdns + +import ( + "fmt" + "log" + + "github.com/miekg/dns" +) + +func (zd *ZoneData) PublishSvcbRR(name string, port uint16, value []dns.SVCBKeyValue) error { + + svcb := dns.SVCB{ + Priority: 1, + Target: dns.Fqdn(name), + Value: value, + } + svcb.Hdr = dns.RR_Header{ + Name: name, + Rrtype: dns.TypeSVCB, + Class: dns.ClassINET, + Ttl: 120, + } + + // if port != 0 { + // e := new(dns.SVCBPort) + // e.Port = port + // svcb.Value = append(svcb.Value, e) + // } + + // for k, v := range params { + // switch k { + // case "ipv4hint", "ipv6hint": + // var e string + // if k == "ipv4hint" { + // e = new(dns.SVCBIPv4Hint) + // } else if k == "ipv6hint" { + // e = new(dns.SVCBIPv6Hint) + // } + // ip := net.ParseIP(v) + // e.Hint = []net.IP{ip} + // svcb.Value = append(svcb.Value, e) + // } + // } + + log.Printf("PublishSVCBRR: publishing SVCB RR: %s", svcb.String()) + + if zd.KeyDB.UpdateQ == nil { + return fmt.Errorf("PublishSVCBRR: KeyDB.UpdateQ is nil") + } + + zd.KeyDB.UpdateQ <- UpdateRequest{ + Cmd: "ZONE-UPDATE", + ZoneName: zd.ZoneName, + Actions: []dns.RR{&svcb}, + InternalUpdate: true, + } + + return nil +} + +func (zd *ZoneData) UnpublishSvcbRR(name string) error { + anti_svcb_rr, err := dns.NewRR(fmt.Sprintf("%s 0 IN SVCB 1 0 %s", name, name)) + if err != nil { + return err + } + anti_svcb_rr.Header().Class = dns.ClassANY // XXX: dns.NewRR fails to parse a CLASS ANY SVCB RRset, so we set the class manually. + + zd.KeyDB.UpdateQ <- UpdateRequest{ + Cmd: "ZONE-UPDATE", + ZoneName: zd.ZoneName, + Actions: []dns.RR{anti_svcb_rr}, + InternalUpdate: true, + } + + return nil +} + +func LookupSVCB(name string) (*RRset, error) { + clientConfig, err := dns.ClientConfigFromFile("/etc/resolv.conf") + if err != nil { + return nil, fmt.Errorf("failed to load DNS client configuration: %v", err) + } + + m := new(dns.Msg) + m.SetQuestion(dns.Fqdn(name), dns.TypeSVCB) + c := new(dns.Client) + r, _, err := c.Exchange(m, clientConfig.Servers[0]+":53") + if err != nil { + return nil, fmt.Errorf("failed to lookup %s SVCB record: %v", name, err) + } + if len(r.Answer) == 0 { + return nil, fmt.Errorf("no %s SVCB records found", name) + } + + // var svcbRecords []*dns.SVCB + var rrset RRset + for _, ans := range r.Answer { + if svcb, ok := ans.(*dns.SVCB); ok { + rrset.RRs = append(rrset.RRs, svcb) + continue + } + if rrsig, ok := ans.(*dns.RRSIG); ok { + if rrsig.TypeCovered == dns.TypeSVCB { + rrset.RRSIGs = append(rrset.RRSIGs, rrsig) + } + continue + } + } + return &rrset, nil +} diff --git a/tdns/tlsa_ops.go b/tdns/ops_tlsa.go similarity index 100% rename from tdns/tlsa_ops.go rename to tdns/ops_tlsa.go diff --git a/tdns/deleg_rr.go b/tdns/rr_deleg.go similarity index 100% rename from tdns/deleg_rr.go rename to tdns/rr_deleg.go diff --git a/tdns/dsync_rr.go b/tdns/rr_dsync.go similarity index 100% rename from tdns/dsync_rr.go rename to tdns/rr_dsync.go diff --git a/tdns/msigner_rr.go b/tdns/rr_msigner.go similarity index 75% rename from tdns/msigner_rr.go rename to tdns/rr_msigner.go index e501529..8773408 100644 --- a/tdns/msigner_rr.go +++ b/tdns/rr_msigner.go @@ -21,19 +21,19 @@ func init() { const TypeMSIGNER = 0x0F9C type MSIGNER struct { - State uint8 // 0=OFF, 1=ON - Scheme MsignerScheme // 1=DNS, 2=API + State uint8 // 0=OFF, 1=ON + Mechanism MsignerMechanism // 1=DNS, 2=API // Port uint16 Target string } -type MsignerScheme uint8 +type MsignerMechanism uint8 const ( - MSignSchemeDNS = 1 - MSignSchemeAPI = 2 - MSignStateON = 1 - MSignStateOFF = 0 + MSignMechanismDNS = 1 + MSignMechanismAPI = 2 + MSignStateON = 1 + MSignStateOFF = 0 ) var StateToString = map[uint8]string{ @@ -46,35 +46,37 @@ var StringToState = map[string]uint8{ "OFF": MSignStateOFF, } -var MsignerSchemeToString = map[MsignerScheme]string{ - MSignSchemeDNS: "DNS", - MSignSchemeAPI: "API", +var MsignerMechanismToString = map[MsignerMechanism]string{ + MSignMechanismDNS: "DNS", + MSignMechanismAPI: "API", } -var StringToMsignerScheme = map[string]MsignerScheme{ - "DNS": MSignSchemeDNS, - "API": MSignSchemeAPI, +var StringToMsignerMechanism = map[string]MsignerMechanism{ + "DNS": MSignMechanismDNS, + "API": MSignMechanismAPI, } func NewMSIGNER() dns.PrivateRdata { return new(MSIGNER) } func (rd MSIGNER) String() string { // return fmt.Sprintf("%s\t%s %d %s", StateToString[rd.State], MsignerSchemeToString[rd.Scheme], rd.Port, rd.Target) - return fmt.Sprintf("%s\t%s %s", StateToString[rd.State], MsignerSchemeToString[rd.Scheme], rd.Target) + return fmt.Sprintf("%s\t%s %s", StateToString[rd.State], MsignerMechanismToString[rd.Mechanism], rd.Target) } func (rd *MSIGNER) Parse(txt []string) error { - if len(txt) != 4 { - return errors.New("MSIGNER requires a state, a scheme, a port and a target") + // if len(txt) != 4 { + // return errors.New("MSIGNER requires a state, a scheme, a port and a target") + if len(txt) != 3 { + return errors.New("MSIGNER requires a state, a sync mechanism and a target") } state, exist := StringToState[txt[0]] if !exist { return fmt.Errorf("invalid MSIGNER type: %s.", txt[0]) } - scheme, exist := StringToMsignerScheme[txt[1]] + mechanism, exist := StringToMsignerMechanism[txt[1]] if !exist { - return fmt.Errorf("invalid MSIGNER scheme: %s.", txt[1]) + return fmt.Errorf("invalid MSIGNER sync mechanism: %s.", txt[1]) } // port, err := strconv.Atoi(txt[2]) @@ -82,13 +84,13 @@ func (rd *MSIGNER) Parse(txt []string) error { // return fmt.Errorf("invalid MSIGNER port: %s. Error: %v", txt[2], err) // } - tgt := dns.Fqdn(txt[3]) + tgt := dns.Fqdn(txt[2]) if _, ok := dns.IsDomainName(tgt); !ok { return fmt.Errorf("invalid MSIGNER target: %s.", txt[2]) } rd.State = state - rd.Scheme = scheme + rd.Mechanism = mechanism // rd.Port = uint16(port) rd.Target = tgt @@ -102,7 +104,7 @@ func (rd *MSIGNER) Pack(buf []byte) (int, error) { return off, err } - off, err = packUint8(uint8(rd.Scheme), buf, off) + off, err = packUint8(uint8(rd.Mechanism), buf, off) if err != nil { return off, err } @@ -137,7 +139,7 @@ func (rd *MSIGNER) Unpack(buf []byte) (int, error) { if err != nil { return off, err } - rd.Scheme = MsignerScheme(tmp) + rd.Mechanism = MsignerMechanism(tmp) if off == len(buf) { return off, nil } @@ -166,7 +168,7 @@ func (rd *MSIGNER) Copy(dest dns.PrivateRdata) error { d := dest.(*MSIGNER) d.State = rd.State - d.Scheme = rd.Scheme + d.Mechanism = rd.Mechanism // d.Port = rd.Port d.Target = rd.Target return nil diff --git a/tdns/notify_rr.go b/tdns/rr_notify.go similarity index 100% rename from tdns/notify_rr.go rename to tdns/rr_notify.go From 348ee0a92b0f0891c7c0a4f03157ad4659360414 Mon Sep 17 00:00:00 2001 From: Johan Stenstam Date: Sun, 24 Nov 2024 01:00:10 +0100 Subject: [PATCH 3/5] * implemented creation of synthetic automatic zones at the sidecar identity names for both API and DNS mechanisms. * implemented automatic generation and publication of the needed TLSA, SVCB, KEY and A/AAAA records for both API and DNS mechanisms. Note that while these zones (at the identity names) are created automatically, this doesn't mean that there must be delegations pointing to them. It is mostly to get the correct and updated DNS records created. If it is unsuitable to have these as separate zones then the records are there so that they may be inserted into other zones. * A bunch of additional integration fallout between TDNS and MUSIC. --- music/config.go | 114 +++++++++++++++++----------------- music/syncengine.go | 3 + sidecar-cli/cmd/root.go | 5 +- sidecar/apiserver.go | 14 +++-- sidecar/main.go | 3 +- tdns/cli/commands.go | 2 +- tdns/cli/keystore_cmds.go | 6 +- tdns/cli/truststore_cmds.go | 4 +- tdns/cli/zone_cmds.go | 4 +- tdns/cli/zone_dsync_cmds.go | 4 +- tdns/delegation_sync.go | 118 +++++++++++++++++++++--------------- tdns/dnsutils.go | 13 ++-- tdns/global.go | 1 + tdns/ops_a_aaaa.go | 77 +++++++++++++++++++++++ tdns/queryresponder.go | 1 + tdns/refreshengine.go | 3 +- tdns/rr_print.go | 32 +++++----- tdns/zone_updater.go | 21 +++++-- tdns/zone_utils.go | 63 ++++++++++++++++++- 19 files changed, 335 insertions(+), 153 deletions(-) create mode 100644 tdns/ops_a_aaaa.go diff --git a/music/config.go b/music/config.go index b1d9747..81b18d5 100644 --- a/music/config.go +++ b/music/config.go @@ -12,8 +12,10 @@ import ( "log" "net" "os" + "slices" "github.com/go-playground/validator/v10" + "github.com/gookit/goutil/dump" tdns "github.com/johanix/tdns/tdns" "github.com/miekg/dns" "github.com/spf13/viper" @@ -46,6 +48,8 @@ type SidecarApiConf struct { Port uint16 Cert string Key string + CertData string + KeyData string } type SidecarDnsConf struct { @@ -270,6 +274,13 @@ func LoadMusicConfig(mconf *Config, appMode string, safemode bool) error { return err } + log.Printf("LoadMusicConfig: unmarshalling MUSIC config into struct") + err = viper.Unmarshal(&mconf) + if err != nil { + log.Fatalf("Error unmarshalling MUSIC config into struct: %v", err) + } + // dump.P(mconf) + TokVip = viper.New() var tokenfile string if viper.GetString("common.tokenfile") != "" { @@ -294,43 +305,42 @@ func LoadMusicConfig(mconf *Config, appMode string, safemode bool) error { func LoadSidecarConfig(mconf *Config, all_zones []string) error { log.Printf("loadSidecarConfig: enter") - // mconf.Sidecar.Identity = viper.GetString("music.sidecar.identity") - // if mconf.Sidecar.Identity == "" { - // return errors.New("LoadSidecarConfig: sidecar identity not set in config file") - // } mconf.Sidecar.Api.Identity = dns.Fqdn(mconf.Sidecar.Api.Identity) mconf.Sidecar.Dns.Identity = dns.Fqdn(mconf.Sidecar.Dns.Identity) - mconf.Sidecar.Api.Addresses = viper.GetStringSlice("music.sidecar.api.addresses") - mconf.Sidecar.Dns.Addresses = viper.GetStringSlice("music.sidecar.dns.addresses") - - mconf.Sidecar.Api.Port = uint16(viper.GetInt("music.sidecar.api.port")) - mconf.Sidecar.Dns.Port = uint16(viper.GetInt("music.sidecar.dns.port")) - if len(mconf.Sidecar.Api.Addresses) == 0 && len(mconf.Sidecar.Dns.Addresses) == 0 { return errors.New("LoadSidecarConfig: neither sidecar syncapi nor syncdns addresses set in config file") } - certFile := viper.GetString("music.sidecar.api.cert") - keyFile := viper.GetString("music.sidecar.api.key") + certFile := viper.GetString("sidecar.api.cert") + keyFile := viper.GetString("sidecar.api.key") + + if mconf.Sidecar.Api.Identity != "." { + if !slices.Contains(all_zones, mconf.Sidecar.Api.Identity) { + log.Printf("LoadSidecarConfig: Zone %s not found, creating a minimal auto zone", mconf.Sidecar.Api.Identity) + zd, err := mconf.Internal.KeyDB.CreateAutoZone(mconf.Sidecar.Api.Identity) + if err != nil { + return fmt.Errorf("LoadSidecarConfig: failed to create minimal auto zone for sidecar DNS identity '%s': %v", mconf.Sidecar.Dns.Identity, err) + } + zd.MultiSignerSyncQ = mconf.Internal.MultiSignerSyncQ + } - if mconf.Sidecar.Api.Identity != "" { if certFile == "" || keyFile == "" { return errors.New("LoadSidecarConfig: Sidecar API identity defined, but cert or key file not set in config file") } - certPEM, err := os.ReadFile(certFile) + certPEM, err := os.ReadFile(mconf.Sidecar.Api.Cert) if err != nil { return fmt.Errorf("LoadSidecarConfig: error reading cert file: %v", err) } - keyPEM, err := os.ReadFile(keyFile) + keyPEM, err := os.ReadFile(mconf.Sidecar.Api.Key) if err != nil { return fmt.Errorf("LoadSidecarConfig: error reading key file: %v", err) } - mconf.Sidecar.Api.Cert = string(certPEM) - mconf.Sidecar.Api.Key = string(keyPEM) + mconf.Sidecar.Api.CertData = string(certPEM) + mconf.Sidecar.Api.KeyData = string(keyPEM) block, _ := pem.Decode(certPEM) if block == nil { @@ -348,14 +358,17 @@ func LoadSidecarConfig(mconf *Config, all_zones []string) error { // Compare the CN with the expected CN if certCN != mconf.Sidecar.Api.Identity { + log.Printf("LoadSidecarConfig: Error: mconf.Sidecar.Api.Identity: %s viper: %v", mconf.Sidecar.Api.Identity, viper.GetString("sidecar.api.identity")) + dump.P(mconf.Sidecar) return fmt.Errorf("LoadSidecarConfig: Error: Sidecar certificate CN '%s' does not match sidecar identity '%s'", certCN, mconf.Sidecar.Api.Identity) } log.Printf("LoadSidecarConfig: cert CN '%s' matches sidecar identity '%s'", certCN, mconf.Sidecar.Api.Identity) ur := tdns.UpdateRequest{ - Cmd: "DEFERRED-UPDATE", - ZoneName: mconf.Sidecar.Api.Identity, + Cmd: "DEFERRED-UPDATE", + ZoneName: mconf.Sidecar.Api.Identity, + Description: fmt.Sprintf("Publish TLSA+SVCB RRs for sidecar API identity '%s'", mconf.Sidecar.Api.Identity), PreCondition: func() bool { _, ok := tdns.Zones.Get(mconf.Sidecar.Api.Identity) if !ok { @@ -416,20 +429,13 @@ func LoadSidecarConfig(mconf *Config, all_zones []string) error { log.Printf("LoadSidecarConfig: Successfully published SVCB RR for sidecar API identity '%s'\n", mconf.Sidecar.Api.Identity) - apex, _ := zd.Data.Get(zd.ZoneName) - if err != nil { - return fmt.Errorf("LoadSidecarConfig: Error: failed to get zone apex %s: %v", zd.ZoneName, err) - } - - tlsarr_rrset := apex.RRtypes.GetOnlyRRSet(dns.TypeTLSA) - var tlsarr *dns.TLSA - if len(tlsarr_rrset.RRs) > 0 { - tlsarr = tlsarr_rrset.RRs[0].(*dns.TLSA) - log.Printf("LoadSidecarConfig: TLSA RR: %s", tlsarr.String()) - } else { - log.Printf("LoadSidecarConfig: Error: TLSA: %v", tlsarr_rrset) - return fmt.Errorf("LoadSidecarConfig: Error: TLSA: %v", tlsarr_rrset) + for _, addr := range mconf.Sidecar.Api.Addresses { + err = zd.PublishAddrRR(mconf.Sidecar.Api.Identity, addr) + if err != nil { + return fmt.Errorf("LoadSidecarConfig: failed to publish address RR: %v", err) + } } + log.Printf("LoadSidecarConfig: Successfully published address RRs for sidecar API identity '%s'\n", mconf.Sidecar.Api.Identity) return nil }, } @@ -438,13 +444,21 @@ func LoadSidecarConfig(mconf *Config, all_zones []string) error { } - if mconf.Sidecar.Dns.Identity != "" { + if mconf.Sidecar.Dns.Identity != "." { - // 1. Check whether we already have a SIG(0) key pair for this sidecar. See tdns.DelegationSyncSetup() + if !slices.Contains(all_zones, mconf.Sidecar.Dns.Identity) { + log.Printf("LoadSidecarConfig: Zone %s not found, creating a minimal auto zone", mconf.Sidecar.Dns.Identity) + zd, err := mconf.Internal.KeyDB.CreateAutoZone(mconf.Sidecar.Dns.Identity) + if err != nil { + return fmt.Errorf("LoadSidecarConfig: failed to create minimal auto zone for sidecar DNS identity '%s': %v", mconf.Sidecar.Dns.Identity, err) + } + zd.MultiSignerSyncQ = mconf.Internal.MultiSignerSyncQ + } ur := tdns.UpdateRequest{ - Cmd: "DEFERRED-UPDATE", - ZoneName: mconf.Sidecar.Dns.Identity, + Cmd: "DEFERRED-UPDATE", + ZoneName: mconf.Sidecar.Dns.Identity, + Description: fmt.Sprintf("Publish SVCB + SIG(0) KEY RR for sidecar DNS identity '%s'", mconf.Sidecar.Dns.Identity), PreCondition: func() bool { _, ok := tdns.Zones.Get(mconf.Sidecar.Dns.Identity) if !ok { @@ -467,10 +481,10 @@ func LoadSidecarConfig(mconf *Config, all_zones []string) error { log.Printf("LoadSidecarConfig: publishing KEY RR for sidecar identity '%s' SIG(0) public key", mconf.Sidecar.Dns.Identity) - // err = zd.PublishKeyRRs(mconf.Sidecar.Dns.Identity, 0, mconf.Sidecar.Dns.Identity) - // if err != nil { - // return fmt.Errorf("LoadSidecarConfig: failed to publish TLSA RR: %v", err) - // } + err = zd.MusicSig0KeyPrep(mconf.Sidecar.Dns.Identity, zd.KeyDB) + if err != nil { + return fmt.Errorf("LoadSidecarConfig: failed to publish KEY RR for sidecar DNS identity '%s' SIG(0) public key: %v", mconf.Sidecar.Dns.Identity, err) + } log.Printf("LoadSidecarConfig: Successfully published KEY RR for sidecar DNS identity '%s' SIG(0) public key\n", mconf.Sidecar.Dns.Identity) @@ -500,28 +514,20 @@ func LoadSidecarConfig(mconf *Config, all_zones []string) error { } log.Printf("LoadSidecarConfig: publishing SVCB RR for sidecar DNS identity '%s'", mconf.Sidecar.Dns.Identity) - err = zd.PublishSvcbRR(mconf.Sidecar.Dns.Identity, mconf.Sidecar.Dns.Port, value) if err != nil { return fmt.Errorf("LoadSidecarConfig: failed to publish SVCB RR: %v", err) } - log.Printf("LoadSidecarConfig: Successfully published SVCB RR for sidecar identity '%s'\n", mconf.Sidecar.Dns.Identity) - apex, _ := zd.Data.Get(zd.ZoneName) - if err != nil { - return fmt.Errorf("LoadSidecarConfig: Error: failed to get zone apex %s: %v", zd.ZoneName, err) + for _, addr := range mconf.Sidecar.Dns.Addresses { + err = zd.PublishAddrRR(mconf.Sidecar.Dns.Identity, addr) + if err != nil { + return fmt.Errorf("LoadSidecarConfig: failed to publish address RR: %v", err) + } } + log.Printf("LoadSidecarConfig: Successfully published address RRs for sidecar DNS identity '%s'\n", mconf.Sidecar.Dns.Identity) - tlsarr_rrset := apex.RRtypes.GetOnlyRRSet(dns.TypeTLSA) - var tlsarr *dns.TLSA - if len(tlsarr_rrset.RRs) > 0 { - tlsarr = tlsarr_rrset.RRs[0].(*dns.TLSA) - log.Printf("LoadSidecarConfig: TLSA RR: %s", tlsarr.String()) - } else { - log.Printf("LoadSidecarConfig: Error: TLSA: %v", tlsarr_rrset) - return fmt.Errorf("LoadSidecarConfig: Error: TLSA: %v", tlsarr_rrset) - } return nil }, } diff --git a/music/syncengine.go b/music/syncengine.go index 401e91a..774ffb5 100644 --- a/music/syncengine.go +++ b/music/syncengine.go @@ -220,8 +220,11 @@ func MaybeSendHello(sidecarId map[tdns.MsignerMechanism]string, sidecars, wannab if msrr, ok := prr.Data.(*tdns.MSIGNER); ok { remoteMechanism := msrr.Mechanism remoteSidecar := msrr.Target + // log.Printf("MaybeSendHello: remoteSidecar: %s, remoteMechanism: %s, sidecarId: %s", remoteSidecar, tdns.MsignerMechanismToString[remoteMechanism], sidecarId[remoteMechanism]) if remoteSidecar == sidecarId[remoteMechanism] { // we don't need to send a hello to ourselves + log.Printf("MaybeSendHello: remoteSidecar [%s][%s] is ourselves (%s), no need to talk to ourselves", + tdns.MsignerMechanismToString[remoteMechanism], remoteSidecar, sidecarId[remoteMechanism]) continue } if _, exists := sidecars[remoteMechanism][remoteSidecar]; !exists { diff --git a/sidecar-cli/cmd/root.go b/sidecar-cli/cmd/root.go index 5ed5445..907d8bb 100644 --- a/sidecar-cli/cmd/root.go +++ b/sidecar-cli/cmd/root.go @@ -32,9 +32,8 @@ func init() { rootCmd.PersistentFlags().BoolVarP(&tdns.Globals.Verbose, "verbose", "v", false, "Verbose output") rootCmd.PersistentFlags().BoolVarP(&tdns.Globals.Debug, "debug", "d", false, "Debugging output") - rootCmd.PersistentFlags().BoolVarP(&mcmd.Showheaders, "headers", "H", false, "Show column headers on output") - rootCmd.PersistentFlags().StringVarP(&mcmd.Zonename, "zone", "z", "", "name of zone") + rootCmd.PersistentFlags().BoolVarP(&tdns.Globals.ShowHeaders, "headers", "H", false, "Show column headers on output") + rootCmd.PersistentFlags().StringVarP(&tdns.Globals.Zonename, "zone", "z", "", "name of zone") rootCmd.PersistentFlags().StringVarP(&mcmd.Signername, "signer", "s", "", "name of signer") rootCmd.PersistentFlags().StringVarP(&mcmd.Sgroupname, "group", "g", "", "name of signer group") } - diff --git a/sidecar/apiserver.go b/sidecar/apiserver.go index bd0963e..9ddba14 100644 --- a/sidecar/apiserver.go +++ b/sidecar/apiserver.go @@ -6,7 +6,9 @@ package main import ( + "fmt" "log" + "net" "net/http" "github.com/johanix/tdns/music" @@ -105,12 +107,13 @@ func MusicSetupRouter(tconf *tdns.Config, mconf *music.Config) *mux.Router { // This is the sidecar-to-sidecar sync API dispatcher. func MusicAPIdispatcher(tconf *tdns.Config, mconf *music.Config, done <-chan struct{}) error { - log.Printf("MusicAPIdispatcher: starting with sidecar ID %s", mconf.Internal.SidecarId) + log.Printf("MusicAPIdispatcher: starting with sidecar ID '%s'", mconf.Sidecar.Api.Identity) router := MusicSetupRouter(tconf, mconf) - addresses := viper.GetStringSlice("music.sidecar.syncapi.addresses") - certFile := viper.GetString("apiserver.certFile") - keyFile := viper.GetString("apiserver.keyFile") + addresses := mconf.Sidecar.Api.Addresses + port := mconf.Sidecar.Api.Port + certFile := mconf.Sidecar.Api.Cert + keyFile := mconf.Sidecar.Api.Key if len(addresses) == 0 { log.Println("MusicAPIdispatcher: no addresses to listen on. Not starting.") return nil @@ -123,7 +126,8 @@ func MusicAPIdispatcher(tconf *tdns.Config, mconf *music.Config, done <-chan str for idx, address := range addresses { log.Printf("Starting API dispatcher #%d. Listening on %s\n", idx, address) go func(address string) { - log.Fatal(http.ListenAndServeTLS(address, certFile, keyFile, router)) + addr := net.JoinHostPort(address, fmt.Sprintf("%d", port)) + log.Fatal(http.ListenAndServeTLS(addr, certFile, keyFile, router)) }(string(address)) log.Println("API dispatcher: unclear how to stop the http server nicely.") diff --git a/sidecar/main.go b/sidecar/main.go index 307aec4..6d07983 100644 --- a/sidecar/main.go +++ b/sidecar/main.go @@ -126,7 +126,8 @@ func main() { // Load MUSIC config; note that this must be after the TDNS config has been parsed and use viper.MergeConfig() music.LoadMusicConfig(&mconf, tconf.AppMode, false) // on initial startup a config error should cause an abort. - // mconf.Internal = music.InternalConf{} + + // dump.P(mconf.Sidecar) logfile := viper.GetString("log.file") err = tdns.SetupLogging(logfile) diff --git a/tdns/cli/commands.go b/tdns/cli/commands.go index 0f3e569..a31714d 100644 --- a/tdns/cli/commands.go +++ b/tdns/cli/commands.go @@ -23,7 +23,7 @@ var StopCmd = &cobra.Command{ }, } -var showhdr, showfile, shownotify, showprimary bool +var showfile, shownotify, showprimary bool func init() { } diff --git a/tdns/cli/keystore_cmds.go b/tdns/cli/keystore_cmds.go index b4cae53..19ee822 100644 --- a/tdns/cli/keystore_cmds.go +++ b/tdns/cli/keystore_cmds.go @@ -211,8 +211,6 @@ func init() { KeystoreCmd.AddCommand(keystoreDnssecListCmd, keystoreDnssecDeleteCmd, keystoreDnssecSetStateCmd) - KeystoreCmd.PersistentFlags().BoolVarP(&showhdr, "showhdr", "H", false, "Show column headers") - keystoreSig0AddCmd.Flags().StringVarP(&filename, "file", "f", "", "Name of file containing either pub or priv SIG(0) data") keystoreSig0ImportCmd.Flags().StringVarP(&filename, "file", "f", "", "Name of file containing either pub or priv SIG(0) data") keystoreSig0ImportCmd.MarkFlagRequired("file") @@ -317,7 +315,7 @@ func Sig0KeyMgmt(cmd string) error { switch cmd { case "list": var out, tmplist []string - if showhdr { + if tdns.Globals.ShowHeaders { out = append(out, "Signer|State|KeyID|Algorithm|PrivKey|KEY Record") } if len(tr.Sig0keys) > 0 { @@ -418,7 +416,7 @@ func DnssecKeyMgmt(cmd string) error { tmp[0], v.State, tmp[1], v.Flags, v.Algorithm, v.PrivateKey, v.Keystr)) } sort.Strings(out) - if showhdr { + if tdns.Globals.ShowHeaders { out = append([]string{"Signer|State|KeyID|Flags|Algorithm|PrivKey|DNSKEY Record"}, out...) } diff --git a/tdns/cli/truststore_cmds.go b/tdns/cli/truststore_cmds.go index d19c1f0..2abf922 100644 --- a/tdns/cli/truststore_cmds.go +++ b/tdns/cli/truststore_cmds.go @@ -103,7 +103,7 @@ func init() { truststoreSig0Cmd.AddCommand(truststoreSig0AddCmd, truststoreSig0DeleteCmd, truststoreSig0ListCmd, truststoreSig0TrustCmd, truststoreSig0UntrustCmd) - TruststoreCmd.PersistentFlags().BoolVarP(&showhdr, "showhdr", "H", false, "Show column headers") + // TruststoreCmd.PersistentFlags().BoolVarP(&showhdr, "showhdr", "H", false, "Show column headers") truststoreSig0DeleteCmd.Flags().IntVarP(&keyid, "keyid", "", 0, "Key ID of key to delete") truststoreSig0TrustCmd.PersistentFlags().IntVarP(&keyid, "keyid", "", 0, "Keyid of child SIG(0) key to change trust for") @@ -131,7 +131,7 @@ func Sig0TrustMgmt(subcommand string) error { } if subcommand == "list" { var out, tmplist []string - if showhdr { + if tdns.Globals.ShowHeaders { out = append(out, "Signer|KeyID|Validated|Trusted|Source|Record") } if len(tr.ChildSig0keys) > 0 { diff --git a/tdns/cli/zone_cmds.go b/tdns/cli/zone_cmds.go index 9e7d6fb..11cb07f 100644 --- a/tdns/cli/zone_cmds.go +++ b/tdns/cli/zone_cmds.go @@ -249,7 +249,7 @@ var zoneListCmd = &cobra.Command{ } hdr += "Frozen|Dirty|Options" out := []string{} - if showhdr { + if tdns.Globals.ShowHeaders { out = append(out, hdr) } for zname, zconf := range cr.Zones { @@ -308,7 +308,7 @@ func init() { ZoneCmd.PersistentFlags().BoolVarP(&force, "force", "F", false, "force operation") - zoneListCmd.Flags().BoolVarP(&showhdr, "headers", "H", false, "Show column headers") + // zoneListCmd.Flags().BoolVarP(&showhdr, "headers", "H", false, "Show column headers") zoneListCmd.Flags().BoolVarP(&showfile, "file", "f", false, "Show zone input file") zoneListCmd.Flags().BoolVarP(&shownotify, "notify", "N", false, "Show zone downstream notify addresses") zoneListCmd.Flags().BoolVarP(&showprimary, "primary", "P", false, "Show zone primary nameserver") diff --git a/tdns/cli/zone_dsync_cmds.go b/tdns/cli/zone_dsync_cmds.go index 6aa65b9..36db194 100644 --- a/tdns/cli/zone_dsync_cmds.go +++ b/tdns/cli/zone_dsync_cmds.go @@ -50,7 +50,7 @@ var zoneDsyncStatusCmd = &cobra.Command{ out = append(out, fmt.Sprintf("%s|%s", key, status)) } sort.Strings(out) - if showhdr { + if tdns.Globals.ShowHeaders { out = append([]string{"Function|Status"}, out...) } fmt.Printf("%s\n", columnize.SimpleFormat(out)) @@ -170,7 +170,7 @@ func init() { ZoneCmd.AddCommand(zoneDsyncCmd) zoneDsyncCmd.AddCommand(zoneDsyncStatusCmd, zoneDsyncBootstrapCmd, zoneDsyncRollKeyCmd, zoneDsyncPublishCmd, zoneDsyncUnpublishCmd) - zoneDsyncCmd.PersistentFlags().BoolVarP(&showhdr, "showhdr", "H", false, "Show headers") + // zoneDsyncCmd.PersistentFlags().BoolVarP(&showhdr, "showhdr", "H", false, "Show headers") zoneDsyncRollKeyCmd.PersistentFlags().StringVarP(&tdns.Globals.Algorithm, "algorithm", "a", "ED25519", "Algorithm to use for the new SIG(0) key") zoneDsyncBootstrapCmd.PersistentFlags().StringVarP(&tdns.Globals.Algorithm, "algorithm", "a", "ED25519", "Algorithm to use for the new SIG(0) key") zoneDsyncRollKeyCmd.PersistentFlags().StringVarP(&rollaction, "rollaction", "r", "complete", "[debug] Phase of the rollover to perform: complete, add, remove, update-local") diff --git a/tdns/delegation_sync.go b/tdns/delegation_sync.go index bef138f..28e24e4 100644 --- a/tdns/delegation_sync.go +++ b/tdns/delegation_sync.go @@ -157,54 +157,88 @@ func (kdb *KeyDB) DelegationSyncher(delsyncq chan DelegationSyncRequest, notifyq } func (zd *ZoneData) DelegationSyncSetup(kdb *KeyDB) error { - log.Printf("DelegationSyncSetup: setting up zone %s", zd.ZoneName) if !zd.Options[OptDelSyncChild] { log.Printf("DelegationSyncSetup: Zone %s does not have child-side delegation sync enabled. Skipping.", zd.ZoneName) return nil } - log.Printf("DelegationSyncSetup: Checking whether zone %s allows updates and if so has a KEY RRset published.", zd.ZoneName) - apex, _ := zd.GetOwner(zd.ZoneName) - _, keyrrexist := apex.RRtypes.Get(dns.TypeKEY) + algstr := viper.GetString("delegationsync.child.update.keygen.algorithm") + alg := dns.StringToAlgorithm[strings.ToUpper(algstr)] + if alg == 0 { + log.Printf("Sig0KeyPreparation: Unknown keygen algorithm: \"%s\", using ED25519", algstr) + alg = dns.ED25519 + } - if keyrrexist && !zd.Options[OptDontPublishKey] { - err := zd.VerifyPublishedKeyRRs() - if err != nil { - zd.Logger.Printf("Error from VerifyPublishedKeyRRs(%s): %v", zd.ZoneName, err) - return err + err := zd.Sig0KeyPreparation(zd.ZoneName, alg, kdb) + if err != nil { + zd.Logger.Printf("DelegationSyncSetup: Zone %s: Error from Sig0KeyPreparation(): %v", zd.ZoneName, err) + return err + } + + // 4. There is a KEY RRset, we have tried to sign it if possible. But has it been uploaded to the parent? + // XXX: This is a bit of a hack, but we need to bootstrap the parent with the child's SIG(0) key. In the future + // we should keep state of whether successful key bootstrapping has been done or not in the keystore. + msg, err, ur := zd.BootstrapSig0KeyWithParent(alg) + if err != nil { + log.Printf("DelegationSyncSetup: Zone %s: Error from BootstrapSig0KeyWithParent(): %v.", zd.ZoneName, err) + for _, tes := range ur.TargetStatus { + log.Printf("DelegationSyncSetup: Zone %s: TargetUpdateStatus: %v", zd.ZoneName, tes) } - zd.Logger.Printf("DelegationSyncSetup: Zone %s: Verified published KEY RRset", zd.ZoneName) + return err } + log.Printf("DelegationSyncSetup: Zone %s: SIG(0) key bootstrap: %s", zd.ZoneName, msg) + return nil +} +func (zd *ZoneData) MusicSig0KeyPrep(name string, kdb *KeyDB) error { algstr := viper.GetString("delegationsync.child.update.keygen.algorithm") alg := dns.StringToAlgorithm[strings.ToUpper(algstr)] if alg == 0 { - log.Printf("DelegationSyncSetup: Unknown keygen algorithm: \"%s\", using ED25519", algstr) + log.Printf("Sig0KeyPreparation: Unknown keygen algorithm: \"%s\", using ED25519", algstr) alg = dns.ED25519 } + return zd.Sig0KeyPreparation(name, alg, kdb) +} + +func (zd *ZoneData) Sig0KeyPreparation(name string, alg uint8, kdb *KeyDB) error { + log.Printf("Sig0KeyPreparation: setting up SIG(0) key pair for zone %s", name) + + log.Printf("Sig0KeyPreparation: Checking whether zone %s allows updates and if so has a KEY RRset published.", name) + apex, _ := zd.GetOwner(zd.ZoneName) + _, keyrrexist := apex.RRtypes.Get(dns.TypeKEY) + + if keyrrexist && !zd.Options[OptDontPublishKey] { + err := zd.VerifyPublishedKeyRRs() + if err != nil { + zd.Logger.Printf("Error from VerifyPublishedKeyRRs(%s): %v", name, err) + return err + } + zd.Logger.Printf("Sig0KeyPreparation: Zone %s: Verified published KEY RRset", name) + } + // 1. Are updates to the zone data allowed? if !zd.Options[OptAllowUpdates] { if keyrrexist { - log.Printf("DelegationSyncSetup: Zone %s does not allow updates, but a KEY RRset is already published in the zone.", zd.ZoneName) + log.Printf("Sig0KeyPreparation: Zone %s does not allow updates, but a KEY RRset is already published in the zone.", name) } else { - log.Printf("DelegationSyncSetup: Zone %s does not allow updates. Cannot publish a KEY RRset.", zd.ZoneName) + log.Printf("Sig0KeyPreparation: Zone %s does not allow updates. Cannot publish a KEY RRset.", name) } return nil } - log.Printf("DelegationSyncSetup: Zone %s allows updates. KEY RR exist: %v, dont-publish-key: %v", zd.ZoneName, keyrrexist, zd.Options[OptDontPublishKey]) + log.Printf("Sig0KeyPreparation: Zone %s allows updates. KEY RR exist: %v, dont-publish-key: %v", name, keyrrexist, zd.Options[OptDontPublishKey]) // 2. Updates allowed, but there is no KEY RRset published. if !keyrrexist && !zd.Options[OptDontPublishKey] { - log.Printf("DelegationSyncSetup: Fetching the private SIG(0) key for %s", zd.ZoneName) + log.Printf("Sig0KeyPreparation: Fetching the private SIG(0) key for %s", name) sak, err := kdb.GetSig0Keys(zd.ZoneName, Sig0StateActive) if err != nil { - log.Printf("DelegationSyncSetup: Error from kdb.GetSig0Keys(%s, %s): %v. Parent sync via UPDATE not possible.", zd.ZoneName, Sig0StateActive, err) + log.Printf("Sig0KeyPreparation: Error from kdb.GetSig0Keys(%s, %s): %v. Parent sync via UPDATE not possible.", name, Sig0StateActive, err) return err } if len(sak.Keys) == 0 { - log.Printf("DelegationSyncSetup: No active SIG(0) key found for zone %s. Will generate new key to enable parent sync via UPDATE.", zd.ZoneName) + log.Printf("Sig0KeyPreparation: No active SIG(0) key found for zone %s. Will generate new key to enable parent sync via UPDATE.", name) kp := KeystorePost{ Command: "sig0-mgmt", @@ -216,37 +250,21 @@ func (zd *ZoneData) DelegationSyncSetup(kdb *KeyDB) error { } resp, err := zd.KeyDB.Sig0KeyMgmt(nil, kp) if err != nil { - return fmt.Errorf("DelegationSyncSetup(%s) failed to generate keypair: %v", zd.ZoneName, err) + return fmt.Errorf("Sig0KeyPreparation(%s) failed to generate keypair: %v", name, err) } zd.Logger.Printf(resp.Msg) sak, err = zd.KeyDB.GetSig0Keys(zd.ZoneName, Sig0StateActive) if err != nil { - return fmt.Errorf("DelegationSyncSetup(%s, after key generation) failed to get SIG(0) active keys: %v", zd.ZoneName, err) + return fmt.Errorf("Sig0KeyPreparation(%s, after key generation) failed to get SIG(0) active keys: %v", name, err) } - - // algstr := viper.GetString("delegationsync.child.update.keygen.algorithm") - // alg := dns.StringToAlgorithm[strings.ToUpper(algstr)] - // if alg == 0 { - // log.Printf("DelegationSynchSetup: Unknown keygen algorithm: \"%s\"", algstr) - // return fmt.Errorf("unknown keygen algorithm: %s", algstr) - // } - // pkc, msg, err := kdb.GenerateKeypair(zd.ZoneName, "del-sync", "active", dns.TypeKEY, alg, "", nil) // nil = no tx - // if err != nil { - // zd.Logger.Printf("Error from kdb.GeneratePrivateKey(%s, KEY, %s): %v", zd.ZoneName, algstr, err) - // return err - // } - // zd.Logger.Printf("DelegationSynchSetup: %s", msg) - // sak = &Sig0ActiveKeys{ - // Keys: []*PrivateKeyCache{pkc}, - // } } if len(sak.Keys) == 0 { - log.Printf("DelegationSyncSetup: No active SIG(0) key found for zone %s. Parent sync via UPDATE not possible.", zd.ZoneName) - return fmt.Errorf("no active SIG(0) key found for zone %s. Parent sync via UPDATE not possible.", zd.ZoneName) + log.Printf("Sig0KeyPreparation: No active SIG(0) key found for zone %s. Parent sync via UPDATE not possible.", name) + return fmt.Errorf("no active SIG(0) key found for zone %s. Parent sync via UPDATE not possible.", name) } - log.Printf("DelegationSyncSetup: Publishing KEY RR for zone %s", zd.ZoneName) + log.Printf("Sig0KeyPreparation: Publishing KEY RR for zone %s", name) err = zd.PublishKeyRRs(sak) if err != nil { log.Printf("DelegationSyncSetup: Error from PublishKeyRRs(): %s", err) @@ -258,7 +276,7 @@ func (zd *ZoneData) DelegationSyncSetup(kdb *KeyDB) error { // 3. There is a KEY RRset, question is whether it is signed or not if keyrrexist && zd.Options[OptOnlineSigning] { - log.Printf("DelegationSyncSetup: Fetching the private DNSSEC key for %s in prep for signing KEY RRset", zd.ZoneName) + log.Printf("Sig0KeyPreparation: Fetching the private DNSSEC key for %s in prep for signing KEY RRset", name) // dak, err := kdb.GetDnssecActiveKeys(zd.ZoneName) // if err != nil { // log.Printf("DelegationSyncher: Error from kdb.GetDnssecActiveKeys(%s): %v. Parent sync via UPDATE not possible.", zd.ZoneName, err) @@ -273,21 +291,23 @@ func (zd *ZoneData) DelegationSyncSetup(kdb *KeyDB) error { log.Printf("Successfully signed %s KEY RRset", zd.ZoneName) } } else { - log.Printf("DelegationSyncSetup: Zone %s does not allow online signing, KEY RRset cannot be re-signed", zd.ZoneName) + log.Printf("Sig0KeyPreparation: Zone %s does not allow online signing, KEY RRset cannot be re-signed", name) } + // This moved to DelegationSyncSetup() above: + // 4. There is a KEY RRset, we have tried to sign it if possible. But has it been uploaded to the parent? // XXX: This is a bit of a hack, but we need to bootstrap the parent with the child's SIG(0) key. In the future // we should keep state of whether successful key bootstrapping has been done or not in the keystore. - msg, err, ur := zd.BootstrapSig0KeyWithParent(alg) - if err != nil { - log.Printf("DelegationSyncSetup: Zone %s: Error from BootstrapSig0KeyWithParent(): %v.", zd.ZoneName, err) - for _, tes := range ur.TargetStatus { - log.Printf("DelegationSyncSetup: Zone %s: TargetUpdateStatus: %v", zd.ZoneName, tes) - } - return err - } - log.Printf("DelegationSyncSetup: Zone %s: SIG(0) key bootstrap: %s", zd.ZoneName, msg) + // msg, err, ur := zd.BootstrapSig0KeyWithParent(alg) + // if err != nil { + // log.Printf("DelegationSyncSetup: Zone %s: Error from BootstrapSig0KeyWithParent(): %v.", zd.ZoneName, err) + // for _, tes := range ur.TargetStatus { + // log.Printf("DelegationSyncSetup: Zone %s: TargetUpdateStatus: %v", zd.ZoneName, tes) + // } + // return err + // } + // log.Printf("DelegationSyncSetup: Zone %s: SIG(0) key bootstrap: %s", zd.ZoneName, msg) return nil } diff --git a/tdns/dnsutils.go b/tdns/dnsutils.go index 9e18424..53e50ed 100644 --- a/tdns/dnsutils.go +++ b/tdns/dnsutils.go @@ -338,13 +338,13 @@ func (zd *ZoneData) ParseZoneFromReader(r io.Reader, force bool) (bool, uint32, var err error if err = zp.Err(); err != nil { - zd.Logger.Printf("ParseZoneFromReader: Error from ZoneParser: %v", err) + zd.Logger.Printf("ParseZoneFromReader: Zone %s: Error from ZoneParser: %v", zd.ZoneName, err) return false, 0, err } apex, _ := zd.Data.Get(zd.ZoneName) if err != nil { - return false, 0, fmt.Errorf("ReadZoneData: Error: failed to get zone apex %s", zd.ZoneName) + return false, 0, fmt.Errorf("ParseZoneFromReader: Zone %s: Error: failed to get zone apex %s", zd.ZoneName, err) } soa_rrset := apex.RRtypes.GetOnlyRRSet(dns.TypeSOA) @@ -352,17 +352,16 @@ func (zd *ZoneData) ParseZoneFromReader(r io.Reader, force bool) (bool, uint32, if len(soa_rrset.RRs) > 0 { soa = soa_rrset.RRs[0].(*dns.SOA) } else { - log.Printf("ReadZoneData: Error: SOA: %v", soa_rrset) - return false, 0, fmt.Errorf("Error loading zone %s from data", zd.ZoneName) + log.Printf("ParseZoneFromReader: Zone %s: Error: SOA: %v", zd.ZoneName, soa_rrset) + return false, 0, fmt.Errorf("ParseZoneFromReader: Zone %s: Error: SOA: %v", zd.ZoneName, soa_rrset) } zd.CurrentSerial = soa.Serial zd.IncomingSerial = soa.Serial if err := zp.Err(); err != nil { - zd.Logger.Printf("ReadZoneData: Error from ZoneParser(%s): %v", - zd.ZoneName, err) - return false, soa.Serial, fmt.Errorf("Error from ZoneParser: %v", err) + zd.Logger.Printf("ParseZoneFromReader: Zone %s: Error from ZoneParser: %v", zd.ZoneName, err) + return false, soa.Serial, fmt.Errorf("ParseZoneFromReader: Zone %s: Error from ZoneParser: %v", zd.ZoneName, err) } zd.ComputeIndices() diff --git a/tdns/global.go b/tdns/global.go index 9a5e689..d215e2c 100644 --- a/tdns/global.go +++ b/tdns/global.go @@ -23,6 +23,7 @@ type GlobalStuff struct { AppMode string AppVersion string AppDate string + ShowHeaders bool // -H in various CLI commands } var Globals = GlobalStuff{ diff --git a/tdns/ops_a_aaaa.go b/tdns/ops_a_aaaa.go new file mode 100644 index 0000000..34eecfa --- /dev/null +++ b/tdns/ops_a_aaaa.go @@ -0,0 +1,77 @@ +/* + * Copyright (c) Johan Stenstam, johan.stenstam@internetstiftelsen.se + */ +package tdns + +import ( + "fmt" + "net" + + "github.com/miekg/dns" +) + +func (zd *ZoneData) PublishAddrRR(name, addr string) error { + var rr dns.RR + + if ip := net.ParseIP(addr); ip != nil { + if ip.To4() != nil { + rr = &dns.A{ + Hdr: dns.RR_Header{ + Name: name, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + Ttl: 120, + }, + A: ip, + } + } else { + rr = &dns.AAAA{ + Hdr: dns.RR_Header{ + Name: name, + Rrtype: dns.TypeAAAA, + Class: dns.ClassINET, + Ttl: 120, + }, + AAAA: ip, + } + } + } else { + return fmt.Errorf("invalid IP address: %s", addr) + } + + zd.KeyDB.UpdateQ <- UpdateRequest{ + Cmd: "ZONE-UPDATE", + ZoneName: zd.ZoneName, + Actions: []dns.RR{rr}, + InternalUpdate: true, + } + + return nil +} + +func (zd *ZoneData) UnpublishAddrRR(name, addr string) error { + var rr dns.RR + var err error + + if ip := net.ParseIP(addr); ip != nil { + if ip.To4() != nil { + rr, err = dns.NewRR(fmt.Sprintf("%s 0 ANY A 0", name)) + } else { + rr, err = dns.NewRR(fmt.Sprintf("%s 0 ANY AAAA 0", name)) + } + if err != nil { + return err + } + } else { + return fmt.Errorf("invalid IP address: %s", addr) + } + + zd.KeyDB.UpdateQ <- UpdateRequest{ + Cmd: "ZONE-UPDATE", + ZoneName: zd.ZoneName, + Actions: []dns.RR{rr}, + InternalUpdate: true, + } + + return nil +} diff --git a/tdns/queryresponder.go b/tdns/queryresponder.go index 9e4945f..b077d24 100644 --- a/tdns/queryresponder.go +++ b/tdns/queryresponder.go @@ -39,6 +39,7 @@ func (zd *ZoneData) ApexResponder(w dns.ResponseWriter, r *dns.Msg, qname string if err != nil || apex == nil { if err != nil { log.Printf("ApexResponder: failed to get apex data for zone %s: %v", zd.ZoneName, err) + return err } else { log.Printf("ApexResponder: failed to get apex data for zone %s", zd.ZoneName) } diff --git a/tdns/refreshengine.go b/tdns/refreshengine.go index f2e70a2..836de6e 100644 --- a/tdns/refreshengine.go +++ b/tdns/refreshengine.go @@ -123,8 +123,7 @@ func RefreshEngine(conf *Config, stopch chan struct{}, appMode string) { updated, err = zd.Refresh(Globals.Verbose, Globals.Debug, zr.Force) if err != nil { - log.Printf("RefreshEngine: Error from zone refresh(%s): %v", - zone, err) + log.Printf("RefreshEngine: Error from zone refresh(%s): %v", zone, err) continue // cannot do much else // return // terminate goroutine } diff --git a/tdns/rr_print.go b/tdns/rr_print.go index 801be47..43b5351 100644 --- a/tdns/rr_print.go +++ b/tdns/rr_print.go @@ -14,7 +14,7 @@ import ( ) // leftpad = amount of white space instead of the domain name on continuation lines during multiline output -func KeyRRPrint(rr dns.RR, rrtype, ktype string, keyid uint16, leftpad, rightmargin int) { +func PrintKeyRR(rr dns.RR, rrtype, ktype string, keyid uint16, leftpad, rightmargin int) { if leftpad == 0 { leftpad = len(fmt.Sprintf("%s %d", rr.Header().Name, rr.Header().Ttl)) } @@ -46,7 +46,7 @@ func KeyRRPrint(rr dns.RR, rrtype, ktype string, keyid uint16, leftpad, rightmar fmt.Printf("%s ; %s alg = %s ; key id = %d\n", spaces, ktype, algstr, keyid) } -func RrsigRRPrint(rr dns.RR, leftpad, rightmargin int) { +func PrintRrsigRR(rr dns.RR, leftpad, rightmargin int) { if leftpad == 0 { leftpad = len(fmt.Sprintf("%s %d", rr.Header().Name, rr.Header().Ttl)) } @@ -76,7 +76,7 @@ func RrsigRRPrint(rr dns.RR, leftpad, rightmargin int) { } } -func SvcbRRPrint(rr dns.RR, leftpad, rightmargin int) { +func PrintSvcbRR(rr dns.RR, leftpad, rightmargin int) { if leftpad == 0 { leftpad = len(fmt.Sprintf("%s %d", rr.Header().Name, rr.Header().Ttl)) } @@ -96,7 +96,7 @@ func SvcbRRPrint(rr dns.RR, leftpad, rightmargin int) { } } -func SoaRRPrint(rr dns.RR, leftpad, rightmargin int) { +func PrintSoaRR(rr dns.RR, leftpad, rightmargin int) { if leftpad == 0 { leftpad = len(fmt.Sprintf("%s %d", rr.Header().Name, rr.Header().Ttl)) } @@ -116,7 +116,7 @@ func SoaRRPrint(rr dns.RR, leftpad, rightmargin int) { } -func GenericRRPrint(rr dns.RR, leftpad, rightmargin int) { +func PrintGenericRR(rr dns.RR, leftpad, rightmargin int) { // fmt.Printf("%s\n", rr.String()) p := strings.Fields(rr.String()) namepad := strings.Repeat(" ", leftpad-len(p[0])-len(p[1])) @@ -182,22 +182,22 @@ func ZoneTransferPrint(zname, upstream string, serial uint32, ttype uint16, opti case *dns.KEY: keyid := rr.(*dns.KEY).KeyTag() t := "" - KeyRRPrint(rr, "KEY", t, keyid, leftpad, rightmargin) + PrintKeyRR(rr, "KEY", t, keyid, leftpad, rightmargin) case *dns.DNSKEY: keyid := rr.(*dns.DNSKEY).KeyTag() t := " ZSK ;" if rr.(*dns.DNSKEY).Flags == 257 { t = " KSK ;" } - KeyRRPrint(rr, "DNSKEY", t, keyid, leftpad, rightmargin) + PrintKeyRR(rr, "DNSKEY", t, keyid, leftpad, rightmargin) case *dns.RRSIG: - RrsigRRPrint(rr, leftpad, rightmargin) + PrintRrsigRR(rr, leftpad, rightmargin) case *dns.SVCB, *dns.PrivateRR: switch rr.Header().Rrtype { case TypeDELEG, dns.TypeSVCB: - SvcbRRPrint(rr, leftpad, rightmargin) + PrintSvcbRR(rr, leftpad, rightmargin) default: // fmt.Printf("This is a %s RR\n", dns.TypeToString[rr.Header().Rrtype]) @@ -208,7 +208,7 @@ func ZoneTransferPrint(zname, upstream string, serial uint32, ttype uint16, opti } case *dns.SOA: - SoaRRPrint(rr, leftpad, rightmargin) + PrintSoaRR(rr, leftpad, rightmargin) default: p := strings.Fields(rr.String()) @@ -313,20 +313,20 @@ func PrintRR(rr dns.RR, leftpad int, options map[string]string) { switch rr.(type) { case *dns.SOA: - SoaRRPrint(rr, leftpad, 78) + PrintSoaRR(rr, leftpad, 78) case *dns.DNSKEY: t := " ZSK ;" if rr.(*dns.DNSKEY).Flags&0x0001 == 1 { t = " KSK ;" } - KeyRRPrint(rr, "DNSKEY", t, rr.(*dns.DNSKEY).KeyTag(), leftpad, 78) + PrintKeyRR(rr, "DNSKEY", t, rr.(*dns.DNSKEY).KeyTag(), leftpad, 78) case *dns.KEY: - KeyRRPrint(rr, "KEY", "", rr.(*dns.KEY).KeyTag(), leftpad, 78) + PrintKeyRR(rr, "KEY", "", rr.(*dns.KEY).KeyTag(), leftpad, 78) case *dns.RRSIG: - RrsigRRPrint(rr, leftpad, 78) + PrintRrsigRR(rr, leftpad, 78) case *dns.SVCB: - SvcbRRPrint(rr, leftpad, 78) + PrintSvcbRR(rr, leftpad, 78) default: - GenericRRPrint(rr, leftpad, 78) + PrintGenericRR(rr, leftpad, 78) } } diff --git a/tdns/zone_updater.go b/tdns/zone_updater.go index 762e3d3..e68b022 100644 --- a/tdns/zone_updater.go +++ b/tdns/zone_updater.go @@ -61,7 +61,7 @@ func (kdb *KeyDB) ZoneUpdaterEngine(stopchan chan struct{}) error { var deferredUpdates []DeferredUpdate - var runQueueTicker = time.NewTicker(60 * time.Second) + var runQueueTicker = time.NewTicker(10 * time.Second) var ur UpdateRequest @@ -77,8 +77,8 @@ func (kdb *KeyDB) ZoneUpdaterEngine(stopchan chan struct{}) error { continue } zd, ok := Zones.Get(ur.ZoneName) - if !ok { - log.Printf("ZoneUpdater: Zone name \"%s\" in request for update is unknown. Ignored.", ur.ZoneName) + if !ok && ur.Cmd != "DEFERRED-UPDATE" { + log.Printf("ZoneUpdater: Cmd=%s: Zone name \"%s\" in request for update is unknown. Ignored.", ur.Cmd, ur.ZoneName) continue } @@ -91,6 +91,8 @@ func (kdb *KeyDB) ZoneUpdaterEngine(stopchan chan struct{}) error { AddTime: time.Now(), } deferredUpdates = append(deferredUpdates, du) + continue + case "CHILD-UPDATE": // This is the case where a DNS UPDATE contains updates to child delegation information. // Either we are the primary (in which case we have the ability to directly modify the contents of the zone), @@ -216,16 +218,27 @@ func (kdb *KeyDB) ZoneUpdaterEngine(stopchan chan struct{}) error { log.Printf("ZoneUpdater: Request for update of type %s is completed.", ur.Cmd) case <-runQueueTicker.C: + if len(deferredUpdates) == 0 { + continue + } + log.Printf("ZoneUpdater: running deferred updates queue (%d items).", len(deferredUpdates)) - for _, du := range deferredUpdates { + for i := 0; i < len(deferredUpdates); { + du := deferredUpdates[i] + log.Printf("ZoneUpdater: running deferred update \"%s\"", du.Description) ok := du.PreCondition() if ok { err := du.Action() if err != nil { log.Printf("ZoneUpdater: Error from deferred update action: %v", err) + i++ + } else { + // Remove the item from deferredUpdates queue + deferredUpdates = append(deferredUpdates[:i], deferredUpdates[i+1:]...) } } else { log.Printf("ZoneUpdater: Deferred update \"%s\" not executed because precondition failed.", du.Description) + i++ } } } diff --git a/tdns/zone_utils.go b/tdns/zone_utils.go index 638c074..00a0e78 100644 --- a/tdns/zone_utils.go +++ b/tdns/zone_utils.go @@ -795,7 +795,12 @@ func (zd *ZoneData) FetchChildDelegationData(childname string) (*ChildDelegation } func (zd *ZoneData) SetupZoneSync(delsyncq chan DelegationSyncRequest) error { - zd.Logger.Printf("SetupZoneSync(%s)", zd.ZoneName) + wantsSync := zd.Options[OptDelSyncParent] || zd.Options[OptDelSyncChild] + if !wantsSync { + zd.Logger.Printf("SetupZoneSync: Zone %s does not require delegation sync", zd.ZoneName) + return nil + } + zd.Logger.Printf("SetupZoneSync: Zone %s requires delegation sync", zd.ZoneName) apex, err := zd.GetOwner(zd.ZoneName) if err != nil { zd.Logger.Printf("Error from GetOwner(%s): %v", zd.ZoneName, err) @@ -981,3 +986,59 @@ func (zd *ZoneData) DelegationData() (*DelegationData, error) { } return &dd, nil } + +func (kdb *KeyDB) CreateAutoZone(zonename string) (*ZoneData, error) { + log.Printf("CreateAutoZone: Zone %s enter", zonename) + + // Create a fake zone for the sidecar identity just to be able to + // to use to generate the TLSA. + tmpl := ` +$ORIGIN %s +$TTL 86400 +%s IN SOA ns1.%s hostmaster.%s ( + 2021010101 ; serial + 3600 ; refresh (1 hour) + 1800 ; retry (30 minutes) + 1209600 ; expire (2 weeks) + 86400 ; minimum (1 day) + ) +%s IN NS ns1.%s +ns1.%s IN A 192.0.2.1 +` + zonedatastr := strings.ReplaceAll(tmpl, "%s", zonename) + + log.Printf("CreateAutoZone: template zone data:\n%s\n", zonedatastr) + + zd := &ZoneData{ + ZoneName: zonename, + ZoneStore: MapZone, + Logger: log.Default(), + ZoneType: Primary, + Options: map[ZoneOption]bool{OptAllowUpdates: true}, + KeyDB: kdb, + } + + log.Printf("CreateAutoZone: reading zone data for zone '%s'", zonename) + _, _, err := zd.ReadZoneData(zonedatastr, false) + if err != nil { + return nil, fmt.Errorf("failed to read zone data: %v", err) + } + + zd.Ready = true + + // log.Printf("CreateAutoZone: zone data for zone '%s':", zonename) + // for ownerName, ownerData := range zd.Owners { + // fmt.Printf("Owner: %s\n", ownerName) + // for _, rrType := range ownerData.RRtypes.Keys() { + // rrset, _ := ownerData.RRtypes.Get(rrType) + // fmt.Printf(" RR Type: %s\n", dns.TypeToString[rrType]) + // for _, rr := range rrset.RRs { + // fmt.Printf(" %s\n", rr.String()) + // } + // } + // } + + Zones.Set(zonename, zd) + + return zd, nil +} From 7a51f5a52f494f47caf51a03c728b2f1e1ae3fa2 Mon Sep 17 00:00:00 2001 From: Johan Stenstam Date: Sun, 24 Nov 2024 14:50:14 +0100 Subject: [PATCH 4/5] * Implemented automatic DNSSEC key generation and signing for the sidecar auto zones. --- music/config.go | 34 ++++++++++++++++++++++++++-------- sidecar/apiserver.go | 12 ++++++------ sidecar/main.go | 13 +++++++------ tdns/cli/keystore_cmds.go | 5 ++--- tdns/parseconfig.go | 7 ++++++- tdns/sign.go | 2 ++ tdns/zone_utils.go | 10 +++++++--- 7 files changed, 56 insertions(+), 27 deletions(-) diff --git a/music/config.go b/music/config.go index 81b18d5..48dc758 100644 --- a/music/config.go +++ b/music/config.go @@ -303,7 +303,7 @@ func LoadMusicConfig(mconf *Config, appMode string, safemode bool) error { return nil } -func LoadSidecarConfig(mconf *Config, all_zones []string) error { +func LoadSidecarConfig(mconf *Config, tconf *tdns.Config, all_zones []string) error { log.Printf("loadSidecarConfig: enter") mconf.Sidecar.Api.Identity = dns.Fqdn(mconf.Sidecar.Api.Identity) mconf.Sidecar.Dns.Identity = dns.Fqdn(mconf.Sidecar.Dns.Identity) @@ -317,12 +317,10 @@ func LoadSidecarConfig(mconf *Config, all_zones []string) error { if mconf.Sidecar.Api.Identity != "." { if !slices.Contains(all_zones, mconf.Sidecar.Api.Identity) { - log.Printf("LoadSidecarConfig: Zone %s not found, creating a minimal auto zone", mconf.Sidecar.Api.Identity) - zd, err := mconf.Internal.KeyDB.CreateAutoZone(mconf.Sidecar.Api.Identity) + _, err := mconf.SetupSidecarAutoZone(mconf.Sidecar.Api.Identity, tconf) if err != nil { - return fmt.Errorf("LoadSidecarConfig: failed to create minimal auto zone for sidecar DNS identity '%s': %v", mconf.Sidecar.Dns.Identity, err) + return fmt.Errorf("LoadSidecarConfig: failed to create minimal auto zone for sidecar API identity '%s': %v", mconf.Sidecar.Api.Identity, err) } - zd.MultiSignerSyncQ = mconf.Internal.MultiSignerSyncQ } if certFile == "" || keyFile == "" { @@ -447,12 +445,10 @@ func LoadSidecarConfig(mconf *Config, all_zones []string) error { if mconf.Sidecar.Dns.Identity != "." { if !slices.Contains(all_zones, mconf.Sidecar.Dns.Identity) { - log.Printf("LoadSidecarConfig: Zone %s not found, creating a minimal auto zone", mconf.Sidecar.Dns.Identity) - zd, err := mconf.Internal.KeyDB.CreateAutoZone(mconf.Sidecar.Dns.Identity) + _, err := mconf.SetupSidecarAutoZone(mconf.Sidecar.Dns.Identity, tconf) if err != nil { return fmt.Errorf("LoadSidecarConfig: failed to create minimal auto zone for sidecar DNS identity '%s': %v", mconf.Sidecar.Dns.Identity, err) } - zd.MultiSignerSyncQ = mconf.Internal.MultiSignerSyncQ } ur := tdns.UpdateRequest{ @@ -537,3 +533,25 @@ func LoadSidecarConfig(mconf *Config, all_zones []string) error { return nil } + +func (mconf *Config) SetupSidecarAutoZone(zonename string, tconf *tdns.Config) (*tdns.ZoneData, error) { + log.Printf("SetupSidecarAutoZone: Zone %s not found, creating a minimal auto zone", zonename) + zd, err := mconf.Internal.KeyDB.CreateAutoZone(zonename) + if err != nil { + return nil, fmt.Errorf("SetupSidecarAutoZone: failed to create minimal auto zone for sidecar DNS identity '%s': %v", zonename, err) + } + zd.Options[tdns.OptAllowUpdates] = true + zd.MultiSignerSyncQ = mconf.Internal.MultiSignerSyncQ + + // A sidecar auto zone needs to be signed by the sidecar. + if tmp, exists := tconf.Internal.DnssecPolicies["default"]; !exists { + log.Fatalf("SetupSidecarAutoZone: DnssecPolicy 'default' not defined. Default policy is required for sidecar auto zones.") + } else { + zd.DnssecPolicy = &tmp + } + + zd.Options[tdns.OptOnlineSigning] = true + zd.SetupZoneSigning(tconf.Internal.ResignQ) + + return zd, nil +} diff --git a/sidecar/apiserver.go b/sidecar/apiserver.go index 9ddba14..5799c97 100644 --- a/sidecar/apiserver.go +++ b/sidecar/apiserver.go @@ -106,8 +106,8 @@ func MusicSetupRouter(tconf *tdns.Config, mconf *music.Config) *mux.Router { } // This is the sidecar-to-sidecar sync API dispatcher. -func MusicAPIdispatcher(tconf *tdns.Config, mconf *music.Config, done <-chan struct{}) error { - log.Printf("MusicAPIdispatcher: starting with sidecar ID '%s'", mconf.Sidecar.Api.Identity) +func MusicSyncAPIdispatcher(tconf *tdns.Config, mconf *music.Config, done <-chan struct{}) error { + log.Printf("MusicSyncAPIdispatcher: starting with sidecar ID '%s'", mconf.Sidecar.Api.Identity) router := MusicSetupRouter(tconf, mconf) addresses := mconf.Sidecar.Api.Addresses @@ -115,22 +115,22 @@ func MusicAPIdispatcher(tconf *tdns.Config, mconf *music.Config, done <-chan str certFile := mconf.Sidecar.Api.Cert keyFile := mconf.Sidecar.Api.Key if len(addresses) == 0 { - log.Println("MusicAPIdispatcher: no addresses to listen on. Not starting.") + log.Println("MusicSyncAPIdispatcher: no addresses to listen on. Not starting.") return nil } if certFile == "" || keyFile == "" { - log.Println("MusicAPIdispatcher: certFile or keyFile not set. Not starting.") + log.Println("MusicSyncAPIdispatcher: certFile or keyFile not set. Not starting.") return nil } for idx, address := range addresses { - log.Printf("Starting API dispatcher #%d. Listening on %s\n", idx, address) + log.Printf("Starting MusicSyncAPI dispatcher #%d. Listening on %s\n", idx, address) go func(address string) { addr := net.JoinHostPort(address, fmt.Sprintf("%d", port)) log.Fatal(http.ListenAndServeTLS(addr, certFile, keyFile, router)) }(string(address)) - log.Println("API dispatcher: unclear how to stop the http server nicely.") + log.Println("MusicSyncAPI dispatcher: unclear how to stop the http server nicely.") } return nil } diff --git a/sidecar/main.go b/sidecar/main.go index 6d07983..97c8f52 100644 --- a/sidecar/main.go +++ b/sidecar/main.go @@ -167,7 +167,7 @@ func main() { // go func() { // time.Sleep(5 * time.Second) - err = music.LoadSidecarConfig(&mconf, all_zones) + err = music.LoadSidecarConfig(&mconf, &tconf, all_zones) if err != nil { fmt.Printf("Error loading sidecar config: %v", err) log.Fatalf("Error loading sidecar config: %v", err) @@ -176,16 +176,15 @@ func main() { apistopper := make(chan struct{}) // tconf.Internal.APIStopCh = apistopper - // sidecar mgmt API: - go APIdispatcher(&tconf, &mconf, apistopper) - // sidecar-to-sidecar sync API: - go MusicAPIdispatcher(&tconf, &mconf, apistopper) + go APIdispatcher(&tconf, &mconf, apistopper) // sidecar mgmt API: + go MusicSyncAPIdispatcher(&tconf, &mconf, apistopper) // sidecar-to-sidecar sync API: tconf.Internal.ScannerQ = make(chan tdns.ScanRequest, 5) tconf.Internal.DnsUpdateQ = make(chan tdns.DnsUpdateRequest, 100) tconf.Internal.DnsNotifyQ = make(chan tdns.DnsNotifyRequest, 100) tconf.Internal.AuthQueryQ = make(chan tdns.AuthQueryRequest, 100) + tconf.Internal.ResignQ = make(chan *tdns.ZoneData, 10) go tdns.AuthQueryEngine(tconf.Internal.AuthQueryQ) go tdns.ScannerEngine(tconf.Internal.ScannerQ, tconf.Internal.AuthQueryQ) @@ -194,7 +193,9 @@ func main() { go tdns.NotifyHandler(&tconf) go tdns.DnsEngine(&tconf) go kdb.DelegationSyncher(tconf.Internal.DelegationSyncQ, tconf.Internal.NotifyQ) - // go tdns.ResignerEngine(conf.Internal.ResignQ, make(chan struct{})) + + // The ResignerEngine is needed only for the sidecar auto zones. + go tdns.ResignerEngine(tconf.Internal.ResignQ, make(chan struct{})) mconf.Internal.EngineCheck = make(chan music.EngineCheck, 100) diff --git a/tdns/cli/keystore_cmds.go b/tdns/cli/keystore_cmds.go index 19ee822..19fe7b3 100644 --- a/tdns/cli/keystore_cmds.go +++ b/tdns/cli/keystore_cmds.go @@ -121,7 +121,7 @@ var keystoreDnssecCmd = &cobra.Command{ Use: "dnssec", Short: "Prefix command, only usable via sub-commands", Run: func(cmd *cobra.Command, args []string) { - fmt.Println("keystore dnssec called (but NYI)") + fmt.Println("keystore dnssec called (this is an empty prefix command)") }, } @@ -208,8 +208,7 @@ func init() { keystoreSig0Cmd.AddCommand(keystoreSig0ListCmd, keystoreSig0DeleteCmd, keystoreSig0SetStateCmd) keystoreDnssecCmd.AddCommand(keystoreDnssecAddCmd, keystoreDnssecImportCmd, keystoreDnssecGenerateCmd) - - KeystoreCmd.AddCommand(keystoreDnssecListCmd, keystoreDnssecDeleteCmd, keystoreDnssecSetStateCmd) + keystoreDnssecCmd.AddCommand(keystoreDnssecListCmd, keystoreDnssecDeleteCmd, keystoreDnssecSetStateCmd) keystoreSig0AddCmd.Flags().StringVarP(&filename, "file", "f", "", "Name of file containing either pub or priv SIG(0) data") keystoreSig0ImportCmd.Flags().StringVarP(&filename, "file", "f", "", "Name of file containing either pub or priv SIG(0) data") diff --git a/tdns/parseconfig.go b/tdns/parseconfig.go index c7edaf2..3d2a832 100644 --- a/tdns/parseconfig.go +++ b/tdns/parseconfig.go @@ -99,7 +99,7 @@ func ParseConfig(conf *Config, reload bool) error { log.Fatalf("Error unmarshalling config into struct: %v", err) } - if conf.AppMode == "server" { + if conf.AppMode == "server" || conf.AppMode == "sidecar" { // dump.P(conf.DnssecPolicies) if conf.Internal.DnssecPolicies == nil { conf.Internal.DnssecPolicies = make(map[string]DnssecPolicy) @@ -121,6 +121,11 @@ func ParseConfig(conf *Config, reload bool) error { conf.Internal.DnssecPolicies[name] = tmp } + if _, exists := conf.Internal.DnssecPolicies["default"]; !exists { + log.Fatalf("Error: DnssecPolicy 'default' not defined. Default policy is required.") + // return errors.New("ParseConfig: DnssecPolicy 'default' not defined. Default policy is required.") + } + // dump.P(conf.Internal.DnssecPolicies) } diff --git a/tdns/sign.go b/tdns/sign.go index dacc6c6..599df88 100644 --- a/tdns/sign.go +++ b/tdns/sign.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "github.com/gookit/goutil/dump" "github.com/miekg/dns" "github.com/spf13/viper" "golang.org/x/exp/rand" @@ -231,6 +232,7 @@ func (zd *ZoneData) SignZone(kdb *KeyDB, force bool) (int, error) { // Bummer, promoting didn't work, let's generate KSK: if len(dak.KSKs) == 0 { + dump.P(zd.DnssecPolicy) _, msg, err := kdb.GenerateKeypair(zd.ZoneName, "signzone", DnskeyStateActive, dns.TypeDNSKEY, zd.DnssecPolicy.Algorithm, "KSK", nil) // nil = no tx if err != nil { return 0, err diff --git a/tdns/zone_utils.go b/tdns/zone_utils.go index 00a0e78..d6aefba 100644 --- a/tdns/zone_utils.go +++ b/tdns/zone_utils.go @@ -860,7 +860,7 @@ func (zd *ZoneData) SetupZoneSync(delsyncq chan DelegationSyncRequest) error { return nil } -func (zd *ZoneData) SetupZoneSigning(resignq chan *ZoneData) error { +func (zd *ZoneData) SetupZoneSigning(resignq chan<- *ZoneData) error { if !zd.Options[OptOnlineSigning] { // XXX: Need to sort out whether to use the sign-zone or online-signing option return nil // this zone should not be signed (at least not by us) } @@ -886,7 +886,11 @@ func (zd *ZoneData) SetupZoneSigning(resignq chan *ZoneData) error { log.Printf("SetupZoneSigning: zone %s signed. %d new RRSIGs", zd.ZoneName, newrrsigs) - resignq <- zd + select { + case resignq <- zd: + case <-time.After(2 * time.Second): + log.Printf("SetupZoneSigning: timeout while sending zone %s to resign queue", zd.ZoneName) + } return nil } @@ -1014,7 +1018,7 @@ ns1.%s IN A 192.0.2.1 ZoneStore: MapZone, Logger: log.Default(), ZoneType: Primary, - Options: map[ZoneOption]bool{OptAllowUpdates: true}, + Options: map[ZoneOption]bool{OptAllowUpdates: true, OptOnlineSigning: true}, KeyDB: kdb, } From 33b52b1a4e4a3d1604418531db3c5457bcd34507 Mon Sep 17 00:00:00 2001 From: Johan Stenstam Date: Mon, 25 Nov 2024 14:38:50 +0100 Subject: [PATCH 5/5] * tweaking the sidecar config structure to find a workable solution * bugfix in the key generator * various debug tweaks --- music/config.go | 83 +++++++++++++++++++++++++++++++------------ music/defaults.go | 8 ++--- sidecar/apiserver.go | 4 +-- sidecar/main.go | 2 +- tdns/parseconfig.go | 15 +++++++- tdns/sig0_utils.go | 4 +-- tdns/zone_utils.go | 4 +-- utils/Makefile.common | 2 +- 8 files changed, 86 insertions(+), 36 deletions(-) diff --git a/music/config.go b/music/config.go index 48dc758..6061779 100644 --- a/music/config.go +++ b/music/config.go @@ -44,18 +44,24 @@ type SidecarConf struct { type SidecarApiConf struct { Identity string - Addresses []string - Port uint16 - Cert string - Key string - CertData string - KeyData string + Addresses struct { + Publish []string + Listen []string + } + Port uint16 + Cert string + Key string + CertData string + KeyData string } type SidecarDnsConf struct { Identity string - Addresses []string - Port uint16 + Addresses struct { + Publish []string + Listen []string + } + Port uint16 } type ZonesConf struct { @@ -196,6 +202,9 @@ var TokVip *viper.Viper var CliConf = CliConfig{} func LoadMusicConfig(mconf *Config, appMode string, safemode bool) error { + if tdns.Globals.Debug { + log.Printf("LoadMusicConfig: enter") + } var cfgfile string switch appMode { case "server": @@ -279,7 +288,7 @@ func LoadMusicConfig(mconf *Config, appMode string, safemode bool) error { if err != nil { log.Fatalf("Error unmarshalling MUSIC config into struct: %v", err) } - // dump.P(mconf) + // dump.P(mconf.Sidecar) TokVip = viper.New() var tokenfile string @@ -300,22 +309,28 @@ func LoadMusicConfig(mconf *Config, appMode string, safemode bool) error { CliConf.Verbose = viper.GetBool("common.verbose") CliConf.Debug = viper.GetBool("common.debug") + if tdns.Globals.Debug { + log.Printf("LoadMusicConfig: exit") + } return nil } func LoadSidecarConfig(mconf *Config, tconf *tdns.Config, all_zones []string) error { - log.Printf("loadSidecarConfig: enter") - mconf.Sidecar.Api.Identity = dns.Fqdn(mconf.Sidecar.Api.Identity) - mconf.Sidecar.Dns.Identity = dns.Fqdn(mconf.Sidecar.Dns.Identity) + if tdns.Globals.Debug { + log.Printf("loadSidecarConfig: enter") + } - if len(mconf.Sidecar.Api.Addresses) == 0 && len(mconf.Sidecar.Dns.Addresses) == 0 { + if len(mconf.Sidecar.Api.Addresses.Listen) == 0 && len(mconf.Sidecar.Dns.Addresses.Listen) == 0 { + // dump.P(mconf.Sidecar) return errors.New("LoadSidecarConfig: neither sidecar syncapi nor syncdns addresses set in config file") } certFile := viper.GetString("sidecar.api.cert") keyFile := viper.GetString("sidecar.api.key") - if mconf.Sidecar.Api.Identity != "." { + if mconf.Sidecar.Api.Identity != "" { + mconf.Sidecar.Api.Identity = dns.Fqdn(mconf.Sidecar.Api.Identity) + if !slices.Contains(all_zones, mconf.Sidecar.Api.Identity) { _, err := mconf.SetupSidecarAutoZone(mconf.Sidecar.Api.Identity, tconf) if err != nil { @@ -396,9 +411,13 @@ func LoadSidecarConfig(mconf *Config, tconf *tdns.Config, all_zones []string) er log.Printf("LoadSidecarConfig: Successfully published TLSA RR for sidecar identity '%s'\n", mconf.Sidecar.Api.Identity) var ipv4hint, ipv6hint []net.IP - if len(mconf.Sidecar.Api.Addresses) > 0 { - for _, addr := range mconf.Sidecar.Api.Addresses { + if len(mconf.Sidecar.Api.Addresses.Publish) > 0 { + for _, addr := range mconf.Sidecar.Api.Addresses.Publish { ip := net.ParseIP(addr) + if ip == nil { + log.Printf("LoadSidecarConfig: failed to parse address '%s'", addr) + continue + } if ip.To4() != nil { ipv4hint = append(ipv4hint, ip) } else { @@ -427,7 +446,7 @@ func LoadSidecarConfig(mconf *Config, tconf *tdns.Config, all_zones []string) er log.Printf("LoadSidecarConfig: Successfully published SVCB RR for sidecar API identity '%s'\n", mconf.Sidecar.Api.Identity) - for _, addr := range mconf.Sidecar.Api.Addresses { + for _, addr := range mconf.Sidecar.Api.Addresses.Publish { err = zd.PublishAddrRR(mconf.Sidecar.Api.Identity, addr) if err != nil { return fmt.Errorf("LoadSidecarConfig: failed to publish address RR: %v", err) @@ -442,7 +461,8 @@ func LoadSidecarConfig(mconf *Config, tconf *tdns.Config, all_zones []string) er } - if mconf.Sidecar.Dns.Identity != "." { + if mconf.Sidecar.Dns.Identity != "" { + mconf.Sidecar.Dns.Identity = dns.Fqdn(mconf.Sidecar.Dns.Identity) if !slices.Contains(all_zones, mconf.Sidecar.Dns.Identity) { _, err := mconf.SetupSidecarAutoZone(mconf.Sidecar.Dns.Identity, tconf) @@ -485,9 +505,13 @@ func LoadSidecarConfig(mconf *Config, tconf *tdns.Config, all_zones []string) er log.Printf("LoadSidecarConfig: Successfully published KEY RR for sidecar DNS identity '%s' SIG(0) public key\n", mconf.Sidecar.Dns.Identity) var ipv4hint, ipv6hint []net.IP - if len(mconf.Sidecar.Dns.Addresses) > 0 { - for _, addr := range mconf.Sidecar.Dns.Addresses { + if len(mconf.Sidecar.Dns.Addresses.Publish) > 0 { + for _, addr := range mconf.Sidecar.Dns.Addresses.Publish { ip := net.ParseIP(addr) + if ip == nil { + log.Printf("LoadSidecarConfig: failed to parse address '%s'", addr) + continue + } if ip.To4() != nil { ipv4hint = append(ipv4hint, ip) } else { @@ -516,7 +540,7 @@ func LoadSidecarConfig(mconf *Config, tconf *tdns.Config, all_zones []string) er } log.Printf("LoadSidecarConfig: Successfully published SVCB RR for sidecar identity '%s'\n", mconf.Sidecar.Dns.Identity) - for _, addr := range mconf.Sidecar.Dns.Addresses { + for _, addr := range mconf.Sidecar.Dns.Addresses.Publish { err = zd.PublishAddrRR(mconf.Sidecar.Dns.Identity, addr) if err != nil { return fmt.Errorf("LoadSidecarConfig: failed to publish address RR: %v", err) @@ -531,6 +555,10 @@ func LoadSidecarConfig(mconf *Config, tconf *tdns.Config, all_zones []string) er mconf.Internal.UpdateQ <- ur } + if tdns.Globals.Debug { + log.Printf("LoadSidecarConfig: exit") + } + return nil } @@ -544,14 +572,23 @@ func (mconf *Config) SetupSidecarAutoZone(zonename string, tconf *tdns.Config) ( zd.MultiSignerSyncQ = mconf.Internal.MultiSignerSyncQ // A sidecar auto zone needs to be signed by the sidecar. + zd.Options[tdns.OptOnlineSigning] = true if tmp, exists := tconf.Internal.DnssecPolicies["default"]; !exists { log.Fatalf("SetupSidecarAutoZone: DnssecPolicy 'default' not defined. Default policy is required for sidecar auto zones.") } else { zd.DnssecPolicy = &tmp } + err = zd.SetupZoneSigning(tconf.Internal.ResignQ) + if err != nil { + return nil, fmt.Errorf("SetupSidecarAutoZone: failed to set up zone signing for sidecar auto zone '%s': %v", zonename, err) + } - zd.Options[tdns.OptOnlineSigning] = true - zd.SetupZoneSigning(tconf.Internal.ResignQ) + // A sidecare auto zone will try to set up delegation syncing with the parent. + zd.Options[tdns.OptDelSyncChild] = true + err = zd.SetupZoneSync(tconf.Internal.DelegationSyncQ) + if err != nil { + return nil, fmt.Errorf("SetupSidecarAutoZone: failed to set up delegation syncing for sidecar auto zone '%s': %v", zonename, err) + } return zd, nil } diff --git a/music/defaults.go b/music/defaults.go index ef74336..116cc35 100644 --- a/music/defaults.go +++ b/music/defaults.go @@ -12,15 +12,15 @@ const ( ) const ( - DefaultCfgFile = "/etc/music/musicd.yaml" // used for monolithic musicd + DefaultCfgFile = "/etc/music/musicd.yaml" // used for monolithic musicd DefaultSidecarCfgFile = "/etc/music/music-sidecar.yaml" // used for everything MUSIC related in music-sidecar (together with next file) - DefaultSidecarTdnsCfgFile = "/etc/music/sidecar-tdns.yaml" // used for everything TDNS related in music-sidecar + DefaultSidecarTdnsCfgFile = "/etc/music/tdns-sidecar.yaml" // used for everything TDNS related in music-sidecar DefaultZonesCfgFile = "/etc/music/music-zones.yaml" // Zones that MUSIC sidecar should serve; may be empty ) type GlobalStuff struct { - Verbose bool - Debug bool + Verbose bool + Debug bool } var Globals GlobalStuff diff --git a/sidecar/apiserver.go b/sidecar/apiserver.go index 5799c97..c9ea4f0 100644 --- a/sidecar/apiserver.go +++ b/sidecar/apiserver.go @@ -110,7 +110,7 @@ func MusicSyncAPIdispatcher(tconf *tdns.Config, mconf *music.Config, done <-chan log.Printf("MusicSyncAPIdispatcher: starting with sidecar ID '%s'", mconf.Sidecar.Api.Identity) router := MusicSetupRouter(tconf, mconf) - addresses := mconf.Sidecar.Api.Addresses + addresses := mconf.Sidecar.Api.Addresses.Listen port := mconf.Sidecar.Api.Port certFile := mconf.Sidecar.Api.Cert keyFile := mconf.Sidecar.Api.Key @@ -124,9 +124,9 @@ func MusicSyncAPIdispatcher(tconf *tdns.Config, mconf *music.Config, done <-chan } for idx, address := range addresses { - log.Printf("Starting MusicSyncAPI dispatcher #%d. Listening on %s\n", idx, address) go func(address string) { addr := net.JoinHostPort(address, fmt.Sprintf("%d", port)) + log.Printf("Starting MusicSyncAPI dispatcher #%d. Listening on '%s'\n", idx, addr) log.Fatal(http.ListenAndServeTLS(addr, certFile, keyFile, router)) }(string(address)) diff --git a/sidecar/main.go b/sidecar/main.go index 97c8f52..ae1ce21 100644 --- a/sidecar/main.go +++ b/sidecar/main.go @@ -169,7 +169,7 @@ func main() { // time.Sleep(5 * time.Second) err = music.LoadSidecarConfig(&mconf, &tconf, all_zones) if err != nil { - fmt.Printf("Error loading sidecar config: %v", err) + fmt.Printf("Error loading sidecar config: %v\n", err) log.Fatalf("Error loading sidecar config: %v", err) } // }() diff --git a/tdns/parseconfig.go b/tdns/parseconfig.go index 3d2a832..0acddd4 100644 --- a/tdns/parseconfig.go +++ b/tdns/parseconfig.go @@ -70,7 +70,9 @@ func GenKeyLifetime(lifetime, sigvalidity string) KeyLifetime { } func ParseConfig(conf *Config, reload bool) error { - log.Printf("Enter ParseConfig") + if Globals.Debug { + log.Printf("Enter ParseConfig") + } cfgfile := conf.Internal.CfgFile if cfgfile == "" { cfgfile = DefaultCfgFile @@ -231,11 +233,18 @@ func ParseConfig(conf *Config, reload bool) error { // } ValidateConfig(nil, DefaultCfgFile) // will terminate on error + + if Globals.Debug { + log.Printf("ParseConfig: exit") + } return nil } // func ParseZones(zones map[string]tdns.ZoneConf, zrch chan tdns.ZoneRefresher) error { func ParseZones(conf *Config, zrch chan ZoneRefresher, reload bool) ([]string, error) { + if Globals.Debug { + log.Printf("ParseZones: enter") + } var all_zones []string zonescfgfile := conf.Internal.ZonesCfgFile @@ -497,6 +506,10 @@ func ParseZones(conf *Config, zrch chan ZoneRefresher, reload bool) ([]string, e ValidateZones(conf, ZonesCfgFile) // will terminate on error log.Printf("All configured zones now refreshing: %v (queued for refresh: %d zones)", all_zones, len(zrch)) + + if Globals.Debug { + log.Printf("ParseConfig: exit") + } return all_zones, nil } diff --git a/tdns/sig0_utils.go b/tdns/sig0_utils.go index 7ed3db3..0aa0452 100644 --- a/tdns/sig0_utils.go +++ b/tdns/sig0_utils.go @@ -156,12 +156,12 @@ func (kdb *KeyDB) GenerateKeypair(owner, creator, state string, rrtype uint16, a nkey.Header().Class = dns.ClassINET nkey.Header().Ttl = 3600 - log.Printf("Generated DNSKEY flags: %d", nkey.(*dns.DNSKEY).Flags) - switch rrtype { case dns.TypeKEY: + log.Printf("Generated KEY flags: %d", nkey.(*dns.KEY).Flags) privkey, err = nkey.(*dns.KEY).Generate(bits) case dns.TypeDNSKEY: + log.Printf("Generated DNSKEY flags: %d", nkey.(*dns.DNSKEY).Flags) privkey, err = nkey.(*dns.DNSKEY).Generate(bits) } if err != nil { diff --git a/tdns/zone_utils.go b/tdns/zone_utils.go index d6aefba..07995c3 100644 --- a/tdns/zone_utils.go +++ b/tdns/zone_utils.go @@ -794,13 +794,13 @@ func (zd *ZoneData) FetchChildDelegationData(childname string) (*ChildDelegation return &cdd, nil } -func (zd *ZoneData) SetupZoneSync(delsyncq chan DelegationSyncRequest) error { +func (zd *ZoneData) SetupZoneSync(delsyncq chan<- DelegationSyncRequest) error { wantsSync := zd.Options[OptDelSyncParent] || zd.Options[OptDelSyncChild] if !wantsSync { zd.Logger.Printf("SetupZoneSync: Zone %s does not require delegation sync", zd.ZoneName) return nil } - zd.Logger.Printf("SetupZoneSync: Zone %s requires delegation sync", zd.ZoneName) + zd.Logger.Printf("SetupZoneSync: Zone %s requests delegation sync", zd.ZoneName) apex, err := zd.GetOwner(zd.ZoneName) if err != nil { zd.Logger.Printf("Error from GetOwner(%s): %v", zd.ZoneName, err) diff --git a/utils/Makefile.common b/utils/Makefile.common index eb46b42..2520ac0 100644 --- a/utils/Makefile.common +++ b/utils/Makefile.common @@ -27,7 +27,7 @@ TDNS_SRC ?= /Users/johani/src/git/tdns TDNS_AWS_DIR ?= /Users/johani/sshfs/msigner1/src/tdns johaniaws: - rsync --exclude=.git --exclude-from=${TDNS_SRC}/.gitignore -avx ${TDNS_SRC}/ ${TDNS_AWS_DIR}/ + rsync --delete --exclude=.git --exclude-from=${TDNS_SRC}/.gitignore -avx ${TDNS_SRC}/ ${TDNS_AWS_DIR}/ test: $(GO) test -v -cover