Parcourir la source

add `console enroll` command to cscli (#828)

blotus il y a 4 ans
Parent
commit
3994aec7fe

+ 1 - 0
cmd/crowdsec-cli/capi.go

@@ -166,5 +166,6 @@ func NewCapiCmd() *cobra.Command {
 		},
 	}
 	cmdCapi.AddCommand(cmdCapiStatus)
+
 	return cmdCapi
 }

+ 98 - 0
cmd/crowdsec-cli/console.go

@@ -0,0 +1,98 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"net/url"
+
+	"github.com/crowdsecurity/crowdsec/pkg/apiclient"
+	"github.com/crowdsecurity/crowdsec/pkg/cwhub"
+	"github.com/crowdsecurity/crowdsec/pkg/cwversion"
+	"github.com/go-openapi/strfmt"
+	log "github.com/sirupsen/logrus"
+	"github.com/spf13/cobra"
+)
+
+func NewConsoleCmd() *cobra.Command {
+	var cmdConsole = &cobra.Command{
+		Use:   "console [action]",
+		Short: "Manage interaction with Crowdsec console (https://app.crowdsec.net)",
+		Args:  cobra.MinimumNArgs(1),
+		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
+			if err := csConfig.LoadAPIServer(); err != nil || csConfig.DisableAPI {
+				log.Fatal("Local API is disabled, please run this command on the local API machine")
+			}
+			if csConfig.API.Server.OnlineClient == nil {
+				log.Fatalf("no configuration for crowdsec API in '%s'", *csConfig.FilePath)
+			}
+
+			return nil
+		},
+	}
+
+	cmdEnroll := &cobra.Command{
+		Use:   "enroll [enroll-key]",
+		Short: "Enroll this instance to https://app.crowdsec.net [requires local API]",
+		Long: `
+Enroll this instance to https://app.crowdsec.net
+		
+You can get your enrollment key by creating an account on https://app.crowdsec.net.
+After running this command your will need to validate the enrollment in the webapp.`,
+		Example: "cscli console enroll YOUR-ENROLL-KEY",
+		Args:    cobra.ExactArgs(1),
+		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
+			if err := csConfig.LoadAPIServer(); err != nil || csConfig.DisableAPI {
+				log.Fatal("Local API is disabled, please run this command on the local API machine")
+			}
+			if csConfig.API.Server.OnlineClient == nil {
+				log.Fatalf("no configuration for crowdsec API in '%s'", *csConfig.FilePath)
+			}
+			if csConfig.API.Server.OnlineClient.Credentials == nil {
+				log.Fatal("You must configure CAPI with `cscli capi register` before enrolling your instance")
+			}
+			return nil
+		},
+		Run: func(cmd *cobra.Command, args []string) {
+			password := strfmt.Password(csConfig.API.Server.OnlineClient.Credentials.Password)
+			apiURL, err := url.Parse(csConfig.API.Server.OnlineClient.Credentials.URL)
+			if err != nil {
+				log.Fatalf("Could not parse CAPI URL : %s", err)
+			}
+
+			if err := csConfig.LoadHub(); err != nil {
+				log.Fatalf(err.Error())
+			}
+
+			if err := cwhub.GetHubIdx(csConfig.Hub); err != nil {
+				log.Fatalf("Failed to load hub index : %s", err)
+				log.Infoln("Run 'sudo cscli hub update' to get the hub index")
+			}
+
+			scenarios, err := cwhub.GetUpstreamInstalledScenariosAsString()
+			if err != nil {
+				log.Fatalf("failed to get scenarios : %s", err.Error())
+			}
+
+			if len(scenarios) == 0 {
+				scenarios = make([]string, 0)
+			}
+
+			c, _ := apiclient.NewClient(&apiclient.Config{
+				MachineID:     csConfig.API.Server.OnlineClient.Credentials.Login,
+				Password:      password,
+				Scenarios:     scenarios,
+				UserAgent:     fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()),
+				URL:           apiURL,
+				VersionPrefix: "v2",
+			})
+			_, err = c.Auth.EnrollWatcher(context.Background(), args[0])
+			if err != nil {
+				log.Fatalf("Could not enroll instance: %s", err)
+			}
+			log.Infof("Watcher successfully enrolled. Visit https://app.crowdsec.net to accept it.")
+		},
+	}
+
+	cmdConsole.AddCommand(cmdEnroll)
+	return cmdConsole
+}

+ 4 - 1
cmd/crowdsec-cli/main.go

