diff --git a/cmd/crowdsec-cli/capi.go b/cmd/crowdsec-cli/capi.go index fb8424b99..45d4d9682 100644 --- a/cmd/crowdsec-cli/capi.go +++ b/cmd/crowdsec-cli/capi.go @@ -1,16 +1,13 @@ package main import ( - "context" "fmt" "net/url" "os" "github.com/crowdsecurity/crowdsec/pkg/apiclient" "github.com/crowdsecurity/crowdsec/pkg/csconfig" - "github.com/crowdsecurity/crowdsec/pkg/cwhub" "github.com/crowdsecurity/crowdsec/pkg/cwversion" - "github.com/crowdsecurity/crowdsec/pkg/models" "github.com/go-openapi/strfmt" "github.com/pkg/errors" log "github.com/sirupsen/logrus" @@ -126,42 +123,12 @@ func NewCapiCmd() *cobra.Command { log.Fatalf("no credentials for Central API (CAPI) in '%s'", csConfig.API.Server.OnlineClient.CredentialsFilePath) } - password := strfmt.Password(csConfig.API.Server.OnlineClient.Credentials.Password) - apiurl, err := url.Parse(csConfig.API.Server.OnlineClient.Credentials.URL) - if err != nil { - log.Fatalf("parsing api url ('%s'): %s", csConfig.API.Server.OnlineClient.Credentials.URL, err) - } - - if err := csConfig.LoadHub(); err != nil { - log.Fatal(err) - } - - if err := cwhub.GetHubIdx(csConfig.Hub); err != nil { - log.Info("Run 'sudo cscli hub update' to get the hub index") - log.Fatalf("Failed to load hub index : %s", err) - } - scenarios, err := cwhub.GetInstalledScenariosAsString() - if err != nil { - log.Fatalf("failed to get scenarios : %s", err) - } - if len(scenarios) == 0 { - log.Fatalf("no scenarios installed, abort") - } - - Client, err = apiclient.NewDefaultClient(apiurl, CAPIURLPrefix, fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()), nil) - if err != nil { - log.Fatalf("init default client: %s", err) - } - t := models.WatcherAuthRequest{ - MachineID: &csConfig.API.Server.OnlineClient.Credentials.Login, - Password: &password, - Scenarios: scenarios, - } log.Infof("Loaded credentials from %s", csConfig.API.Server.OnlineClient.CredentialsFilePath) - log.Infof("Trying to authenticate with username %s on %s", csConfig.API.Server.OnlineClient.Credentials.Login, apiurl) - _, err = Client.Auth.AuthenticateWatcher(context.Background(), t) + log.Infof("Trying to authenticate with username %s on %s", csConfig.API.Server.OnlineClient.Credentials.Login, csConfig.API.Server.OnlineClient.Credentials.URL) + + _, err = CapiAuth(csConfig.API.Server.OnlineClient) if err != nil { - log.Fatalf("Failed to authenticate to Central API (CAPI) : %s", err) + log.Fatalf("unable to connect to CrowdSec Central API: %s", err) } log.Infof("You can successfully interact with Central API (CAPI)") }, diff --git a/cmd/crowdsec-cli/console.go b/cmd/crowdsec-cli/console.go index c92a06c01..7ef248dd3 100644 --- a/cmd/crowdsec-cli/console.go +++ b/cmd/crowdsec-cli/console.go @@ -17,11 +17,27 @@ import ( "github.com/crowdsecurity/crowdsec/pkg/types" "github.com/enescakir/emoji" "github.com/go-openapi/strfmt" + "github.com/golang-jwt/jwt/v4" "github.com/olekukonko/tablewriter" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) +func isEnrolled(client *apiclient.ApiClient) bool { + apiHTTPClient := client.GetClient() + jwtTransport := apiHTTPClient.Transport.(*apiclient.JWTTransport) + tokenStr := jwtTransport.Token + + token, _ := jwt.Parse(tokenStr, nil) + if token == nil { + return false + } + claims := token.Claims.(jwt.MapClaims) + _, ok := claims["organization_id"] + + return ok +} + func NewConsoleCmd() *cobra.Command { var cmdConsole = &cobra.Command{ Use: "console [action]", @@ -192,6 +208,32 @@ Disable given information push to the central API.`, Example: "status tainted", DisableAutoGenTag: true, Run: func(cmd *cobra.Command, args []string) { + var err error + if csConfig.API.Server == nil { + log.Fatalln("There is no configuration on 'api.server:'") + } + if csConfig.API.Server.OnlineClient == nil { + log.Fatalf("Please provide credentials for the Central API (CAPI) in '%s'", csConfig.API.Server.OnlineClient.CredentialsFilePath) + } + + if csConfig.API.Server.OnlineClient.Credentials == nil { + log.Fatalf("no credentials for Central API (CAPI) in '%s'", csConfig.API.Server.OnlineClient.CredentialsFilePath) + } + + log.Debugf("Loaded credentials from %s", csConfig.API.Server.OnlineClient.CredentialsFilePath) + log.Debugf("Trying to authenticate with username %s on %s", csConfig.API.Server.OnlineClient.Credentials.Login, csConfig.API.Server.OnlineClient.Credentials.URL) + + client, err := CapiAuth(csConfig.API.Server.OnlineClient) + if err != nil { + log.Fatalf("unable to connect to CrowdSec Central API: %s", err) + } + + if isEnrolled(client) { + fmt.Printf("Machine '%s' is enrolled in the console", csConfig.API.Server.OnlineClient.Credentials.Login) + } else { + fmt.Printf("Machine '%s' is not enrolled in the console", csConfig.API.Server.OnlineClient.Credentials.Login) + } + switch csConfig.Cscli.Output { case "human": table := tablewriter.NewWriter(os.Stdout) diff --git a/cmd/crowdsec-cli/utils.go b/cmd/crowdsec-cli/utils.go index f6bd0f51a..c4d76a555 100644 --- a/cmd/crowdsec-cli/utils.go +++ b/cmd/crowdsec-cli/utils.go @@ -2,17 +2,25 @@ package main import ( "bytes" + "context" "encoding/csv" "encoding/json" "fmt" "math" "net" "net/http" + "net/url" "os" "strconv" "strings" "time" + "github.com/crowdsecurity/crowdsec/pkg/apiclient" + "github.com/crowdsecurity/crowdsec/pkg/csconfig" + "github.com/crowdsecurity/crowdsec/pkg/cwversion" + "github.com/crowdsecurity/crowdsec/pkg/models" + "github.com/go-openapi/strfmt" + "github.com/crowdsecurity/crowdsec/pkg/cwhub" "github.com/crowdsecurity/crowdsec/pkg/types" "github.com/enescakir/emoji" @@ -776,3 +784,47 @@ func formatNumber(num int) string { res := math.Round(float64(num)/float64(goodUnit.value)*100) / 100 return fmt.Sprintf("%.2f%s", res, goodUnit.symbol) } + +func CapiAuth(capiConfig *csconfig.OnlineApiClientCfg) (*apiclient.ApiClient, error) { + var err error + var client *apiclient.ApiClient + + password := strfmt.Password(csConfig.API.Server.OnlineClient.Credentials.Password) + apiurl, err := url.Parse(csConfig.API.Server.OnlineClient.Credentials.URL) + if err != nil { + return client, fmt.Errorf("parsing api url ('%s'): %s", csConfig.API.Server.OnlineClient.Credentials.URL, err) + } + + if err := csConfig.LoadHub(); err != nil { + log.Fatal(err) + } + + if err := cwhub.GetHubIdx(csConfig.Hub); err != nil { + log.Info("Run 'sudo cscli hub update' to get the hub index") + return client, fmt.Errorf("Failed to load hub index : %s", err) + } + scenarios, err := cwhub.GetInstalledScenariosAsString() + if err != nil { + return client, fmt.Errorf("failed to get scenarios : %s", err) + } + if len(scenarios) == 0 { + return client, fmt.Errorf("no scenarios installed, abort") + } + + client, err = apiclient.NewDefaultClient(apiurl, CAPIURLPrefix, fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()), nil) + if err != nil { + return client, fmt.Errorf("init default client: %s", err) + } + t := models.WatcherAuthRequest{ + MachineID: &csConfig.API.Server.OnlineClient.Credentials.Login, + Password: &password, + Scenarios: scenarios, + } + + _, err = client.Auth.AuthenticateWatcher(context.Background(), t) + if err != nil { + return client, fmt.Errorf("Failed to authenticate to Central API (CAPI) : %s", err) + } + + return client, nil +} diff --git a/pkg/apiclient/auth.go b/pkg/apiclient/auth.go index 87d725e80..e0b45beff 100644 --- a/pkg/apiclient/auth.go +++ b/pkg/apiclient/auth.go @@ -78,7 +78,7 @@ func (t *APIKeyTransport) transport() http.RoundTripper { type JWTTransport struct { MachineID *string Password *strfmt.Password - token string + Token string Expiration time.Time Scenarios []string URL *url.URL @@ -161,15 +161,15 @@ func (t *JWTTransport) refreshJwtToken() error { if err := t.Expiration.UnmarshalText([]byte(response.Expire)); err != nil { return errors.Wrap(err, "unable to parse jwt expiration") } - t.token = response.Token + t.Token = response.Token - log.Debugf("token %s will expire on %s", t.token, t.Expiration.String()) + log.Debugf("token %s will expire on %s", t.Token, t.Expiration.String()) return nil } // RoundTrip implements the RoundTripper interface. func (t *JWTTransport) RoundTrip(req *http.Request) (*http.Response, error) { - if t.token == "" || t.Expiration.Add(-time.Minute).Before(time.Now().UTC()) { + if t.Token == "" || t.Expiration.Add(-time.Minute).Before(time.Now().UTC()) { if err := t.refreshJwtToken(); err != nil { return nil, err } @@ -179,7 +179,7 @@ func (t *JWTTransport) RoundTrip(req *http.Request) (*http.Response, error) { // that we don't modify the Request we were given. This is required by the // specification of http.RoundTripper. req = cloneRequest(req) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", t.token)) + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", t.Token)) log.Debugf("req-jwt: %s %s", req.Method, req.URL.String()) if log.GetLevel() >= log.TraceLevel { dump, _ := httputil.DumpRequest(req, true) @@ -196,7 +196,7 @@ func (t *JWTTransport) RoundTrip(req *http.Request) (*http.Response, error) { } if err != nil || resp.StatusCode == 401 { /*we had an error (network error for example, or 401 because token is refused), reset the token ?*/ - t.token = "" + t.Token = "" return resp, errors.Wrapf(err, "performing jwt auth") } log.Debugf("resp-jwt: %d", resp.StatusCode) diff --git a/pkg/apiclient/client.go b/pkg/apiclient/client.go index f6cc73894..751f8b771 100644 --- a/pkg/apiclient/client.go +++ b/pkg/apiclient/client.go @@ -38,6 +38,10 @@ type ApiClient struct { HeartBeat *HeartBeatService } +func (a *ApiClient) GetClient() *http.Client { + return a.client +} + type service struct { client *ApiClient }