mirror of
https://github.com/crowdsecurity/crowdsec.git
synced 2025-05-10 20:05:55 +02:00
Allow auto registration of machines in LAPI (#3202)
Co-authored-by: marco <marco@crowdsec.net>
This commit is contained in:
parent
8c0c10cd7a
commit
d2616766de
17 changed files with 548 additions and 173 deletions
|
@ -95,7 +95,7 @@ func (cli *cliLapi) Status(out io.Writer, hub *cwhub.Hub) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (cli *cliLapi) register(apiURL string, outputFile string, machine string) error {
|
||||
func (cli *cliLapi) register(apiURL string, outputFile string, machine string, token string) error {
|
||||
var err error
|
||||
|
||||
lapiUser := machine
|
||||
|
@ -116,11 +116,12 @@ func (cli *cliLapi) register(apiURL string, outputFile string, machine string) e
|
|||
}
|
||||
|
||||
_, err = apiclient.RegisterClient(&apiclient.Config{
|
||||
MachineID: lapiUser,
|
||||
Password: password,
|
||||
UserAgent: cwversion.UserAgent(),
|
||||
URL: apiurl,
|
||||
VersionPrefix: LAPIURLPrefix,
|
||||
MachineID: lapiUser,
|
||||
Password: password,
|
||||
UserAgent: cwversion.UserAgent(),
|
||||
RegistrationToken: token,
|
||||
URL: apiurl,
|
||||
VersionPrefix: LAPIURLPrefix,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("api client register: %w", err)
|
||||
|
@ -138,10 +139,12 @@ func (cli *cliLapi) register(apiURL string, outputFile string, machine string) e
|
|||
dumpFile = ""
|
||||
}
|
||||
|
||||
apiCfg := csconfig.ApiCredentialsCfg{
|
||||
Login: lapiUser,
|
||||
Password: password.String(),
|
||||
URL: apiURL,
|
||||
apiCfg := cfg.API.Client.Credentials
|
||||
apiCfg.Login = lapiUser
|
||||
apiCfg.Password = password.String()
|
||||
|
||||
if apiURL != "" {
|
||||
apiCfg.URL = apiURL
|
||||
}
|
||||
|
||||
apiConfigDump, err := yaml.Marshal(apiCfg)
|
||||
|
@ -212,6 +215,7 @@ func (cli *cliLapi) newRegisterCmd() *cobra.Command {
|
|||
apiURL string
|
||||
outputFile string
|
||||
machine string
|
||||
token string
|
||||
)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -222,7 +226,7 @@ Keep in mind the machine needs to be validated by an administrator on LAPI side
|
|||
Args: cobra.MinimumNArgs(0),
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
return cli.register(apiURL, outputFile, machine)
|
||||
return cli.register(apiURL, outputFile, machine, token)
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -230,6 +234,7 @@ Keep in mind the machine needs to be validated by an administrator on LAPI side
|
|||
flags.StringVarP(&apiURL, "url", "u", "", "URL of the API (ie. http://127.0.0.1)")
|
||||
flags.StringVarP(&outputFile, "file", "f", "", "output file destination")
|
||||
flags.StringVar(&machine, "machine", "", "Name of the machine to register with")
|
||||
flags.StringVar(&token, "token", "", "Auto registration token to use")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
@ -177,7 +177,7 @@ func RegisterClient(config *Config, client *http.Client) (*ApiClient, error) {
|
|||
c.Alerts = (*AlertsService)(&c.common)
|
||||
c.Auth = (*AuthService)(&c.common)
|
||||
|
||||
resp, err := c.Auth.RegisterWatcher(context.Background(), models.WatcherRegistrationRequest{MachineID: &config.MachineID, Password: &config.Password})
|
||||
resp, err := c.Auth.RegisterWatcher(context.Background(), models.WatcherRegistrationRequest{MachineID: &config.MachineID, Password: &config.Password, RegistrationToken: config.RegistrationToken})
|
||||
/*if we have http status, return it*/
|
||||
if err != nil {
|
||||
if resp != nil && resp.Response != nil {
|
||||
|
|
|
@ -7,12 +7,13 @@ import (
|
|||
)
|
||||
|
||||
type Config struct {
|
||||
MachineID string
|
||||
Password strfmt.Password
|
||||
Scenarios []string
|
||||
URL *url.URL
|
||||
PapiURL *url.URL
|
||||
VersionPrefix string
|
||||
UserAgent string
|
||||
UpdateScenario func() ([]string, error)
|
||||
MachineID string
|
||||
Password strfmt.Password
|
||||
Scenarios []string
|
||||
URL *url.URL
|
||||
PapiURL *url.URL
|
||||
VersionPrefix string
|
||||
UserAgent string
|
||||
RegistrationToken string
|
||||
UpdateScenario func() ([]string, error)
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ func InitMachineTest(t *testing.T) (*gin.Engine, models.WatcherAuthResponse, csc
|
|||
}
|
||||
|
||||
func LoginToTestAPI(t *testing.T, router *gin.Engine, config csconfig.Config) models.WatcherAuthResponse {
|
||||
body := CreateTestMachine(t, router)
|
||||
body := CreateTestMachine(t, router, "")
|
||||
ValidateMachine(t, "test", config.API.Server.DbConfig)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
|
|
@ -21,7 +21,7 @@ import (
|
|||
"github.com/crowdsecurity/go-cs-lib/trace"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/apiserver/controllers"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/apiserver/middlewares/v1"
|
||||
v1 "github.com/crowdsecurity/crowdsec/pkg/apiserver/middlewares/v1"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/csplugin"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/database"
|
||||
|
@ -235,6 +235,7 @@ func NewServer(config *csconfig.LocalApiServerCfg) (*APIServer, error) {
|
|||
Log: clog,
|
||||
ConsoleConfig: config.ConsoleConfig,
|
||||
DisableRemoteLapiRegistration: config.DisableRemoteLapiRegistration,
|
||||
AutoRegisterCfg: config.AutoRegister,
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
|
@ -29,10 +29,15 @@ import (
|
|||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||
)
|
||||
|
||||
const (
|
||||
validRegistrationToken = "igheethauCaeteSaiyee3LosohPhahze"
|
||||
invalidRegistrationToken = "vohl1feibechieG5coh8musheish2auj"
|
||||
)
|
||||
|
||||
var (
|
||||
testMachineID = "test"
|
||||
testPassword = strfmt.Password("test")
|
||||
MachineTest = models.WatcherAuthRequest{
|
||||
MachineTest = models.WatcherRegistrationRequest{
|
||||
MachineID: &testMachineID,
|
||||
Password: &testPassword,
|
||||
}
|
||||
|
@ -65,6 +70,14 @@ func LoadTestConfig(t *testing.T) csconfig.Config {
|
|||
ShareTaintedScenarios: new(bool),
|
||||
ShareCustomScenarios: new(bool),
|
||||
},
|
||||
AutoRegister: &csconfig.LocalAPIAutoRegisterCfg{
|
||||
Enable: ptr.Of(true),
|
||||
Token: validRegistrationToken,
|
||||
AllowedRanges: []string{
|
||||
"127.0.0.1/8",
|
||||
"::1/128",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
apiConfig := csconfig.APICfg{
|
||||
|
@ -75,6 +88,9 @@ func LoadTestConfig(t *testing.T) csconfig.Config {
|
|||
err := config.API.Server.LoadProfiles()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = config.API.Server.LoadAutoRegister()
|
||||
require.NoError(t, err)
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
|
@ -113,6 +129,9 @@ func LoadTestConfigForwardedFor(t *testing.T) csconfig.Config {
|
|||
err := config.API.Server.LoadProfiles()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = config.API.Server.LoadAutoRegister()
|
||||
require.NoError(t, err)
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
|
@ -251,8 +270,10 @@ func readDecisionsStreamResp(t *testing.T, resp *httptest.ResponseRecorder) (map
|
|||
return response, resp.Code
|
||||
}
|
||||
|
||||
func CreateTestMachine(t *testing.T, router *gin.Engine) string {
|
||||
b, err := json.Marshal(MachineTest)
|
||||
func CreateTestMachine(t *testing.T, router *gin.Engine, token string) string {
|
||||
regReq := MachineTest
|
||||
regReq.RegistrationToken = token
|
||||
b, err := json.Marshal(regReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
body := string(b)
|
||||
|
|
|
@ -29,6 +29,7 @@ type Controller struct {
|
|||
ConsoleConfig *csconfig.ConsoleConfig
|
||||
TrustedIPs []net.IPNet
|
||||
HandlerV1 *v1.Controller
|
||||
AutoRegisterCfg *csconfig.LocalAPIAutoRegisterCfg
|
||||
DisableRemoteLapiRegistration bool
|
||||
}
|
||||
|
||||
|
@ -89,6 +90,7 @@ func (c *Controller) NewV1() error {
|
|||
PluginChannel: c.PluginChannel,
|
||||
ConsoleConfig: *c.ConsoleConfig,
|
||||
TrustedIPs: c.TrustedIPs,
|
||||
AutoRegisterCfg: c.AutoRegisterCfg,
|
||||
}
|
||||
|
||||
c.HandlerV1, err = v1.New(&v1Config)
|
||||
|
|
|
@ -23,9 +23,10 @@ type Controller struct {
|
|||
AlertsAddChan chan []*models.Alert
|
||||
DecisionDeleteChan chan []*models.Decision
|
||||
|
||||
PluginChannel chan csplugin.ProfileAlert
|
||||
ConsoleConfig csconfig.ConsoleConfig
|
||||
TrustedIPs []net.IPNet
|
||||
PluginChannel chan csplugin.ProfileAlert
|
||||
ConsoleConfig csconfig.ConsoleConfig
|
||||
TrustedIPs []net.IPNet
|
||||
AutoRegisterCfg *csconfig.LocalAPIAutoRegisterCfg
|
||||
}
|
||||
|
||||
type ControllerV1Config struct {
|
||||
|
@ -36,9 +37,10 @@ type ControllerV1Config struct {
|
|||
AlertsAddChan chan []*models.Alert
|
||||
DecisionDeleteChan chan []*models.Decision
|
||||
|
||||
PluginChannel chan csplugin.ProfileAlert
|
||||
ConsoleConfig csconfig.ConsoleConfig
|
||||
TrustedIPs []net.IPNet
|
||||
PluginChannel chan csplugin.ProfileAlert
|
||||
ConsoleConfig csconfig.ConsoleConfig
|
||||
TrustedIPs []net.IPNet
|
||||
AutoRegisterCfg *csconfig.LocalAPIAutoRegisterCfg
|
||||
}
|
||||
|
||||
func New(cfg *ControllerV1Config) (*Controller, error) {
|
||||
|
@ -59,9 +61,10 @@ func New(cfg *ControllerV1Config) (*Controller, error) {
|
|||
PluginChannel: cfg.PluginChannel,
|
||||
ConsoleConfig: cfg.ConsoleConfig,
|
||||
TrustedIPs: cfg.TrustedIPs,
|
||||
AutoRegisterCfg: cfg.AutoRegisterCfg,
|
||||
}
|
||||
v1.Middlewares, err = middlewares.NewMiddlewares(cfg.DbClient)
|
||||
|
||||
v1.Middlewares, err = middlewares.NewMiddlewares(cfg.DbClient)
|
||||
if err != nil {
|
||||
return v1, err
|
||||
}
|
||||
|
|
|
@ -1,15 +1,50 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-openapi/strfmt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||
)
|
||||
|
||||
func (c *Controller) shouldAutoRegister(token string, gctx *gin.Context) (bool, error) {
|
||||
if !*c.AutoRegisterCfg.Enable {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
clientIP := net.ParseIP(gctx.ClientIP())
|
||||
|
||||
// Can probaby happen if using unix socket ?
|
||||
if clientIP == nil {
|
||||
log.Warnf("Failed to parse client IP for watcher self registration: %s", gctx.ClientIP())
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if token == "" || c.AutoRegisterCfg == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Check the token
|
||||
if token != c.AutoRegisterCfg.Token {
|
||||
return false, errors.New("invalid token for auto registration")
|
||||
}
|
||||
|
||||
// Check the source IP
|
||||
for _, ipRange := range c.AutoRegisterCfg.AllowedRangesParsed {
|
||||
if ipRange.Contains(clientIP) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, errors.New("IP not in allowed range for auto registration")
|
||||
}
|
||||
|
||||
func (c *Controller) CreateMachine(gctx *gin.Context) {
|
||||
var input models.WatcherRegistrationRequest
|
||||
|
||||
|
@ -19,14 +54,27 @@ func (c *Controller) CreateMachine(gctx *gin.Context) {
|
|||
}
|
||||
|
||||
if err := input.Validate(strfmt.Default); err != nil {
|
||||
gctx.JSON(http.StatusUnprocessableEntity, gin.H{"message": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
autoRegister, err := c.shouldAutoRegister(input.RegistrationToken, gctx)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{"ip": gctx.ClientIP(), "machine_id": *input.MachineID}).Errorf("Auto-register failed: %s", err)
|
||||
gctx.JSON(http.StatusUnauthorized, gin.H{"message": err.Error()})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := c.DBClient.CreateMachine(input.MachineID, input.Password, gctx.ClientIP(), autoRegister, false, types.PasswordAuthType); err != nil {
|
||||
c.HandleDBErrors(gctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := c.DBClient.CreateMachine(input.MachineID, input.Password, gctx.ClientIP(), false, false, types.PasswordAuthType); err != nil {
|
||||
c.HandleDBErrors(gctx, err)
|
||||
return
|
||||
if autoRegister {
|
||||
log.WithFields(log.Fields{"ip": gctx.ClientIP(), "machine_id": *input.MachineID}).Info("Auto-registered machine")
|
||||
gctx.Status(http.StatusAccepted)
|
||||
} else {
|
||||
gctx.Status(http.StatusCreated)
|
||||
}
|
||||
|
||||
gctx.Status(http.StatusCreated)
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
func TestLogin(t *testing.T) {
|
||||
router, config := NewAPITest(t)
|
||||
|
||||
body := CreateTestMachine(t, router)
|
||||
body := CreateTestMachine(t, router, "")
|
||||
|
||||
// Login with machine not validated yet
|
||||
w := httptest.NewRecorder()
|
||||
|
|
|
@ -9,6 +9,8 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/crowdsecurity/go-cs-lib/ptr"
|
||||
)
|
||||
|
||||
func TestCreateMachine(t *testing.T) {
|
||||
|
@ -20,7 +22,7 @@ func TestCreateMachine(t *testing.T) {
|
|||
req.Header.Add("User-Agent", UserAgent)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, 400, w.Code)
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
assert.Equal(t, `{"message":"invalid character 'e' in literal true (expecting 'r')"}`, w.Body.String())
|
||||
|
||||
// Create machine with invalid input
|
||||
|
@ -29,7 +31,7 @@ func TestCreateMachine(t *testing.T) {
|
|||
req.Header.Add("User-Agent", UserAgent)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, 500, w.Code)
|
||||
assert.Equal(t, http.StatusUnprocessableEntity, w.Code)
|
||||
assert.Equal(t, `{"message":"validation failure list:\nmachine_id in body is required\npassword in body is required"}`, w.Body.String())
|
||||
|
||||
// Create machine
|
||||
|
@ -43,7 +45,7 @@ func TestCreateMachine(t *testing.T) {
|
|||
req.Header.Add("User-Agent", UserAgent)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, 201, w.Code)
|
||||
assert.Equal(t, http.StatusCreated, w.Code)
|
||||
assert.Equal(t, "", w.Body.String())
|
||||
}
|
||||
|
||||
|
@ -62,7 +64,7 @@ func TestCreateMachineWithForwardedFor(t *testing.T) {
|
|||
req.Header.Add("X-Real-Ip", "1.1.1.1")
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, 201, w.Code)
|
||||
assert.Equal(t, http.StatusCreated, w.Code)
|
||||
assert.Equal(t, "", w.Body.String())
|
||||
|
||||
ip := GetMachineIP(t, *MachineTest.MachineID, config.API.Server.DbConfig)
|
||||
|
@ -85,7 +87,7 @@ func TestCreateMachineWithForwardedForNoConfig(t *testing.T) {
|
|||
req.Header.Add("X-Real-IP", "1.1.1.1")
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, 201, w.Code)
|
||||
assert.Equal(t, http.StatusCreated, w.Code)
|
||||
assert.Equal(t, "", w.Body.String())
|
||||
|
||||
ip := GetMachineIP(t, *MachineTest.MachineID, config.API.Server.DbConfig)
|
||||
|
@ -109,7 +111,7 @@ func TestCreateMachineWithoutForwardedFor(t *testing.T) {
|
|||
req.Header.Add("User-Agent", UserAgent)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, 201, w.Code)
|
||||
assert.Equal(t, http.StatusCreated, w.Code)
|
||||
assert.Equal(t, "", w.Body.String())
|
||||
|
||||
ip := GetMachineIP(t, *MachineTest.MachineID, config.API.Server.DbConfig)
|
||||
|
@ -122,7 +124,7 @@ func TestCreateMachineWithoutForwardedFor(t *testing.T) {
|
|||
func TestCreateMachineAlreadyExist(t *testing.T) {
|
||||
router, _ := NewAPITest(t)
|
||||
|
||||
body := CreateTestMachine(t, router)
|
||||
body := CreateTestMachine(t, router, "")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(http.MethodPost, "/v1/watchers", strings.NewReader(body))
|
||||
|
@ -134,6 +136,90 @@ func TestCreateMachineAlreadyExist(t *testing.T) {
|
|||
req.Header.Add("User-Agent", UserAgent)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, 403, w.Code)
|
||||
assert.Equal(t, http.StatusForbidden, w.Code)
|
||||
assert.Equal(t, `{"message":"user 'test': user already exist"}`, w.Body.String())
|
||||
}
|
||||
|
||||
func TestAutoRegistration(t *testing.T) {
|
||||
router, _ := NewAPITest(t)
|
||||
|
||||
//Invalid registration token / valid source IP
|
||||
regReq := MachineTest
|
||||
regReq.RegistrationToken = invalidRegistrationToken
|
||||
b, err := json.Marshal(regReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
body := string(b)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(http.MethodPost, "/v1/watchers", strings.NewReader(body))
|
||||
req.Header.Add("User-Agent", UserAgent)
|
||||
req.RemoteAddr = "127.0.0.1:4242"
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||||
|
||||
//Invalid registration token / invalid source IP
|
||||
regReq = MachineTest
|
||||
regReq.RegistrationToken = invalidRegistrationToken
|
||||
b, err = json.Marshal(regReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
body = string(b)
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest(http.MethodPost, "/v1/watchers", strings.NewReader(body))
|
||||
req.Header.Add("User-Agent", UserAgent)
|
||||
req.RemoteAddr = "42.42.42.42:4242"
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||||
|
||||
//valid registration token / invalid source IP
|
||||
regReq = MachineTest
|
||||
regReq.RegistrationToken = validRegistrationToken
|
||||
b, err = json.Marshal(regReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
body = string(b)
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest(http.MethodPost, "/v1/watchers", strings.NewReader(body))
|
||||
req.Header.Add("User-Agent", UserAgent)
|
||||
req.RemoteAddr = "42.42.42.42:4242"
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||||
|
||||
//Valid registration token / valid source IP
|
||||
regReq = MachineTest
|
||||
regReq.RegistrationToken = validRegistrationToken
|
||||
b, err = json.Marshal(regReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
body = string(b)
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest(http.MethodPost, "/v1/watchers", strings.NewReader(body))
|
||||
req.Header.Add("User-Agent", UserAgent)
|
||||
req.RemoteAddr = "127.0.0.1:4242"
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusAccepted, w.Code)
|
||||
|
||||
//No token / valid source IP
|
||||
regReq = MachineTest
|
||||
regReq.MachineID = ptr.Of("test2")
|
||||
b, err = json.Marshal(regReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
body = string(b)
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest(http.MethodPost, "/v1/watchers", strings.NewReader(body))
|
||||
req.Header.Add("User-Agent", UserAgent)
|
||||
req.RemoteAddr = "127.0.0.1:4242"
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusCreated, w.Code)
|
||||
}
|
||||
|
|
|
@ -236,32 +236,40 @@ type CapiWhitelist struct {
|
|||
Cidrs []*net.IPNet `yaml:"cidrs,omitempty"`
|
||||
}
|
||||
|
||||
type LocalAPIAutoRegisterCfg struct {
|
||||
Enable *bool `yaml:"enabled"`
|
||||
Token string `yaml:"token"`
|
||||
AllowedRanges []string `yaml:"allowed_ranges,omitempty"`
|
||||
AllowedRangesParsed []*net.IPNet `yaml:"-"`
|
||||
}
|
||||
|
||||
/*local api service configuration*/
|
||||
type LocalApiServerCfg struct {
|
||||
Enable *bool `yaml:"enable"`
|
||||
ListenURI string `yaml:"listen_uri,omitempty"` // 127.0.0.1:8080
|
||||
ListenSocket string `yaml:"listen_socket,omitempty"`
|
||||
TLS *TLSCfg `yaml:"tls"`
|
||||
DbConfig *DatabaseCfg `yaml:"-"`
|
||||
LogDir string `yaml:"-"`
|
||||
LogMedia string `yaml:"-"`
|
||||
OnlineClient *OnlineApiClientCfg `yaml:"online_client"`
|
||||
ProfilesPath string `yaml:"profiles_path,omitempty"`
|
||||
ConsoleConfigPath string `yaml:"console_path,omitempty"`
|
||||
ConsoleConfig *ConsoleConfig `yaml:"-"`
|
||||
Profiles []*ProfileCfg `yaml:"-"`
|
||||
LogLevel *log.Level `yaml:"log_level"`
|
||||
UseForwardedForHeaders bool `yaml:"use_forwarded_for_headers,omitempty"`
|
||||
TrustedProxies *[]string `yaml:"trusted_proxies,omitempty"`
|
||||
CompressLogs *bool `yaml:"-"`
|
||||
LogMaxSize int `yaml:"-"`
|
||||
LogMaxAge int `yaml:"-"`
|
||||
LogMaxFiles int `yaml:"-"`
|
||||
TrustedIPs []string `yaml:"trusted_ips,omitempty"`
|
||||
PapiLogLevel *log.Level `yaml:"papi_log_level"`
|
||||
DisableRemoteLapiRegistration bool `yaml:"disable_remote_lapi_registration,omitempty"`
|
||||
CapiWhitelistsPath string `yaml:"capi_whitelists_path,omitempty"`
|
||||
CapiWhitelists *CapiWhitelist `yaml:"-"`
|
||||
Enable *bool `yaml:"enable"`
|
||||
ListenURI string `yaml:"listen_uri,omitempty"` // 127.0.0.1:8080
|
||||
ListenSocket string `yaml:"listen_socket,omitempty"`
|
||||
TLS *TLSCfg `yaml:"tls"`
|
||||
DbConfig *DatabaseCfg `yaml:"-"`
|
||||
LogDir string `yaml:"-"`
|
||||
LogMedia string `yaml:"-"`
|
||||
OnlineClient *OnlineApiClientCfg `yaml:"online_client"`
|
||||
ProfilesPath string `yaml:"profiles_path,omitempty"`
|
||||
ConsoleConfigPath string `yaml:"console_path,omitempty"`
|
||||
ConsoleConfig *ConsoleConfig `yaml:"-"`
|
||||
Profiles []*ProfileCfg `yaml:"-"`
|
||||
LogLevel *log.Level `yaml:"log_level"`
|
||||
UseForwardedForHeaders bool `yaml:"use_forwarded_for_headers,omitempty"`
|
||||
TrustedProxies *[]string `yaml:"trusted_proxies,omitempty"`
|
||||
CompressLogs *bool `yaml:"-"`
|
||||
LogMaxSize int `yaml:"-"`
|
||||
LogMaxAge int `yaml:"-"`
|
||||
LogMaxFiles int `yaml:"-"`
|
||||
TrustedIPs []string `yaml:"trusted_ips,omitempty"`
|
||||
PapiLogLevel *log.Level `yaml:"papi_log_level"`
|
||||
DisableRemoteLapiRegistration bool `yaml:"disable_remote_lapi_registration,omitempty"`
|
||||
CapiWhitelistsPath string `yaml:"capi_whitelists_path,omitempty"`
|
||||
CapiWhitelists *CapiWhitelist `yaml:"-"`
|
||||
AutoRegister *LocalAPIAutoRegisterCfg `yaml:"auto_registration,omitempty"`
|
||||
}
|
||||
|
||||
func (c *LocalApiServerCfg) ClientURL() string {
|
||||
|
@ -348,6 +356,14 @@ func (c *Config) LoadAPIServer(inCli bool) error {
|
|||
log.Infof("loaded capi whitelist from %s: %d IPs, %d CIDRs", c.API.Server.CapiWhitelistsPath, len(c.API.Server.CapiWhitelists.Ips), len(c.API.Server.CapiWhitelists.Cidrs))
|
||||
}
|
||||
|
||||
if err := c.API.Server.LoadAutoRegister(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.API.Server.AutoRegister != nil && c.API.Server.AutoRegister.Enable != nil && *c.API.Server.AutoRegister.Enable && !inCli {
|
||||
log.Infof("auto LAPI registration enabled for ranges %+v", c.API.Server.AutoRegister.AllowedRanges)
|
||||
}
|
||||
|
||||
c.API.Server.LogDir = c.Common.LogDir
|
||||
c.API.Server.LogMedia = c.Common.LogMedia
|
||||
c.API.Server.CompressLogs = c.Common.CompressLogs
|
||||
|
@ -455,3 +471,47 @@ func (c *Config) LoadAPIClient() error {
|
|||
|
||||
return c.API.Client.Load()
|
||||
}
|
||||
|
||||
func (c *LocalApiServerCfg) LoadAutoRegister() error {
|
||||
if c.AutoRegister == nil {
|
||||
c.AutoRegister = &LocalAPIAutoRegisterCfg{
|
||||
Enable: ptr.Of(false),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Disable by default
|
||||
if c.AutoRegister.Enable == nil {
|
||||
c.AutoRegister.Enable = ptr.Of(false)
|
||||
}
|
||||
|
||||
if !*c.AutoRegister.Enable {
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.AutoRegister.Token == "" {
|
||||
return errors.New("missing token value for api.server.auto_register")
|
||||
}
|
||||
|
||||
if len(c.AutoRegister.Token) < 32 {
|
||||
return errors.New("token value for api.server.auto_register is too short (min 32 characters)")
|
||||
}
|
||||
|
||||
if c.AutoRegister.AllowedRanges == nil {
|
||||
return errors.New("missing allowed_ranges value for api.server.auto_register")
|
||||
}
|
||||
|
||||
c.AutoRegister.AllowedRangesParsed = make([]*net.IPNet, 0, len(c.AutoRegister.AllowedRanges))
|
||||
|
||||
for _, ipRange := range c.AutoRegister.AllowedRanges {
|
||||
_, ipNet, err := net.ParseCIDR(ipRange)
|
||||
if err != nil {
|
||||
return fmt.Errorf("auto_register: failed to parse allowed range '%s': %w", ipRange, err)
|
||||
}
|
||||
|
||||
c.AutoRegister.AllowedRangesParsed = append(c.AutoRegister.AllowedRangesParsed, ipNet)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -217,6 +217,12 @@ func TestLoadAPIServer(t *testing.T) {
|
|||
ProfilesPath: "./testdata/profiles.yaml",
|
||||
UseForwardedForHeaders: false,
|
||||
PapiLogLevel: &logLevel,
|
||||
AutoRegister: &LocalAPIAutoRegisterCfg{
|
||||
Enable: ptr.Of(false),
|
||||
Token: "",
|
||||
AllowedRanges: nil,
|
||||
AllowedRangesParsed: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -312,6 +312,9 @@ paths:
|
|||
'201':
|
||||
description: Watcher Created
|
||||
headers: {}
|
||||
'202':
|
||||
description: Watcher Validated
|
||||
headers: {}
|
||||
'400':
|
||||
description: "400 response"
|
||||
schema:
|
||||
|
@ -726,6 +729,10 @@ definitions:
|
|||
password:
|
||||
type: string
|
||||
format: password
|
||||
registration_token:
|
||||
type: string
|
||||
minLength: 32
|
||||
maxLength: 255
|
||||
required:
|
||||
- machine_id
|
||||
- password
|
||||
|
|
|
@ -27,6 +27,11 @@ type WatcherRegistrationRequest struct {
|
|||
// Required: true
|
||||
// Format: password
|
||||
Password *strfmt.Password `json:"password"`
|
||||
|
||||
// registration token
|
||||
// Max Length: 255
|
||||
// Min Length: 32
|
||||
RegistrationToken string `json:"registration_token,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this watcher registration request
|
||||
|
@ -41,6 +46,10 @@ func (m *WatcherRegistrationRequest) Validate(formats strfmt.Registry) error {
|
|||
res = append(res, err)
|
||||
}
|
||||
|
||||
if err := m.validateRegistrationToken(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
|
@ -69,6 +78,22 @@ func (m *WatcherRegistrationRequest) validatePassword(formats strfmt.Registry) e
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *WatcherRegistrationRequest) validateRegistrationToken(formats strfmt.Registry) error {
|
||||
if swag.IsZero(m.RegistrationToken) { // not required
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := validate.MinLength("registration_token", "body", m.RegistrationToken, 32); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validate.MaxLength("registration_token", "body", m.RegistrationToken, 255); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContextValidate validates this watcher registration request based on context it is used
|
||||
func (m *WatcherRegistrationRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||
return nil
|
||||
|
|
|
@ -209,109 +209,6 @@ teardown() {
|
|||
rm -rf -- "${backupdir:?}"
|
||||
}
|
||||
|
||||
@test "cscli lapi status" {
|
||||
rune -0 ./instance-crowdsec start
|
||||
rune -0 cscli lapi status
|
||||
|
||||
assert_output --partial "Loaded credentials from"
|
||||
assert_output --partial "Trying to authenticate with username"
|
||||
assert_output --partial "You can successfully interact with Local API (LAPI)"
|
||||
}
|
||||
|
||||
@test "cscli - missing LAPI credentials file" {
|
||||
LOCAL_API_CREDENTIALS=$(config_get '.api.client.credentials_path')
|
||||
rm -f "$LOCAL_API_CREDENTIALS"
|
||||
rune -1 cscli lapi status
|
||||
assert_stderr --partial "loading api client: while reading yaml file: open ${LOCAL_API_CREDENTIALS}: no such file or directory"
|
||||
|
||||
rune -1 cscli alerts list
|
||||
assert_stderr --partial "loading api client: while reading yaml file: open ${LOCAL_API_CREDENTIALS}: no such file or directory"
|
||||
|
||||
rune -1 cscli decisions list
|
||||
assert_stderr --partial "loading api client: while reading yaml file: open ${LOCAL_API_CREDENTIALS}: no such file or directory"
|
||||
}
|
||||
|
||||
@test "cscli - empty LAPI credentials file" {
|
||||
LOCAL_API_CREDENTIALS=$(config_get '.api.client.credentials_path')
|
||||
: > "$LOCAL_API_CREDENTIALS"
|
||||
rune -1 cscli lapi status
|
||||
assert_stderr --partial "no credentials or URL found in api client configuration '${LOCAL_API_CREDENTIALS}'"
|
||||
|
||||
rune -1 cscli alerts list
|
||||
assert_stderr --partial "no credentials or URL found in api client configuration '${LOCAL_API_CREDENTIALS}'"
|
||||
|
||||
rune -1 cscli decisions list
|
||||
assert_stderr --partial "no credentials or URL found in api client configuration '${LOCAL_API_CREDENTIALS}'"
|
||||
}
|
||||
|
||||
@test "cscli - LAPI credentials file can reference env variables" {
|
||||
LOCAL_API_CREDENTIALS=$(config_get '.api.client.credentials_path')
|
||||
URL=$(config_get "$LOCAL_API_CREDENTIALS" '.url')
|
||||
export URL
|
||||
LOGIN=$(config_get "$LOCAL_API_CREDENTIALS" '.login')
|
||||
export LOGIN
|
||||
PASSWORD=$(config_get "$LOCAL_API_CREDENTIALS" '.password')
|
||||
export PASSWORD
|
||||
|
||||
# shellcheck disable=SC2016
|
||||
echo '{"url":"$URL","login":"$LOGIN","password":"$PASSWORD"}' > "$LOCAL_API_CREDENTIALS".local
|
||||
|
||||
config_set '.crowdsec_service.enable=false'
|
||||
rune -0 ./instance-crowdsec start
|
||||
|
||||
rune -0 cscli lapi status
|
||||
assert_output --partial "You can successfully interact with Local API (LAPI)"
|
||||
|
||||
rm "$LOCAL_API_CREDENTIALS".local
|
||||
|
||||
# shellcheck disable=SC2016
|
||||
config_set "$LOCAL_API_CREDENTIALS" '.url="$URL"'
|
||||
# shellcheck disable=SC2016
|
||||
config_set "$LOCAL_API_CREDENTIALS" '.login="$LOGIN"'
|
||||
# shellcheck disable=SC2016
|
||||
config_set "$LOCAL_API_CREDENTIALS" '.password="$PASSWORD"'
|
||||
|
||||
rune -0 cscli lapi status
|
||||
assert_output --partial "You can successfully interact with Local API (LAPI)"
|
||||
|
||||
# but if a variable is not defined, there is no specific error message
|
||||
unset URL
|
||||
rune -1 cscli lapi status
|
||||
# shellcheck disable=SC2016
|
||||
assert_stderr --partial 'BaseURL must have a trailing slash'
|
||||
}
|
||||
|
||||
@test "cscli - missing LAPI client settings" {
|
||||
config_set 'del(.api.client)'
|
||||
rune -1 cscli lapi status
|
||||
assert_stderr --partial "loading api client: no API client section in configuration"
|
||||
|
||||
rune -1 cscli alerts list
|
||||
assert_stderr --partial "loading api client: no API client section in configuration"
|
||||
|
||||
rune -1 cscli decisions list
|
||||
assert_stderr --partial "loading api client: no API client section in configuration"
|
||||
}
|
||||
|
||||
@test "cscli - malformed LAPI url" {
|
||||
LOCAL_API_CREDENTIALS=$(config_get '.api.client.credentials_path')
|
||||
config_set "$LOCAL_API_CREDENTIALS" '.url="http://127.0.0.1:-80"'
|
||||
|
||||
rune -1 cscli lapi status -o json
|
||||
rune -0 jq -r '.msg' <(stderr)
|
||||
assert_output 'failed to authenticate to Local API (LAPI): parse "http://127.0.0.1:-80/": invalid port ":-80" after host'
|
||||
}
|
||||
|
||||
@test "cscli - bad LAPI password" {
|
||||
rune -0 ./instance-crowdsec start
|
||||
LOCAL_API_CREDENTIALS=$(config_get '.api.client.credentials_path')
|
||||
config_set "$LOCAL_API_CREDENTIALS" '.password="meh"'
|
||||
|
||||
rune -1 cscli lapi status -o json
|
||||
rune -0 jq -r '.msg' <(stderr)
|
||||
assert_output 'failed to authenticate to Local API (LAPI): API error: incorrect Username or Password'
|
||||
}
|
||||
|
||||
@test "'cscli completion' with or without configuration file" {
|
||||
rune -0 cscli completion bash
|
||||
assert_output --partial "# bash completion for cscli"
|
||||
|
|
213
test/bats/01_cscli_lapi.bats
Normal file
213
test/bats/01_cscli_lapi.bats
Normal file
|
@ -0,0 +1,213 @@
|
|||
#!/usr/bin/env bats
|
||||
# vim: ft=bats:list:ts=8:sts=4:sw=4:et:ai:si:
|
||||
|
||||
set -u
|
||||
|
||||
setup_file() {
|
||||
load "../lib/setup_file.sh"
|
||||
}
|
||||
|
||||
teardown_file() {
|
||||
load "../lib/teardown_file.sh"
|
||||
}
|
||||
|
||||
setup() {
|
||||
load "../lib/setup.sh"
|
||||
load "../lib/bats-file/load.bash"
|
||||
./instance-data load
|
||||
# don't run crowdsec here, not all tests require a running instance
|
||||
}
|
||||
|
||||
teardown() {
|
||||
cd "$TEST_DIR" || exit 1
|
||||
./instance-crowdsec stop
|
||||
}
|
||||
|
||||
#----------
|
||||
|
||||
@test "cscli lapi status" {
|
||||
rune -0 ./instance-crowdsec start
|
||||
rune -0 cscli lapi status
|
||||
|
||||
assert_output --partial "Loaded credentials from"
|
||||
assert_output --partial "Trying to authenticate with username"
|
||||
assert_output --partial "You can successfully interact with Local API (LAPI)"
|
||||
}
|
||||
|
||||
@test "cscli - missing LAPI credentials file" {
|
||||
LOCAL_API_CREDENTIALS=$(config_get '.api.client.credentials_path')
|
||||
rm -f "$LOCAL_API_CREDENTIALS"
|
||||
rune -1 cscli lapi status
|
||||
assert_stderr --partial "loading api client: while reading yaml file: open $LOCAL_API_CREDENTIALS: no such file or directory"
|
||||
|
||||
rune -1 cscli alerts list
|
||||
assert_stderr --partial "loading api client: while reading yaml file: open $LOCAL_API_CREDENTIALS: no such file or directory"
|
||||
|
||||
rune -1 cscli decisions list
|
||||
assert_stderr --partial "loading api client: while reading yaml file: open $LOCAL_API_CREDENTIALS: no such file or directory"
|
||||
}
|
||||
|
||||
@test "cscli - empty LAPI credentials file" {
|
||||
LOCAL_API_CREDENTIALS=$(config_get '.api.client.credentials_path')
|
||||
: > "$LOCAL_API_CREDENTIALS"
|
||||
rune -1 cscli lapi status
|
||||
assert_stderr --partial "no credentials or URL found in api client configuration '$LOCAL_API_CREDENTIALS'"
|
||||
|
||||
rune -1 cscli alerts list
|
||||
assert_stderr --partial "no credentials or URL found in api client configuration '$LOCAL_API_CREDENTIALS'"
|
||||
|
||||
rune -1 cscli decisions list
|
||||
assert_stderr --partial "no credentials or URL found in api client configuration '$LOCAL_API_CREDENTIALS'"
|
||||
}
|
||||
|
||||
@test "cscli - LAPI credentials file can reference env variables" {
|
||||
LOCAL_API_CREDENTIALS=$(config_get '.api.client.credentials_path')
|
||||
URL=$(config_get "$LOCAL_API_CREDENTIALS" '.url')
|
||||
export URL
|
||||
LOGIN=$(config_get "$LOCAL_API_CREDENTIALS" '.login')
|
||||
export LOGIN
|
||||
PASSWORD=$(config_get "$LOCAL_API_CREDENTIALS" '.password')
|
||||
export PASSWORD
|
||||
|
||||
# shellcheck disable=SC2016
|
||||
echo '{"url":"$URL","login":"$LOGIN","password":"$PASSWORD"}' > "$LOCAL_API_CREDENTIALS".local
|
||||
|
||||
config_set '.crowdsec_service.enable=false'
|
||||
rune -0 ./instance-crowdsec start
|
||||
|
||||
rune -0 cscli lapi status
|
||||
assert_output --partial "You can successfully interact with Local API (LAPI)"
|
||||
|
||||
rm "$LOCAL_API_CREDENTIALS".local
|
||||
|
||||
# shellcheck disable=SC2016
|
||||
config_set "$LOCAL_API_CREDENTIALS" '.url="$URL"'
|
||||
# shellcheck disable=SC2016
|
||||
config_set "$LOCAL_API_CREDENTIALS" '.login="$LOGIN"'
|
||||
# shellcheck disable=SC2016
|
||||
config_set "$LOCAL_API_CREDENTIALS" '.password="$PASSWORD"'
|
||||
|
||||
rune -0 cscli lapi status
|
||||
assert_output --partial "You can successfully interact with Local API (LAPI)"
|
||||
|
||||
# but if a variable is not defined, there is no specific error message
|
||||
unset URL
|
||||
rune -1 cscli lapi status
|
||||
# shellcheck disable=SC2016
|
||||
assert_stderr --partial 'BaseURL must have a trailing slash'
|
||||
}
|
||||
|
||||
@test "cscli - missing LAPI client settings" {
|
||||
config_set 'del(.api.client)'
|
||||
rune -1 cscli lapi status
|
||||
assert_stderr --partial "loading api client: no API client section in configuration"
|
||||
|
||||
rune -1 cscli alerts list
|
||||
assert_stderr --partial "loading api client: no API client section in configuration"
|
||||
|
||||
rune -1 cscli decisions list
|
||||
assert_stderr --partial "loading api client: no API client section in configuration"
|
||||
}
|
||||
|
||||
@test "cscli - malformed LAPI url" {
|
||||
LOCAL_API_CREDENTIALS=$(config_get '.api.client.credentials_path')
|
||||
config_set "$LOCAL_API_CREDENTIALS" '.url="http://127.0.0.1:-80"'
|
||||
|
||||
rune -1 cscli lapi status -o json
|
||||
rune -0 jq -r '.msg' <(stderr)
|
||||
assert_output 'failed to authenticate to Local API (LAPI): parse "http://127.0.0.1:-80/": invalid port ":-80" after host'
|
||||
}
|
||||
|
||||
@test "cscli - bad LAPI password" {
|
||||
rune -0 ./instance-crowdsec start
|
||||
LOCAL_API_CREDENTIALS=$(config_get '.api.client.credentials_path')
|
||||
config_set "$LOCAL_API_CREDENTIALS" '.password="meh"'
|
||||
|
||||
rune -1 cscli lapi status -o json
|
||||
rune -0 jq -r '.msg' <(stderr)
|
||||
assert_output 'failed to authenticate to Local API (LAPI): API error: incorrect Username or Password'
|
||||
}
|
||||
|
||||
@test "cscli lapi register / machines validate" {
|
||||
rune -1 cscli lapi register
|
||||
assert_stderr --partial "connection refused"
|
||||
|
||||
LOCAL_API_CREDENTIALS=$(config_get '.api.client.credentials_path')
|
||||
|
||||
rune -0 ./instance-crowdsec start
|
||||
rune -0 cscli lapi register
|
||||
assert_stderr --partial "Successfully registered to Local API"
|
||||
assert_stderr --partial "Local API credentials written to '$LOCAL_API_CREDENTIALS'"
|
||||
assert_stderr --partial "Run 'sudo systemctl reload crowdsec' for the new configuration to be effective."
|
||||
|
||||
LOGIN=$(config_get "$LOCAL_API_CREDENTIALS" '.login')
|
||||
|
||||
rune -0 cscli machines inspect "$LOGIN" -o json
|
||||
rune -0 jq -r '.isValidated' <(output)
|
||||
assert_output "null"
|
||||
|
||||
rune -0 cscli machines validate "$LOGIN"
|
||||
|
||||
rune -0 cscli machines inspect "$LOGIN" -o json
|
||||
rune -0 jq -r '.isValidated' <(output)
|
||||
assert_output "true"
|
||||
}
|
||||
|
||||
@test "cscli lapi register --machine" {
|
||||
rune -0 ./instance-crowdsec start
|
||||
rune -0 cscli lapi register --machine newmachine
|
||||
rune -0 cscli machines validate newmachine
|
||||
rune -0 cscli machines inspect newmachine -o json
|
||||
rune -0 jq -r '.isValidated' <(output)
|
||||
assert_output "true"
|
||||
}
|
||||
|
||||
@test "cscli lapi register --token (ignored)" {
|
||||
# A token is ignored if the server is not configured with it
|
||||
rune -1 cscli lapi register --machine newmachine --token meh
|
||||
assert_stderr --partial "connection refused"
|
||||
|
||||
rune -0 ./instance-crowdsec start
|
||||
rune -1 cscli lapi register --machine newmachine --token meh
|
||||
assert_stderr --partial '422 Unprocessable Entity: API error: http code 422, invalid request:'
|
||||
assert_stderr --partial 'registration_token in body should be at least 32 chars long'
|
||||
|
||||
rune -0 cscli lapi register --machine newmachine --token 12345678901234567890123456789012
|
||||
assert_stderr --partial "Successfully registered to Local API"
|
||||
|
||||
rune -0 cscli machines inspect newmachine -o json
|
||||
rune -0 jq -r '.isValidated' <(output)
|
||||
assert_output "null"
|
||||
}
|
||||
|
||||
@test "cscli lapi register --token" {
|
||||
config_set '.api.server.auto_registration.enabled=true'
|
||||
config_set '.api.server.auto_registration.token="12345678901234567890123456789012"'
|
||||
config_set '.api.server.auto_registration.allowed_ranges=["127.0.0.1/32"]'
|
||||
|
||||
rune -0 ./instance-crowdsec start
|
||||
|
||||
rune -1 cscli lapi register --machine malicious --token 123456789012345678901234badtoken
|
||||
assert_stderr --partial "401 Unauthorized: API error: invalid token for auto registration"
|
||||
rune -1 cscli machines inspect malicious -o json
|
||||
assert_stderr --partial "unable to read machine data 'malicious': user 'malicious': user doesn't exist"
|
||||
|
||||
rune -0 cscli lapi register --machine newmachine --token 12345678901234567890123456789012
|
||||
assert_stderr --partial "Successfully registered to Local API"
|
||||
rune -0 cscli machines inspect newmachine -o json
|
||||
rune -0 jq -r '.isValidated' <(output)
|
||||
assert_output "true"
|
||||
}
|
||||
|
||||
@test "cscli lapi register --token (bad source ip)" {
|
||||
config_set '.api.server.auto_registration.enabled=true'
|
||||
config_set '.api.server.auto_registration.token="12345678901234567890123456789012"'
|
||||
config_set '.api.server.auto_registration.allowed_ranges=["127.0.0.2/32"]'
|
||||
|
||||
rune -0 ./instance-crowdsec start
|
||||
|
||||
rune -1 cscli lapi register --machine outofrange --token 12345678901234567890123456789012
|
||||
assert_stderr --partial "401 Unauthorized: API error: IP not in allowed range for auto registration"
|
||||
rune -1 cscli machines inspect outofrange -o json
|
||||
assert_stderr --partial "unable to read machine data 'outofrange': user 'outofrange': user doesn't exist"
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue