From 9734653440fcc25b08cb3dc6ef7471996c1e5b5e Mon Sep 17 00:00:00 2001 From: Esonhugh Date: Sat, 4 Jan 2025 02:38:54 +0800 Subject: [PATCH] update: multi 1. update service subcommands 2. add feature of pods PTR record scan --- Makefile | 14 +++++++ cmd/all/all.go | 29 +++++++++++--- cmd/neighbor/neighbor.go | 64 +++++++++++++++++++------------ cmd/service/service.go | 40 ------------------- cmd/{subnet => service}/subnet.go | 44 +++++++++++++++++++-- main.go | 1 - pkg/mutli/executor.go | 7 +++- pkg/mutli/neigbhor.go | 55 +++++++++++++++++++++++++- pkg/post/record_parser.go | 27 +++++++++++++ rootcmd_test.go | 22 +++++++++++ 10 files changed, 228 insertions(+), 75 deletions(-) rename cmd/{subnet => service}/subnet.go (55%) create mode 100644 rootcmd_test.go diff --git a/Makefile b/Makefile index 5c551c7..502b361 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,20 @@ default: build build-static check-size build: go build -o $(BUILD_DIR)/$(MAIN_PROGRAM_NAME) main.go +test: build + $(BUILD_DIR)/$(MAIN_PROGRAM_NAME) all --help + $(BUILD_DIR)/$(MAIN_PROGRAM_NAME) axfr --help + $(BUILD_DIR)/$(MAIN_PROGRAM_NAME) dns --help + $(BUILD_DIR)/$(MAIN_PROGRAM_NAME) dnssd --help + $(BUILD_DIR)/$(MAIN_PROGRAM_NAME) dnssd ptr --help + $(BUILD_DIR)/$(MAIN_PROGRAM_NAME) dnssd srv --help + $(BUILD_DIR)/$(MAIN_PROGRAM_NAME) metric --help + $(BUILD_DIR)/$(MAIN_PROGRAM_NAME) neighbor --help + $(BUILD_DIR)/$(MAIN_PROGRAM_NAME) neighbor pod --help + $(BUILD_DIR)/$(MAIN_PROGRAM_NAME) neighbor svc --help + $(BUILD_DIR)/$(MAIN_PROGRAM_NAME) whereisdns --help + $(BUILD_DIR)/$(MAIN_PROGRAM_NAME) wild --help + build-static: GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o $(BUILD_DIR)/$(MAIN_PROGRAM_NAME)-linux-static main.go upx --lzma --brute $(BUILD_DIR)/$(MAIN_PROGRAM_NAME)-linux-static diff --git a/cmd/all/all.go b/cmd/all/all.go index ab3335e..0c55a2d 100644 --- a/cmd/all/all.go +++ b/cmd/all/all.go @@ -2,6 +2,7 @@ package all import ( "net" + "strings" command "github.com/esonhugh/k8spider/cmd" "github.com/esonhugh/k8spider/define" @@ -42,18 +43,24 @@ var AllCmd = &cobra.Command{ } else { log.Errorf("Transfer failed: %v", err) } + // Service Discovery ipNets, err := pkg.ParseStringToIPNet(command.Opts.Cidr) if err != nil { log.Warnf("ParseStringToIPNet failed: %v", err) return } + podNets, err := pkg.ParseStringToIPNet(command.Opts.PodCidr) + if err != nil { + log.Warnf("ParseStringToIPNet failed: %v", err) + return + } var finalRecord define.Records if command.Opts.MultiThreadingMode { - finalRecord = RunMultiThread(ipNets, command.Opts.ThreadingNum) + finalRecord = RunMultiThread(ipNets, podNets, command.Opts.ThreadingNum) } else { - finalRecord = Run(ipNets) + finalRecord = Run(ipNets, podNets) } printer.PrintResult(finalRecord, command.Opts.OutputFile) @@ -61,19 +68,26 @@ var AllCmd = &cobra.Command{ }, } -func Run(net *net.IPNet) (finalRecord define.Records) { +func Run(net, pod *net.IPNet) (finalRecord define.Records) { var records define.Records = scanner.ScanSubnet(net) if records == nil || len(records) == 0 { log.Warnf("ScanSubnet Found Nothing") return } records = scanner.ScanSvcForPorts(records) + for r := range mutli.ScanNeighborSvc(pod, 1) { + finalRecord = append(finalRecord, r...) + } return records } -func RunMultiThread(net *net.IPNet, count int) (finalRecord define.Records) { +func RunMultiThread(net, pod *net.IPNet, count int) (finalRecord define.Records) { scan := mutli.ScanAll(net, count) - for r := range scan { + scan2 := mutli.ScanNeighborSvc(pod, count) + select { + case r := <-scan: + finalRecord = append(finalRecord, r...) + case r := <-scan2: finalRecord = append(finalRecord, r...) } return @@ -93,4 +107,9 @@ func PostRun(finalRecord define.Records) { for _, svc := range list { log.Infof("Service: %s", svc) } + log.Info("Possible Pod and service ip maps") + maps := post.PodServiceMap(finalRecord) + for svc, ips := range maps { + log.Infof("service %s has ips %s", svc, strings.Join(ips, ",")) + } } diff --git a/cmd/neighbor/neighbor.go b/cmd/neighbor/neighbor.go index fc68aa0..678820b 100644 --- a/cmd/neighbor/neighbor.go +++ b/cmd/neighbor/neighbor.go @@ -2,10 +2,8 @@ package neighbor import ( "bufio" - "fmt" "net" "os" - "strings" cmdx "github.com/esonhugh/k8spider/cmd" "github.com/esonhugh/k8spider/define" @@ -19,34 +17,40 @@ import ( var Opts = struct { NamespaceWordlist string NamespaceList []string - PodCidr string }{} func init() { + NeighborCmd.AddCommand(NeighborSvcCmd, NeighborPodCmd) cmdx.RootCmd.AddCommand(NeighborCmd) - NeighborCmd.Flags().StringVar(&Opts.NamespaceWordlist, "ns-file", "", "namespace wordlist file") - NeighborCmd.Flags().StringSliceVar(&Opts.NamespaceList, "ns", []string{}, "namespace list") - NeighborCmd.Flags().StringVarP(&Opts.PodCidr, "pod-cidr", "p", defaultPodCidr(), "pod cidr list, watch out for the network interface name, default is eth0") + NeighborPodCmd.Flags().StringVar(&Opts.NamespaceWordlist, "ns-file", "", "namespace wordlist file") + NeighborPodCmd.Flags().StringSliceVar(&Opts.NamespaceList, "ns", []string{}, "namespace list") } -func defaultPodCidr() string { - interfaces, _ := net.Interfaces() - for _, i := range interfaces { - if i.Name == "eth0" { - addrs, _ := i.Addrs() - if addrs != nil || len(addrs) > 0 { - ip := strings.Split(addrs[0].String(), "/")[0] - return fmt.Sprintf("%v/16", ip) - } +var NeighborCmd = &cobra.Command{ + Use: "neighbor", + Short: "neighbor is a tool to discover k8s neighbor pods", + Run: func(cmd *cobra.Command, args []string) { + cmd.Help() + }, +} + +var NeighborSvcCmd = &cobra.Command{ + Use: "svc", + Short: "neighbor is a tool to discover k8s neighbor pod with services", + Run: func(cmd *cobra.Command, args []string) { + ipNets, err := pkg.ParseStringToIPNet(cmdx.Opts.PodCidr) + if err != nil { + log.Warnf("ParseStringToIPNet failed: %v", err) + return } - } - return "10.0.0.1/16" + r := RunSvc(ipNets, cmdx.Opts.ThreadingNum) + printer.PrintResult(r, cmdx.Opts.OutputFile) + }, } -var NeighborCmd = &cobra.Command{ - Use: "neighbor", - Short: "neighbor is a tool to discover k8s pod and available ip in subnet (require k8s coredns with pod verified config)", - Aliases: []string{"n", "nei"}, +var NeighborPodCmd = &cobra.Command{ + Use: "pod", + Short: "neighbor is a tool to discover k8s pod and available ip in subnet (require k8s coredns with pod verified config)", Run: func(cmd *cobra.Command, args []string) { if !pkg.CheckPodVerified() { log.Fatalf("k8s coredns with pod verified config could not be set") @@ -64,17 +68,17 @@ var NeighborCmd = &cobra.Command{ } } log.Tracef("namespace list: %v", Opts.NamespaceList) - ipNets, err := pkg.ParseStringToIPNet(Opts.PodCidr) + ipNets, err := pkg.ParseStringToIPNet(cmdx.Opts.PodCidr) if err != nil { log.Warnf("ParseStringToIPNet failed: %v", err) return } - r := RunMultiThread(Opts.NamespaceList, ipNets, cmdx.Opts.ThreadingNum) + r := RunPod(Opts.NamespaceList, ipNets, cmdx.Opts.ThreadingNum) printer.PrintResult(r, cmdx.Opts.OutputFile) }, } -func RunMultiThread(ns []string, net *net.IPNet, num int) (finalRecord define.Records) { +func RunPod(ns []string, net *net.IPNet, num int) (finalRecord define.Records) { scan := mutli.ScanNeighbor(ns, net, num) for r := range scan { finalRecord = append(finalRecord, r...) @@ -85,3 +89,15 @@ func RunMultiThread(ns []string, net *net.IPNet, num int) (finalRecord define.Re } return } + +func RunSvc(net *net.IPNet, num int) (finalRecord define.Records) { + scan := mutli.ScanNeighborSvc(net, num) + for r := range scan { + finalRecord = append(finalRecord, r...) + } + if len(finalRecord) == 0 { + log.Warn("ScanSubnet Found Nothing") + return + } + return +} diff --git a/cmd/service/service.go b/cmd/service/service.go index 0650d21..6d43c33 100644 --- a/cmd/service/service.go +++ b/cmd/service/service.go @@ -1,41 +1 @@ package service - -import ( - "fmt" - - command "github.com/esonhugh/k8spider/cmd" - "github.com/esonhugh/k8spider/define" - "github.com/esonhugh/k8spider/pkg/printer" - "github.com/esonhugh/k8spider/pkg/scanner" - log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -var Opts struct { - SvcDomains []string -} - -func init() { - ServiceCmd.PersistentFlags().StringSliceVarP(&Opts.SvcDomains, "svc-domains", "s", []string{}, "service domains, like: kubernetes.default,etcd.default don't add zone like svc.cluster.local") - command.RootCmd.AddCommand(ServiceCmd) -} - -var ServiceCmd = &cobra.Command{ - Use: "service", - Aliases: []string{ - "srv", - }, - Short: "service is a tool to discover k8s services ports", - Run: func(cmd *cobra.Command, args []string) { - if command.Opts.Zone == "" || Opts.SvcDomains == nil || len(Opts.SvcDomains) == 0 { - log.Warn("zone can't empty and svc-domains can't empty") - return - } - var records define.Records - for _, domain := range Opts.SvcDomains { - records = append(records, define.Record{SvcDomain: fmt.Sprintf("%s.svc.%s", domain, command.Opts.Zone)}) - } - records = scanner.ScanSvcForPorts(records) - printer.PrintResult(records, command.Opts.OutputFile) - }, -} diff --git a/cmd/subnet/subnet.go b/cmd/service/subnet.go similarity index 55% rename from cmd/subnet/subnet.go rename to cmd/service/subnet.go index adda57a..e9a47c4 100644 --- a/cmd/subnet/subnet.go +++ b/cmd/service/subnet.go @@ -1,6 +1,7 @@ -package subnet +package service import ( + "fmt" "net" command "github.com/esonhugh/k8spider/cmd" @@ -14,11 +15,24 @@ import ( ) func init() { - command.RootCmd.AddCommand(SubNetCmd) + command.RootCmd.AddCommand(DNSSDCmd) + DNSSDCmd.AddCommand(SubNetCmd, ServiceCmd) + ServiceCmd.PersistentFlags().StringSliceVarP(&Opts.SvcDomains, "svc-domains", "s", []string{}, "service domains, like: kubernetes.default,etcd.default don't add zone like svc.cluster.local") +} + +var DNSSDCmd = &cobra.Command{ + Use: "dnssd", + Aliases: []string{ + "sd", + }, + Short: "dnssd is a subcommand to discover k8s dns-sd technique", + Run: func(cmd *cobra.Command, args []string) { + cmd.Help() + }, } var SubNetCmd = &cobra.Command{ - Use: "subnet", + Use: "ptr", Aliases: []string{ "sub", "s", @@ -64,3 +78,27 @@ func RunMultiThread(net *net.IPNet, num int) (finalRecord define.Records) { } return } + +var Opts struct { + SvcDomains []string +} + +var ServiceCmd = &cobra.Command{ + Use: "srv", + Aliases: []string{ + "service", + }, + Short: "service is a tool to discover k8s services ports", + Run: func(cmd *cobra.Command, args []string) { + if command.Opts.Zone == "" || Opts.SvcDomains == nil || len(Opts.SvcDomains) == 0 { + log.Warn("zone can't empty and svc-domains can't empty") + return + } + var records define.Records + for _, domain := range Opts.SvcDomains { + records = append(records, define.Record{SvcDomain: fmt.Sprintf("%s.svc.%s", domain, command.Opts.Zone)}) + } + records = scanner.ScanSvcForPorts(records) + printer.PrintResult(records, command.Opts.OutputFile) + }, +} diff --git a/main.go b/main.go index 57547d5..66cc9f0 100644 --- a/main.go +++ b/main.go @@ -8,7 +8,6 @@ import ( _ "github.com/esonhugh/k8spider/cmd/metrics" _ "github.com/esonhugh/k8spider/cmd/neighbor" _ "github.com/esonhugh/k8spider/cmd/service" - _ "github.com/esonhugh/k8spider/cmd/subnet" _ "github.com/esonhugh/k8spider/cmd/whereisdns" _ "github.com/esonhugh/k8spider/cmd/wildcard" ) diff --git a/pkg/mutli/executor.go b/pkg/mutli/executor.go index 8978608..fdba60d 100644 --- a/pkg/mutli/executor.go +++ b/pkg/mutli/executor.go @@ -13,9 +13,14 @@ func ScanAll(subnet *net.IPNet, num int) (result <-chan []define.Record) { } func ScanNeighbor(namespace []string, subnet *net.IPNet, num int) <-chan []define.Record { - subs := NewNeighborScanner(num) + subs := NewNeighborScanner(ScanPods, num) if len(namespace) == 1 { return subs.ScanSingleNeighbor(namespace[0], subnet) } return subs.ScanMultiNeighbor(namespace, subnet) } + +func ScanNeighborSvc(subnet *net.IPNet, num int) <-chan []define.Record { + subs := NewNeighborScanner(ScanSvc, num) + return ScanServiceWithChan(subs.ScanSvcNeighbor(subnet)) +} diff --git a/pkg/mutli/neigbhor.go b/pkg/mutli/neigbhor.go index f2e54ca..ecfa982 100644 --- a/pkg/mutli/neigbhor.go +++ b/pkg/mutli/neigbhor.go @@ -3,11 +3,13 @@ package mutli import ( "fmt" "net" + "strings" "sync" "time" "github.com/esonhugh/k8spider/define" "github.com/esonhugh/k8spider/pkg" + "github.com/esonhugh/k8spider/pkg/post" "github.com/esonhugh/k8spider/pkg/scanner" log "github.com/sirupsen/logrus" ) @@ -15,9 +17,17 @@ import ( type NeighborScanner struct { wg *sync.WaitGroup count int + m ScanMode } -func NewNeighborScanner(threading ...int) *NeighborScanner { +type ScanMode string + +const ( + ScanPods ScanMode = "pods" + ScanSvc ScanMode = "svc" +) + +func NewNeighborScanner(mode ScanMode, threading ...int) *NeighborScanner { if len(threading) == 0 { return &NeighborScanner{ wg: new(sync.WaitGroup), @@ -83,3 +93,46 @@ func (s *NeighborScanner) scan(ns string, subnet *net.IPNet, to chan []define.Re } s.wg.Done() } + +func (s *NeighborScanner) ScanSvcNeighbor(subnet *net.IPNet) <-chan []define.Record { + if subnet == nil { + log.Debugf("subnet is nil") + return nil + } + out := make(chan []define.Record, 100) + go func() { + // if subnets, err := pkg.SubnetShift(subnet, 4); err != nil { + if subnets, err := pkg.SubnetInto(subnet, s.count); err != nil { + log.Errorf("Subnet split into %v failed, fallback to single mode, reason: %v", s.count, err) + go s.scanSvc(subnet, out) + } else { + log.Debugf("Subnet split into %v success", len(subnets)) + for _, sn := range subnets { + go s.scanSvc(sn, out) + } + } + time.Sleep(10 * time.Millisecond) // wait for all goroutines to start + s.wg.Wait() + close(out) + }() + return out +} + +func (s *NeighborScanner) scanSvc(subnet *net.IPNet, to chan []define.Record) { + s.wg.Add(1) + for _, ip := range pkg.ParseIPNetToIPs(subnet) { + hostList := pkg.PTRRecord(ip) + for _, host := range hostList { + if post.IsPodServiceFormat(host) { + newRecord := define.Record{ + Ip: ip, + SvcDomain: strings.SplitN(host, ".", 1)[1], + } + to <- []define.Record{newRecord} + } else { + continue + } + } + } + s.wg.Done() +} diff --git a/pkg/post/record_parser.go b/pkg/post/record_parser.go index be1aaec..a98c51b 100644 --- a/pkg/post/record_parser.go +++ b/pkg/post/record_parser.go @@ -63,3 +63,30 @@ func GetNamespaceFromDomain(domain string, zone string) string { } return "" } + +func IsPodServiceFormat(domain string) bool { + str := strings.Split(dns.Fqdn(domain), ".") + if len(str) > 5 { + // 0:IP 1:ServiceName 2:Namespace 3:svc 4:cluster 5:local 6: + if str[3] == "svc" { + return true + } + } + return false +} + +func GetPodServiceRawService(domain string) string { + str := strings.Split(dns.Fqdn(domain), ".") + return strings.Join(str[1:], ".") +} + +func PodServiceMap(BaseService define.Records) map[string][]string { + result := make(map[string][]string) + for _, r := range BaseService { + if r.SvcDomain != "" { + svcDomain := GetPodServiceRawService(r.SvcDomain) + result[svcDomain] = append(result[svcDomain], r.Ip.String()) + } + } + return result +} diff --git a/rootcmd_test.go b/rootcmd_test.go new file mode 100644 index 0000000..a8755f2 --- /dev/null +++ b/rootcmd_test.go @@ -0,0 +1,22 @@ +package main + +import ( + "strings" + "testing" + + "github.com/esonhugh/k8spider/cmd" +) + +func TestGetCommandTree(t *testing.T) { + t.Logf("TestGetCommandTree") + treeRoot := cmd.RootCmd.Root() + t.Logf("treeRoot: %v", treeRoot) + for _, c := range treeRoot.Commands() { + t.Logf("test: $(BUILD_DIR)/$(MAIN_PROGRAM_NAME) %v --help", strings.ReplaceAll(c.CommandPath(), "k8spider ", "")) + if c.HasSubCommands() { + for _, sc := range c.Commands() { + t.Logf("test: $(BUILD_DIR)/$(MAIN_PROGRAM_NAME) %v --help", strings.ReplaceAll(sc.CommandPath(), "k8spider ", "")) + } + } + } +}