diff --git a/k8scan/Dockerfile b/k8scan/Dockerfile new file mode 100644 index 0000000..1473a42 --- /dev/null +++ b/k8scan/Dockerfile @@ -0,0 +1,31 @@ +FROM golang:alpine as builder +MAINTAINER Jessica Frazelle + +ENV PATH /go/bin:/usr/local/go/bin:$PATH +ENV GOPATH /go + +RUN apk add --no-cache \ + ca-certificates + +COPY main.go /go/src/k8scan/ + +RUN set -x \ + && apk add --no-cache --virtual .build-deps \ + git \ + gcc \ + libc-dev \ + libgcc \ + make \ + && cd /go/src/k8scan/ \ + && go get -d . \ + && CGO_ENABLED=0 go build -a -tags netgo -ldflags '-w -extldflags "-static"' -o /usr/bin/k8scan *.go \ + && apk del .build-deps \ + && rm -rf /go \ + && echo "Build complete." + +FROM scratch + +COPY --from=builder /usr/bin/k8scan /usr/bin/k8scan +COPY --from=builder /etc/ssl/certs/ /etc/ssl/certs + +CMD [ "k8scan" ] diff --git a/k8scan/main.go b/k8scan/main.go new file mode 100644 index 0000000..42b0129 --- /dev/null +++ b/k8scan/main.go @@ -0,0 +1,181 @@ +package main + +import ( + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net" + "net/http" + "os" + "os/signal" + "strings" + "sync" + "syscall" + "time" + + "github.com/Sirupsen/logrus" +) + +const ( + cidr = "0.0.0.0/0" + beginPort = 80 + endPort = 65535 + + arinAPIEndpoint = "http://whois.arin.net/rest/ip/%s" +) + +func main() { + // On ^C, or SIGTERM handle exit. + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + signal.Notify(c, syscall.SIGTERM) + go func() { + for sig := range c { + logrus.Infof("Received %s, exiting.", sig.String()) + os.Exit(0) + } + }() + + logrus.Infof("Scanning for Kubernetes Dashboards and API Servers on %s over port range %d-%d", cidr, beginPort, endPort) + logrus.Infof("This may take a bit...") + + ip, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + logrus.Fatal(err) + } + + var wg sync.WaitGroup + for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); inc(ip) { + wg.Add(1) + go func(ip string) { + defer wg.Done() + + for port := beginPort; port <= endPort; port++ { + // Check if the port is open. + ok := portOpen(ip, port) + if !ok { + return + } + + // Check if it's a kubernetes dashboard. + ok = isKubernetesDashboard(ip, port) + if !ok { + return + } + + fmt.Printf("%s:%d\n", ip, port) + // Get the info for the ip address. + info, err := getIPInfo(ip) + if err != nil { + logrus.Warnf("ip info err: %v", err) + return + } + fmt.Printf("%s:%d\t%s\t%s\t%s\n", + ip, port, + info.Organization.Handle, info.Organization.Name, info.Organization.Reference) + } + }(ip.String()) + } + + wg.Wait() +} + +func portOpen(ip string, port int) bool { + c, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", ip, port), 2*time.Second) + if err != nil { + // logrus.Warnf("listen at %s:%s failed: %v", ip, port, err) + return false + } + defer c.Close() + + return true +} + +func isKubernetesDashboard(ip string, port int) bool { + client := &http.Client{ + Timeout: time.Second * 3, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + }, + } + + tryAddrs := []string{ + fmt.Sprintf("http://%s:%d", ip, port), + fmt.Sprintf("https://%s:%d", ip, port), + fmt.Sprintf("http://%s:%d/api/", ip, port), + fmt.Sprintf("https://%s:%d/api/", ip, port), + } + + var ( + resp *http.Response + err = errors.New("not yet run") + uri string + ) + + for i := 0; i < len(tryAddrs) && err != nil; i++ { + uri = tryAddrs[i] + resp, err = client.Get(uri) + } + if err != nil { + //logrus.Warnf("getting %s:%s failed: %v", ip, port, err) + return false + } + defer resp.Body.Close() + + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return false + } + + body := strings.ToLower(string(b)) + if strings.Contains(body, "kubernetes") || + (strings.Contains(body, "versions") && strings.Contains(body, "serverAddress")) { + logrus.Infof("uri: %s", uri) + return true + } + + return false +} + +type ARINResponse struct { + Organization OrganizationJSON `json:"orgReg,omitempty"` +} + +type OrganizationJSON struct { + Handle string `json:"@handle,omitempty"` + Name string `json:"@name,omitempty"` + Reference string `json:"$,omitempty"` +} + +func getIPInfo(ip string) (b ARINResponse, err error) { + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf(arinAPIEndpoint, ip), nil) + if err != nil { + return b, err + } + req.Header.Set("Accept", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return b, err + } + defer resp.Body.Close() + + if err := json.NewDecoder(resp.Body).Decode(&b); err != nil { + return b, err + } + + return b, nil +} + +func inc(ip net.IP) { + for j := len(ip) - 1; j >= 0; j-- { + ip[j]++ + if ip[j] > 0 { + break + } + } +}