crowdsec/pkg/cwapi/auth.go
AlteredCoder d23512e9c6
improve logging (#164)
Co-authored-by: AlteredCoder <AlteredCoder>
2020-07-30 15:30:02 +02:00

206 lines
6 KiB
Go

package cwapi
import (
"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"
"gopkg.in/tomb.v2"
)
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:"-"`
/*mostly for mocking/faking api*/
Muted bool `yaml:"-"`
DebugDump bool `yaml:"-"`
/*runtime*/
tokenExpired bool `yaml:"-"`
toPush []types.Event `yaml:"-"`
Http *sling.Sling `yaml:"-"`
PusherTomb tomb.Tomb `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 /")
}
httpClient := &http.Client{Timeout: 20 * time.Second}
ctx.Http = sling.New().Client(httpClient).Base(ctx.BaseURL+"/"+ctx.ApiVersion+"/").Set("User-Agent", fmt.Sprintf("Crowdsec/%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
ctx.PusherTomb.Go(func() error {
err := ctx.pushLoop()
if err != nil {
log.Errorf("api push error : %s", err)
return err
}
return nil
})
return nil
}
func (ctx *ApiCtx) Shutdown() error {
ctx.PusherTomb.Kill(nil)
log.Infof("Waiting for API routine to finish")
if err := ctx.PusherTomb.Wait(); err != nil {
return fmt.Errorf("API routine returned error : %s", err)
}
return nil
}
func (ctx *ApiCtx) Signin() error {
if ctx.Creds.User == "" || ctx.Creds.Password == "" {
return fmt.Errorf("api signin: missing credentials in api.yaml")
}
jsonResp := &ApiResp{}
errResp := &ApiResp{}
resp, err := ctx.Http.New().Post(ctx.SigninPath).BodyJSON(ctx.Creds).Receive(jsonResp, errResp)
if err != nil {
return fmt.Errorf("api signin: HTTP request creation failed: %s", err)
}
if resp.StatusCode != 200 {
return fmt.Errorf("api signin: return bad HTTP code (%d): %s", resp.StatusCode, errResp.Message)
}
if jsonResp.Message == "" || jsonResp.StatusCode != 200 {
return fmt.Errorf("api signin failed")
}
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
jsonResp := &ApiResp{}
errResp := &ApiResp{}
resp, err := ctx.Http.New().Post(ctx.RegisterPath).BodyJSON(ctx.Creds).Receive(jsonResp, errResp)
if err != nil {
return fmt.Errorf("api register machine: HTTP request creation failed: %s", err)
}
if resp.StatusCode != 200 {
return fmt.Errorf("api register machine: return bad HTTP code (%d) : %s", resp.StatusCode, errResp.Message)
}
if jsonResp.Message == "" || jsonResp.Message != "OK" || jsonResp.StatusCode != 200 {
return fmt.Errorf("api register machine failed")
}
return nil
}
func (ctx *ApiCtx) ResetPassword(machineID string, password string) error {
ctx.Creds.User = machineID
ctx.Creds.Password = password
jsonResp := &ApiResp{}
errResp := &ApiResp{}
data := map[string]string{"machine_id": ctx.Creds.User, "password": ctx.Creds.Password}
resp, err := ctx.Http.New().Post(ctx.ResetPwdPath).BodyJSON(data).Receive(jsonResp, errResp)
if err != nil {
return fmt.Errorf("api reset password: HTTP request creation failed: %s", err)
}
if resp.StatusCode != 200 {
return fmt.Errorf("api reset password: return bad HTTP code (%d): %s", resp.StatusCode, errResp.Message)
}
if jsonResp.Message == "" || jsonResp.StatusCode != 200 {
return fmt.Errorf("api reset password failed")
}
return nil
}