From 9740f61e81c907feed57f3b245618521d8dcfb61 Mon Sep 17 00:00:00 2001 From: "Joel D. Elkins" Date: Sun, 17 Dec 2023 11:51:43 -0600 Subject: [PATCH] Add nsupdate command Will send updates to the configured dns server. To accomodate, added a global options section to the configuration as well. TODO: per-container domain name, dns server, and tsig keys Status: lightly tested development version, needs field testing --- cmd/nsupdate.go | 50 ++++++++++ go.mod | 9 +- go.sum | 21 ++-- internal/pkg/config/config.go | 15 ++- internal/pkg/container/nsupdate.go | 149 +++++++++++++++++++++++++++++ 5 files changed, 229 insertions(+), 15 deletions(-) create mode 100644 cmd/nsupdate.go create mode 100644 internal/pkg/container/nsupdate.go diff --git a/cmd/nsupdate.go b/cmd/nsupdate.go new file mode 100644 index 0000000..249b8af --- /dev/null +++ b/cmd/nsupdate.go @@ -0,0 +1,50 @@ +/* +Package cmd defines user commands accessible from the cli tool. + +Copyright © 2022-2023 Joel D. Elkins + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package cmd + +import ( + "gitea.elkins.co/Networking/ccl/internal/pkg/command" + "gitea.elkins.co/Networking/ccl/internal/pkg/config" + "gitea.elkins.co/Networking/ccl/internal/pkg/container" + "github.com/spf13/cobra" +) + +// createCmd represents the create command +var nsupdateCmd = &cobra.Command{ + Use: "nsupdate", + Short: "Update DNS records", + Args: cobra.OnlyValidArgs, + ValidArgsFunction: validNouns, + Long: `Update DNS records to identify the configured containers.`, + Run: func(_ *cobra.Command, args []string) { + conts := config.Union(args, contMask) + execForEach(conts, func(c *container.Container) command.Set { + return c.NsUpdateCommands(config.Options.DomainName, config.Options.DNSServer, config.Options.TSIGName, config.Options.TSIGKey) + }, 0) + }, +} + +func init() { + rootCmd.AddCommand(nsupdateCmd) +} diff --git a/go.mod b/go.mod index 1e0847d..7861204 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/containers/common v0.55.4 github.com/containers/podman/v4 v4.6.2 github.com/emirpasic/gods v1.18.1 + github.com/miekg/dns v1.1.50 github.com/opencontainers/runtime-spec v1.1.0 github.com/pelletier/go-toml v1.9.5 github.com/sirupsen/logrus v1.9.3 @@ -115,13 +116,13 @@ require ( go.mongodb.org/mongo-driver v1.12.1 // indirect go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/crypto v0.12.0 // indirect + golang.org/x/crypto v0.16.0 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.14.0 // indirect golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/term v0.11.0 // indirect - golang.org/x/text v0.12.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/term v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.12.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577 // indirect google.golang.org/grpc v1.57.0 // indirect diff --git a/go.sum b/go.sum index 6ab3a5d..40abdf9 100644 --- a/go.sum +++ b/go.sum @@ -651,6 +651,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182aff github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= +github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= +github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= @@ -1009,8 +1011,8 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1086,6 +1088,7 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -1184,6 +1187,7 @@ golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1195,12 +1199,12 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1212,8 +1216,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1270,6 +1274,7 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go index f82b05c..f1baea4 100644 --- a/internal/pkg/config/config.go +++ b/internal/pkg/config/config.go @@ -44,10 +44,18 @@ const ( ConfigFileDefault = "/etc/ccl.toml" // ConfigFileDefault is default configuration file path ) +type GlobalOptions struct { + DNSServer string `toml:"dns_server,omitempty"` + TSIGName string `toml:"tsig_name,omitempty"` + TSIGKey string `toml:"tsig_key,omitempty"` + DomainName string `toml:"domain_name,omitempty"` +} + var ( ConfigFile = ConfigFileDefault // ConfigFile is the path to the configuration file to use in a particular invocation. Networks = []*network.Network{} // Networks is the program-global set of congiured network.Networks structures Containers = []*container.Container{} // Containers is the program-global set of configured container.Container structures. + Options = new(GlobalOptions) // Options contains global options, mostly pertaining to dns updates now ) // Categories returns a slice of all of the distinct categories found in the @@ -139,8 +147,9 @@ func UnionNetworks(ids []string) []*network.Network { func Init(conn context.Context) error { // A parsing convenience type parse struct { - Networks []*network.Network - Containers []*container.Container + Networks []*network.Network `toml:"networks"` + Containers []*container.Container `toml:"containers"` + Options *GlobalOptions `toml:"options"` } f, err := os.ReadFile(ConfigFile) @@ -152,7 +161,7 @@ func Init(conn context.Context) error { if err != nil { return err } - Containers, Networks = p.Containers, p.Networks + Containers, Networks, Options = p.Containers, p.Networks, p.Options for i := range Containers { Containers[i].Init(conn, Networks) } diff --git a/internal/pkg/container/nsupdate.go b/internal/pkg/container/nsupdate.go new file mode 100644 index 0000000..8659e61 --- /dev/null +++ b/internal/pkg/container/nsupdate.go @@ -0,0 +1,149 @@ +package container + +import ( + "net" + "time" + + cmd "gitea.elkins.co/Networking/ccl/internal/pkg/command" + "github.com/miekg/dns" +) + +func do_reverse(rv string, dn string, server string, tsn string, tsk string) error { + ptr := dns.PTR{ + Hdr: dns.RR_Header{ + Name: rv, + Rrtype: dns.TypePTR, + Class: dns.ClassINET, + Ttl: 7200, + }, + Ptr: dn, + } + + cli := new(dns.Client) + if tsn != "" { + cli.TsigSecret = map[string]string{tsn: tsk} + } + + msg := new(dns.Msg) + msg.SetQuestion(rv, dns.TypeSOA) + resp, _, err := cli.Exchange(msg, server) + if err != nil { + return err + } + soa := resp.Ns[0].Header().Name + + // Update the reverse record + msg = new(dns.Msg) + msg.SetUpdate(soa) + msg.Ns = append(msg.Ns, &ptr) + if tsn != "" { + msg.SetTsig(tsn, dns.HmacSHA256, 300, time.Now().Unix()) + } + _, _, err = cli.Exchange(msg, server) + if err != nil { + return err + } + return nil +} + +func (c *Container) NsUpdateCommands(forward_domain string, server string, tsn string, tsk string) cmd.Set { + hostname := c.Hostname + if c.Hostname == "" { + hostname = c.Name + } + dn := dns.Fqdn(hostname + "." + forward_domain) + cmds := []cmd.Command{} + + // TODO: also iterate over c.IPv6Addresses + for i := range c.Networks { + n := &c.Networks[i] + if n.IPv6.Bool && !n.IPv6Address.IsUnspecified() { + ad := net.ParseIP(n.IPv6Address.String()) + if ad != nil { + f_6 := func() error { + aaaa := dns.AAAA{ + Hdr: dns.RR_Header{ + Name: dn, + Rrtype: dns.TypeAAAA, + Class: dns.ClassINET, + Ttl: 7200, + }, + AAAA: ad, + } + + rv, err := dns.ReverseAddr(aaaa.AAAA.String()) + if err != nil { + return err + } + + cli := new(dns.Client) + if tsn != "" { + cli.TsigSecret = map[string]string{tsn: tsk} + } + + // Update the forward record + msg := new(dns.Msg) + msg.SetUpdate(dns.Fqdn(forward_domain)) + msg.Ns = append(msg.Ns, &aaaa) + + if tsn != "" { + msg.SetTsig(tsn, dns.HmacSHA256, 300, time.Now().Unix()) + } + if _, _, err = cli.Exchange(msg, server); err != nil { + return err + } + + if err = do_reverse(rv, dn, server, tsn, tsk); err != nil { + return err + } + return nil + } + cmds = append(cmds, cmd.NewFunc("nsupate6", f_6)) + } + } + if !n.IPv4Address.IsUnspecified() { + ad := net.ParseIP(n.IPv4Address.String()) + if ad != nil { + f_4 := func() error { + a := dns.A{ + Hdr: dns.RR_Header{ + Name: dn, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + Ttl: 7200, + }, + A: ad, + } + + rv, err := dns.ReverseAddr(a.A.String()) + if err != nil { + return err + } + + cli := new(dns.Client) + if tsn != "" { + cli.TsigSecret = map[string]string{tsn: tsk} + } + + // Update the forward record + msg := new(dns.Msg) + msg.SetUpdate(dns.Fqdn(forward_domain)) + msg.Ns = append(msg.Ns, &a) + if tsn != "" { + msg.SetTsig(tsn, dns.HmacSHA256, 300, time.Now().Unix()) + } + if _, _, err = cli.Exchange(msg, server); err != nil { + return err + } + + if err = do_reverse(rv, dn, server, tsn, tsk); err != nil { + return err + } + return nil + } + cmds = append(cmds, cmd.NewFunc("nsupate4", f_4)) + } + } + } + return c.newCommandSet("NSUPDATE", cmds) +}