Skip to content

Commit

Permalink
Add cli support
Browse files Browse the repository at this point in the history
Add CLI support for discovery and nmap
```
$ cloud-discovery discover --config <path to config file>
$ cloud-discovery nmap --subnet <subnet to scan>
```

Signed-off-by: liron <[email protected]>
  • Loading branch information
liron committed Nov 11, 2018
1 parent cabd498 commit ffc635c
Show file tree
Hide file tree
Showing 29 changed files with 5,584 additions and 95 deletions.
84 changes: 63 additions & 21 deletions cmd/cli/main.go
Original file line number Diff line number Diff line change
@@ -1,33 +1,75 @@
package main

import (
"flag"
"encoding/json"
"fmt"
"github.com/twistlock/cloud-discovery/internal/provider/aws"
"github.com/twistlock/cloud-discovery/internal/nmap"
"github.com/twistlock/cloud-discovery/internal/provider"
"github.com/twistlock/cloud-discovery/internal/shared"
"github.com/urfave/cli"
"io/ioutil"
"log"
"os"
"text/tabwriter"
)

func main() {
var (
username, password string
)
flag.StringVar(&username, "username", "", "Username")
flag.StringVar(&password, "password", "", "Password")
flag.Parse()
if username == "" {
panic("username is missing")
app := cli.NewApp()
app.Name = "cloud-discovery"
app.Usage = " Cloud Discovery provides a point in time enumeration of all the cloud native platform services"
app.Version = "1.0.0"

var configPath, format, subnet string
app.Commands = []cli.Command{
{
Name: "discover",
Usage: "Discover all cloud assets",
Flags: []cli.Flag{cli.StringFlag{
Name: "config",
Usage: "Path to credential configuration",
Destination: &configPath,
},
cli.StringFlag{
Name: "format",
Usage: "Output Formatting (json or csv)",
Value: "csv",
Destination: &format,
},
},
Action: func(c *cli.Context) error {
if configPath == "" {
return fmt.Errorf("missing config path")
}
data, err := ioutil.ReadFile(configPath)
if err != nil {
return err
}
var creds []shared.Credentials
if err := json.Unmarshal(data, &creds); err != nil {
return err
}
provider.Discover(creds, os.Stdout, shared.Format(format))
return nil
},
},
{
Name: "nmap",
Usage: "Scan all exposed cloud assets",
Flags: []cli.Flag{cli.StringFlag{
Name: "subnet",
Usage: "The subnet to scan",
Value: "127.0.0.1",
Destination: &subnet,
},
},
Action: func(c *cli.Context) error {
nmap.Nmap(os.Stdout, subnet, true)
return nil
},
},
}
if password == "" {
panic("password is missing")

err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.AlignRight|tabwriter.Debug)
fmt.Fprintln(w, "Type\tRegion\tID")
aws.Discover(username, password, func(result shared.CloudDiscoveryResult) {
for _, asset := range result.Assets {
fmt.Fprintf(w, "%s\t%s\t%s\n", result.Type, result.Region, asset.ID)
}
w.Flush()
})
}
74 changes: 3 additions & 71 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,14 @@ import (
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
"github.com/twistlock/cloud-discovery/internal/nmap"
"github.com/twistlock/cloud-discovery/internal/provider/aws"
"github.com/twistlock/cloud-discovery/internal/provider/gcp"
"github.com/twistlock/cloud-discovery/internal/provider"
"github.com/twistlock/cloud-discovery/internal/shared"
"io"
"io/ioutil"
"math/big"
"net"
"net/http"
"os"
"text/tabwriter"
"time"
)

Expand Down Expand Up @@ -164,21 +162,8 @@ func main() {
return
}
defer close(wr)
tw := newTabWriter(wr)
nmap.Nmap(wr, req.Subnet, req.Verbose)

var nmapWriter io.Writer
if req.Verbose {
nmapWriter = wr
} else {
nmapWriter = os.Stdout
}
fmt.Fprintf(tw, "\nHost\tPort\tApp\tInsecure\tReason\t\n")
if err := nmap.Nmap(req.Subnet, 30, 30000, nmapWriter, func(result shared.CloudNmapResult) {
fmt.Fprintf(tw, "%s\t%d\t%s\t%t\t%s\t\n", result.Host, result.Port, result.App, result.Insecure, result.Reason)
}); err != nil {
log.Error(err)
}
tw.Flush()
})).Methods(http.MethodPost)