@@ -78,7 +78,9 @@ func initConfig() {
 }
 
 var validArgs = []string{
-	"scenarios", "parsers", "collections", "capi", "lapi", "postoverflows", "machines", "metrics", "bouncers", "alerts", "decisions", "simulation", "hub", "dashboard", "config", "completion", "version",
+	"scenarios", "parsers", "collections", "capi", "lapi", "postoverflows", "machines",
+	"metrics", "bouncers", "alerts", "decisions", "simulation", "hub", "dashboard",
+	"config", "completion", "version", "console",
 }
 
 func main() {
@@ -148,6 +150,7 @@ It is meant to allow you to manage bans, parsers/scenarios/etc, api and generall
 	rootCmd.AddCommand(NewCapiCmd())
 	rootCmd.AddCommand(NewLapiCmd())
 	rootCmd.AddCommand(NewCompletionCmd())
+	rootCmd.AddCommand(NewConsoleCmd())
 	if err := rootCmd.Execute(); err != nil {
 		log.Fatalf("While executing root command : %s", err)
 	}

+ 19 - 0
pkg/apiclient/auth_service.go

@@ -11,6 +11,11 @@ import (
 
 type AuthService service
 
+// Don't add it to the models, as they are used with LAPI, but the enroll endpoint is specific to CAPI
+type enrollRequest struct {
+	EnrollKey string `json:"attachment_key"`
+}
+
 func (s *AuthService) UnregisterWatcher(ctx context.Context) (*Response, error) {
 
 	u := fmt.Sprintf("%s/watchers", s.client.URLPrefix)
@@ -55,3 +60,17 @@ func (s *AuthService) AuthenticateWatcher(ctx context.Context, auth models.Watch
 	}
 	return resp, nil
 }
+
+func (s *AuthService) EnrollWatcher(ctx context.Context, enrollKey string) (*Response, error) {
+	u := fmt.Sprintf("%s/watchers/enroll", s.client.URLPrefix)
+	req, err := s.client.NewRequest("POST", u, &enrollRequest{EnrollKey: enrollKey})
+	if err != nil {
+		return nil, err
+	}
+
+	resp, err := s.client.Do(ctx, req, nil)
+	if err != nil {
+		return resp, err
+	}
+	return resp, nil
+}

+ 57 - 0
pkg/apiclient/auth_service_test.go

@@ -179,3 +179,60 @@ func TestWatcherUnregister(t *testing.T) {
 	}
 	log.Printf("->%T", client)
 }
+
+func TestWatcherEnroll(t *testing.T) {
+	log.SetLevel(log.DebugLevel)
+
+	mux, urlx, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/watchers/enroll", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "POST")
+		buf := new(bytes.Buffer)
+		_, _ = buf.ReadFrom(r.Body)
+		newStr := buf.String()
+		log.Debugf("body -> %s", newStr)
+		if newStr == `{"attachment_key":"goodkey"}
+` {
+			log.Print("good key")
+			w.WriteHeader(http.StatusOK)
+			fmt.Fprintf(w, `{"statusCode": 200, "message": "OK"}`)
+		} else {
+			log.Print("bad key")
+			w.WriteHeader(http.StatusForbidden)
+			fmt.Fprintf(w, `{"message":"the attachment key provided is not valid"}`)
+		}
+	})
+	mux.HandleFunc("/watchers/login", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "POST")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, `{"code":200,"expire":"2029-11-30T14:14:24+01:00","token":"toto"}`)
+	})
+	log.Printf("URL is %s", urlx)
+	apiURL, err := url.Parse(urlx + "/")
+	if err != nil {
+		log.Fatalf("parsing api url: %s", apiURL)
+	}
+
+	mycfg := &Config{
+		MachineID:     "test_login",
+		Password:      "test_password",
+		UserAgent:     fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()),
+		URL:           apiURL,
+		VersionPrefix: "v1",
+		Scenarios:     []string{"crowdsecurity/test"},
+	}
+	client, err := NewClient(mycfg)
+
+	if err != nil {
+		log.Fatalf("new api client: %s", err.Error())
+	}
+
+	_, err = client.Auth.EnrollWatcher(context.Background(), "goodkey")
+	if err != nil {
+		t.Fatalf("unexpect auth err: %s", err)
+	}
+
+	_, err = client.Auth.EnrollWatcher(context.Background(), "badkey")
+	assert.Contains(t, err.Error(), "the attachment key provided is not valid")
+}