225 lines
6.9 KiB
Go
225 lines
6.9 KiB
Go
package cwapi
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
|
|
"github.com/crowdsecurity/crowdsec/pkg/types"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
"gopkg.in/yaml.v2"
|
|
|
|
"github.com/dghubble/sling"
|
|
)
|
|
|
|
type ApiCtx struct {
|
|
/*config*/
|
|
ApiVersion string `yaml:"version"`
|
|
PullPath string `yaml:"pull_path"`
|
|
PushPath string `yaml:"push_path"`
|
|
SigninPath string `yaml:"signin_path"`
|
|
RegisterPath string `yaml:"register_path"`
|
|
ResetPwdPath string `yaml:"reset_pwd_path"`
|
|
EnrollPath string `yaml:"enroll_path"`
|
|
BaseURL string `yaml:"url"`
|
|
CfgUser string `yaml:"machine_id"`
|
|
CfgPassword string `yaml:"password"`
|
|
Creds ApiCreds `yaml:"-"`
|
|
Muted bool `yaml:"muted"`
|
|
DebugDump bool `yaml:"debug_dump"`
|
|
|
|
/*runtime*/
|
|
tokenExpired bool `yaml:"-"`
|
|
toPush []types.Event `yaml:"-"`
|
|
Http *sling.Sling `yaml:"-"`
|
|
}
|
|
|
|
type ApiCreds struct {
|
|
User string `json:"machine_id" yaml:"machine_id"`
|
|
Password string `json:"password" yaml:"password"`
|
|
Profile string `json:"profile,omitempty" yaml:"profile,omitempty"`
|
|
}
|
|
|
|
type ApiResp struct {
|
|
StatusCode int `json:"statusCode"`
|
|
Error string `json:"error"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
type PullResp struct {
|
|
StatusCode int `json:"statusCode"`
|
|
Body []map[string]string `json:"message"`
|
|
}
|
|
|
|
func (ctx *ApiCtx) WriteConfig(cfg string) error {
|
|
ret, err := yaml.Marshal(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal config : %s", err)
|
|
}
|
|
if err := ioutil.WriteFile(cfg, ret, 0600); err != nil {
|
|
return fmt.Errorf("failed to write api file %s : %s", cfg, ret)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ctx *ApiCtx) LoadConfig(cfg string) error {
|
|
rcfg, err := ioutil.ReadFile(cfg)
|
|
if err != nil {
|
|
return fmt.Errorf("api load configuration: unable to read configuration file '%s' : %s", cfg, err)
|
|
}
|
|
if err := yaml.UnmarshalStrict(rcfg, &ctx); err != nil {
|
|
return fmt.Errorf("api load configuration: unable to unmarshall configuration file '%s' : %s", cfg, err)
|
|
}
|
|
if ctx.ApiVersion != cwversion.Constraint_api {
|
|
return fmt.Errorf("api load configuration: cscli version only supports %s api, not %s", cwversion.Constraint_api, ctx.ApiVersion)
|
|
}
|
|
ctx.Creds.User = ctx.CfgUser
|
|
ctx.Creds.Password = ctx.CfgPassword
|
|
|
|
/*
|
|
For sling, if a path starts with '/', it's an absolute path, and it will get rid of the 'prefix',
|
|
leading to bad urls
|
|
*/
|
|
if strings.HasPrefix(ctx.PullPath, "/") ||
|
|
strings.HasPrefix(ctx.PushPath, "/") ||
|
|
strings.HasPrefix(ctx.SigninPath, "/") ||
|
|
strings.HasPrefix(ctx.RegisterPath, "/") ||
|
|
strings.HasPrefix(ctx.ResetPwdPath, "/") ||
|
|
strings.HasPrefix(ctx.EnrollPath, "/") {
|
|
log.Warningf("!API paths must not be prefixed by /")
|
|
}
|
|
|
|
ctx.Http = sling.New().Base(ctx.BaseURL+"/"+ctx.ApiVersion+"/").Set("User-Agent", fmt.Sprintf("CrowdWatch/%s", cwversion.VersionStr()))
|
|
log.Printf("api load configuration: configuration loaded successfully (base:%s)", ctx.BaseURL+"/"+ctx.ApiVersion+"/")
|
|
return nil
|
|
}
|
|
|
|
func (ctx *ApiCtx) Init(cfg string, profile string) error {
|
|
var err error
|
|
|
|
err = ctx.LoadConfig(cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctx.Creds.Profile = profile
|
|
ctx.toPush = make([]types.Event, 0)
|
|
err = ctx.Signin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
//start the background go-routine
|
|
go ctx.pushLoop() //nolint:errcheck // runs into the background, we can't check error with chan or such
|
|
return nil
|
|
}
|
|
|
|
func (ctx *ApiCtx) Signin() error {
|
|
if ctx.Creds.User == "" || ctx.Creds.Password == "" {
|
|
return fmt.Errorf("api signin: missing credentials in api.yaml")
|
|
}
|
|
|
|
req, err := ctx.Http.New().Post(ctx.SigninPath).BodyJSON(ctx.Creds).Request()
|
|
if err != nil {
|
|
return fmt.Errorf("api signin: HTTP request creation failed: %s", err)
|
|
}
|
|
log.Debugf("api signin: URL: '%s'", req.URL)
|
|
httpClient := http.Client{Timeout: 20 * time.Second}
|
|
resp, err := httpClient.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("api signin: API call failed : %s", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return fmt.Errorf("api signin: unable to read API response body: '%s'", err)
|
|
}
|
|
|
|
if resp.StatusCode != 200 {
|
|
return fmt.Errorf("api signin: return bad HTTP code (%d): %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
jsonResp := ApiResp{}
|
|
err = json.Unmarshal(body, &jsonResp)
|
|
if err != nil {
|
|
return fmt.Errorf("api signin: unable to unmarshall api response '%s': %s", string(body), err.Error())
|
|
}
|
|
ctx.Http = ctx.Http.Set("Authorization", jsonResp.Message)
|
|
log.Printf("api signin: signed in successfuly")
|
|
return nil
|
|
}
|
|
|
|
func (ctx *ApiCtx) RegisterMachine(machineID string, password string) error {
|
|
ctx.Creds.User = machineID
|
|
ctx.Creds.Password = password
|
|
|
|
req, err := ctx.Http.New().Post(ctx.RegisterPath).BodyJSON(ctx.Creds).Request()
|
|
if err != nil {
|
|
return fmt.Errorf("api register machine: HTTP request creation failed: %s", err)
|
|
}
|
|
log.Debugf("api register: URL: '%s'", req.URL)
|
|
|
|
httpClient := http.Client{Timeout: 20 * time.Second}
|
|
resp, err := httpClient.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("api register machine: API call failed : %s", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return fmt.Errorf("api register machine: unable to read API response body: %s", err.Error())
|
|
}
|
|
|
|
if resp.StatusCode != 200 {
|
|
return fmt.Errorf("api register machine: return bad HTTP code (%d): %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
jsonResp := ApiResp{}
|
|
err = json.Unmarshal(body, &jsonResp)
|
|
if err != nil {
|
|
return fmt.Errorf("api register machine: unable to unmarshall api response '%s': %s", string(body), err.Error())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ctx *ApiCtx) ResetPassword(machineID string, password string) error {
|
|
ctx.Creds.User = machineID
|
|
ctx.Creds.Password = password
|
|
|
|
data := map[string]string{"machine_id": ctx.Creds.User, "password": ctx.Creds.Password}
|
|
req, err := ctx.Http.New().Post(ctx.ResetPwdPath).BodyJSON(data).Request()
|
|
if err != nil {
|
|
return fmt.Errorf("api reset password: HTTP request creation failed: %s", err)
|
|
}
|
|
log.Debugf("api reset: URL: '%s'", req.URL)
|
|
|
|
httpClient := http.Client{Timeout: 20 * time.Second}
|
|
resp, err := httpClient.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("api reset password: API call failed : %s", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return fmt.Errorf("api reset password: unable to read API response body: %s", err.Error())
|
|
}
|
|
|
|
if resp.StatusCode != 200 {
|
|
return fmt.Errorf("api reset password: return bad HTTP code (%d): %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
jsonResp := ApiResp{}
|
|
err = json.Unmarshal(body, &jsonResp)
|
|
if err != nil {
|
|
return fmt.Errorf("api reset password: unable to unmarshall api response '%s': %s", string(body), err.Error())
|
|
}
|
|
if jsonResp.StatusCode != 200 {
|
|
return fmt.Errorf("api reset password: return bad HTTP code (%d): %s", jsonResp.StatusCode, string(body))
|
|
}
|
|
return nil
|
|
}
|