r.HandleFunc("/discover", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand All @@ -198,21 +183,7 @@ func main() {
return
}
defer close(wr)

var writer responseWriter
if r.URL.Query().Get("format") == "json" {
writer = NewJsonResponseWriter(wr)
} else {
writer = NewTabResponseWriter(wr)
}
for _, cred := range req.Credentials {
switch cred.Provider {
case shared.ProviderGCP:
gcp.Discover(cred.Secret, writer.Write)
default:
aws.Discover(cred.ID, cred.Secret, writer.Write)
}
}
provider.Discover(req.Credentials, wr, shared.Format(r.URL.Query().Get("format")))
})).Methods(http.MethodPost)

s := &http.Server{
Expand All @@ -226,41 +197,6 @@ func main() {
log.Fatal(s.ListenAndServeTLS(config.tlsCertPath, config.tlsKeyPath))
}

type responseWriter interface {
Write(shared.CloudDiscoveryResult)
}

type csvResponseWriter struct {
tw *tabwriter.Writer
}

func NewTabResponseWriter(writer io.Writer) *csvResponseWriter {
tw := newTabWriter(writer)
fmt.Fprintf(tw, "Type\tRegion\tID\n")
return &csvResponseWriter{tw: tw}
}

func (w *csvResponseWriter) Write(result shared.CloudDiscoveryResult) {
for _, asset := range result.Assets {
fmt.Fprintf(w.tw, "%s\t%s\t%s\n", result.Type, result.Region, asset.ID)
}
w.tw.Flush()
}

type jsonResposeWriter struct {
w io.Writer
}

func NewJsonResponseWriter(w io.Writer) *jsonResposeWriter {
return &jsonResposeWriter{w: w}
}

func (w *jsonResposeWriter) Write(result shared.CloudDiscoveryResult) {
out, _ := json.Marshal(result)
w.w.Write(out)
w.w.Write([]byte("\n"))
}

func genCert(certPath, keyPath, host string) error {
const rsaBits = 2048
priv, err := rsa.GenerateKey(rand.Reader, rsaBits)
Expand Down Expand Up @@ -330,7 +266,3 @@ func isBadRequestErr(err error) bool {
_, ok := err.(badRequestErr)
return ok
}

func newTabWriter(wr io.Writer) *tabwriter.Writer {
return tabwriter.NewWriter(wr, 0, 0, 5, ' ', tabwriter.TabIndent)
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/lair-framework/go-nmap v0.0.0-20180506230210-84c21710ccc8
github.com/sirupsen/logrus v1.1.0
github.com/spf13/cobra v0.0.4-0.20180915222204-8d114be902bc // indirect
github.com/urfave/cli v1.20.0
golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc // indirect
golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc
google.golang.org/api v0.0.0-20181003000758-f5c49d98d21c
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ github.com/sirupsen/logrus v1.1.0 h1:65VZabgUiV9ktjGM5nTq0+YurgTyX+YI2lSSfDjI+qU
github.com/sirupsen/logrus v1.1.0/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A=
github.com/spf13/cobra v0.0.4-0.20180915222204-8d114be902bc/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand Down
23 changes: 20 additions & 3 deletions internal/nmap/nmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package nmap

import (
"fmt"
"github.com/lair-framework/go-nmap"
gonmap "github.com/lair-framework/go-nmap"
log "github.com/sirupsen/logrus"
"github.com/twistlock/cloud-discovery/internal/shared"
"io"
Expand All @@ -14,7 +14,24 @@ import (
"time"
)

func Nmap(subnet string, minPort, maxPort int, nmapWriter io.Writer, emitFn func(result shared.CloudNmapResult)) error {
func Nmap(wr io.Writer, subnet string, verbose bool) {
tw := shared.NewTabWriter(wr)
var nmapWriter io.Writer
if verbose {
nmapWriter = wr
} else {
nmapWriter = os.Stdout
}
fmt.Fprintf(tw, "\nHost\tPort\tApp\tInsecure\tReason\t\n")
if err := nmap(subnet, 30, 30000, nmapWriter, func(result shared.CloudNmapResult) {
fmt.Fprintf(tw, "%s\t%d\t%s\t%t\t%s\t\n", result.Host, result.Port, result.App, result.Insecure, result.Reason)
}); err != nil {
log.Error(err)
}
tw.Flush()
}

func nmap(subnet string, minPort, maxPort int, nmapWriter io.Writer, emitFn func(result shared.CloudNmapResult)) error {
log.Debugf("Scanning subnet %s", subnet)
dir, err := ioutil.TempDir("", "nmap")
if err != nil {
Expand All @@ -40,7 +57,7 @@ func Nmap(subnet string, minPort, maxPort int, nmapWriter io.Writer, emitFn func
if err != nil {
return err
}
nmap, err := nmap.Parse(out)
nmap, err := gonmap.Parse(out)
if err != nil {
return err
}
Expand Down
48 changes: 48 additions & 0 deletions internal/provider/discover.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package provider

import (
"fmt"
"github.com/twistlock/cloud-discovery/internal/provider/aws"
"github.com/twistlock/cloud-discovery/internal/provider/gcp"
"github.com/twistlock/cloud-discovery/internal/shared"
"io"
"text/tabwriter"
)

func Discover(creds []shared.Credentials, wr io.Writer, format shared.Format) {
var writer ResponseWriter
if format == shared.FormatJson {
writer = shared.NewJsonResponseWriter(wr)
} else {
writer = NewTabResponseWriter(wr)
}
for _, cred := range creds {
switch cred.Provider {
case shared.ProviderGCP:
gcp.Discover(cred.Secret, writer.Write)
default:
aws.Discover(cred.ID, cred.Secret, writer.Write)
}
}
}

type ResponseWriter interface {
Write(shared.CloudDiscoveryResult)
}

type csvResponseWriter struct {
tw *tabwriter.Writer
}

func NewTabResponseWriter(writer io.Writer) *csvResponseWriter {
tw := shared.NewTabWriter(writer)
fmt.Fprintf(tw, "Type\tRegion\tID\n")
return &csvResponseWriter{tw: tw}
}

func (w *csvResponseWriter) Write(result shared.CloudDiscoveryResult) {
for _, asset := range result.Assets {
fmt.Fprintf(w.tw, "%s\t%s\t%s\n", result.Type, result.Region, asset.ID)
}
w.tw.Flush()
}
8 changes: 8 additions & 0 deletions internal/shared/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ const (
ProviderGCP Provider = "gcp"
)

// Format is the output format
type Format string

const (
FormatJson Format = "json"
FormatCSV Format = "csv"
)

// Credentials holds authentication data for a specific provider
type Credentials struct {
Provider Provider `json:"provider"` // Provider is the authentication provider (AWS/Azure/GCP)
Expand Down
21 changes: 21 additions & 0 deletions internal/shared/utils.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package shared

import (
"encoding/json"
log "github.com/sirupsen/logrus"
"io"
"os"
"text/tabwriter"
)

func init() {
Expand All @@ -15,3 +18,21 @@ func init() {
// Only log the warning severity or above.
log.SetLevel(log.DebugLevel)
}

func NewTabWriter(wr io.Writer) *tabwriter.Writer {
return tabwriter.NewWriter(wr, 0, 0, 5, ' ', tabwriter.TabIndent)
}

type jsonResposeWriter struct {
w io.Writer
}

func NewJsonResponseWriter(w io.Writer) *jsonResposeWriter {
return &jsonResposeWriter{w: w}
}

func (w *jsonResposeWriter) Write(result CloudDiscoveryResult) {
out, _ := json.Marshal(result)
w.w.Write(out)
w.w.Write([]byte("\n"))
}
2 changes: 2 additions & 0 deletions vendor/github.com/urfave/cli/.flake8

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions vendor/github.com/urfave/cli/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit ffc635c

Please sign in to comment.