Add use_forwarded_for_headers configuration option for LAPI (#610)
* Add use_forwarded_for_headers configuration option for LAPI * update documentation
This commit is contained in:
parent
9f515cb7ef
commit
260332c726
|
@ -50,6 +50,7 @@ api:
|
||||||
log_level: info
|
log_level: info
|
||||||
listen_uri: 127.0.0.1:8080
|
listen_uri: 127.0.0.1:8080
|
||||||
profiles_path: /etc/crowdsec/profiles.yaml
|
profiles_path: /etc/crowdsec/profiles.yaml
|
||||||
|
use_forwarded_for_headers: false
|
||||||
online_client: # Crowdsec API
|
online_client: # Crowdsec API
|
||||||
credentials_path: /etc/crowdsec/online_api_credentials.yaml
|
credentials_path: /etc/crowdsec/online_api_credentials.yaml
|
||||||
# tls:
|
# tls:
|
||||||
|
@ -132,6 +133,7 @@ api:
|
||||||
log_level: (error|info|debug|trace>)
|
log_level: (error|info|debug|trace>)
|
||||||
listen_uri: <listen_uri> # host:port
|
listen_uri: <listen_uri> # host:port
|
||||||
profiles_path: <path_to_profile_file>
|
profiles_path: <path_to_profile_file>
|
||||||
|
use_forwarded_for_headers: <true|false>
|
||||||
online_client:
|
online_client:
|
||||||
credentials_path: <path_to_crowdsec_api_client_credential_file>
|
credentials_path: <path_to_crowdsec_api_client_credential_file>
|
||||||
tls:
|
tls:
|
||||||
|
@ -304,6 +306,7 @@ api:
|
||||||
log_level: (error|info|debug|trace>)
|
log_level: (error|info|debug|trace>)
|
||||||
listen_uri: <listen_uri> # host:port
|
listen_uri: <listen_uri> # host:port
|
||||||
profiles_path: <path_to_profile_file>
|
profiles_path: <path_to_profile_file>
|
||||||
|
use_forwarded_for_headers: (true|false)
|
||||||
online_client:
|
online_client:
|
||||||
credentials_path: <path_to_crowdsec_api_client_credential_file>
|
credentials_path: <path_to_crowdsec_api_client_credential_file>
|
||||||
tls:
|
tls:
|
||||||
|
@ -340,6 +343,7 @@ server:
|
||||||
log_level: (error|info|debug|trace)
|
log_level: (error|info|debug|trace)
|
||||||
listen_uri: <listen_uri> # host:port
|
listen_uri: <listen_uri> # host:port
|
||||||
profiles_path: <path_to_profile_file>
|
profiles_path: <path_to_profile_file>
|
||||||
|
use_forwarded_for_headers: (true|false)
|
||||||
online_client:
|
online_client:
|
||||||
credentials_path: <path_to_crowdsec_api_client_credential_file>
|
credentials_path: <path_to_crowdsec_api_client_credential_file>
|
||||||
tls:
|
tls:
|
||||||
|
@ -357,6 +361,11 @@ Address and port listen configuration, the form `host:port`.
|
||||||
|
|
||||||
The path to the {{v1X.profiles.htmlname}} configuration.
|
The path to the {{v1X.profiles.htmlname}} configuration.
|
||||||
|
|
||||||
|
#### `use_forwarded_for_headers`
|
||||||
|
> string
|
||||||
|
|
||||||
|
Allow the usage of `X-Forwarded-For` or `X-Real-IP` to get the client IP address. Do not enable if you are not running the LAPI behind a trusted reverse-proxy or LB.
|
||||||
|
|
||||||
#### `online_client`
|
#### `online_client`
|
||||||
|
|
||||||
Configuration to push signals and receive bad IPs from Crowdsec API.
|
Configuration to push signals and receive bad IPs from Crowdsec API.
|
||||||
|
|
|
@ -61,10 +61,13 @@ func NewServer(config *csconfig.LocalApiServerCfg) (*APIServer, error) {
|
||||||
}
|
}
|
||||||
log.Debugf("starting router, logging to %s", logFile)
|
log.Debugf("starting router, logging to %s", logFile)
|
||||||
router := gin.New()
|
router := gin.New()
|
||||||
/*related to https://github.com/gin-gonic/gin/pull/2474
|
/* See https://github.com/gin-gonic/gin/pull/2474:
|
||||||
Gin team doesn't seem to be willing to have a opt-in/opt-out on the trusted proxies.
|
Gin does not handle safely X-Forwarded-For or X-Real-IP.
|
||||||
For now, let's not trust that. */
|
We do not trust them by default, but the user can opt-in
|
||||||
router.ForwardedByClientIP = false
|
if they host LAPI behind a trusted proxy which sanitize
|
||||||
|
X-Forwarded-For and X-Real-IP.
|
||||||
|
*/
|
||||||
|
router.ForwardedByClientIP = config.UseForwardedForHeaders
|
||||||
|
|
||||||
/*The logger that will be used by handlers*/
|
/*The logger that will be used by handlers*/
|
||||||
clog := log.New()
|
clog := log.New()
|
||||||
|
|
|
@ -57,6 +57,33 @@ func LoadTestConfig() csconfig.GlobalConfig {
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func LoadTestConfigForwardedFor() csconfig.GlobalConfig {
|
||||||
|
config := csconfig.GlobalConfig{}
|
||||||
|
maxAge := "1h"
|
||||||
|
flushConfig := csconfig.FlushDBCfg{
|
||||||
|
MaxAge: &maxAge,
|
||||||
|
}
|
||||||
|
dbconfig := csconfig.DatabaseCfg{
|
||||||
|
Type: "sqlite",
|
||||||
|
DbPath: "./ent",
|
||||||
|
Flush: &flushConfig,
|
||||||
|
}
|
||||||
|
apiServerConfig := csconfig.LocalApiServerCfg{
|
||||||
|
ListenURI: "http://127.0.0.1:8080",
|
||||||
|
DbConfig: &dbconfig,
|
||||||
|
ProfilesPath: "./tests/profiles.yaml",
|
||||||
|
UseForwardedForHeaders: true,
|
||||||
|
}
|
||||||
|
apiConfig := csconfig.APICfg{
|
||||||
|
Server: &apiServerConfig,
|
||||||
|
}
|
||||||
|
config.API = &apiConfig
|
||||||
|
if err := config.API.Server.LoadProfiles(); err != nil {
|
||||||
|
log.Fatalf("failed to load profiles: %s", err)
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
func NewAPITest() (*gin.Engine, error) {
|
func NewAPITest() (*gin.Engine, error) {
|
||||||
config := LoadTestConfig()
|
config := LoadTestConfig()
|
||||||
|
|
||||||
|
@ -74,6 +101,23 @@ func NewAPITest() (*gin.Engine, error) {
|
||||||
return router, nil
|
return router, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewAPITestForwardedFor() (*gin.Engine, error) {
|
||||||
|
config := LoadTestConfigForwardedFor()
|
||||||
|
|
||||||
|
os.Remove("./ent")
|
||||||
|
apiServer, err := NewServer(config.API.Server)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to run local API: %s", err)
|
||||||
|
}
|
||||||
|
log.Printf("Creating new API server")
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
router, err := apiServer.Router()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to run local API: %s", err)
|
||||||
|
}
|
||||||
|
return router, nil
|
||||||
|
}
|
||||||
|
|
||||||
func ValidateMachine(machineID string) error {
|
func ValidateMachine(machineID string) error {
|
||||||
config := LoadTestConfig()
|
config := LoadTestConfig()
|
||||||
dbClient, err := database.NewClient(config.API.Server.DbConfig)
|
dbClient, err := database.NewClient(config.API.Server.DbConfig)
|
||||||
|
@ -86,6 +130,24 @@ func ValidateMachine(machineID string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetMachineIP(machineID string) (string, error) {
|
||||||
|
config := LoadTestConfig()
|
||||||
|
dbClient, err := database.NewClient(config.API.Server.DbConfig)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("unable to create new database client: %s", err)
|
||||||
|
}
|
||||||
|
machines, err := dbClient.ListMachines()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Unable to list machines: %s", err)
|
||||||
|
}
|
||||||
|
for _, machine := range machines {
|
||||||
|
if machine.MachineId == machineID {
|
||||||
|
return machine.IpAddress, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
func CreateTestMachine(router *gin.Engine) (string, error) {
|
func CreateTestMachine(router *gin.Engine) (string, error) {
|
||||||
b, err := json.Marshal(MachineTest)
|
b, err := json.Marshal(MachineTest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -52,6 +52,96 @@ func TestCreateMachine(t *testing.T) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreateMachineWithForwardedFor(t *testing.T) {
|
||||||
|
router, err := NewAPITestForwardedFor()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to run local API: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create machine
|
||||||
|
b, err := json.Marshal(MachineTest)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to marshal MachineTest")
|
||||||
|
}
|
||||||
|
body := string(b)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest("POST", "/v1/watchers", strings.NewReader(body))
|
||||||
|
req.Header.Add("User-Agent", UserAgent)
|
||||||
|
req.Header.Add("X-Real-IP", "1.1.1.1")
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, 201, w.Code)
|
||||||
|
assert.Equal(t, "", w.Body.String())
|
||||||
|
|
||||||
|
ip, err := GetMachineIP(*MachineTest.MachineID)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Could not get machine IP : %s", err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, "1.1.1.1", ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateMachineWithForwardedForNoConfig(t *testing.T) {
|
||||||
|
router, err := NewAPITest()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to run local API: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create machine
|
||||||
|
b, err := json.Marshal(MachineTest)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to marshal MachineTest")
|
||||||
|
}
|
||||||
|
body := string(b)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest("POST", "/v1/watchers", strings.NewReader(body))
|
||||||
|
req.Header.Add("User-Agent", UserAgent)
|
||||||
|
req.Header.Add("X-Real-IP", "1.1.1.1")
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, 201, w.Code)
|
||||||
|
assert.Equal(t, "", w.Body.String())
|
||||||
|
|
||||||
|
ip, err := GetMachineIP(*MachineTest.MachineID)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Could not get machine IP : %s", err)
|
||||||
|
}
|
||||||
|
//For some reason, the IP is empty when running tests
|
||||||
|
//if no forwarded-for headers are present
|
||||||
|
assert.Equal(t, "", ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateMachineWithoutForwardedFor(t *testing.T) {
|
||||||
|
router, err := NewAPITestForwardedFor()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to run local API: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create machine
|
||||||
|
b, err := json.Marshal(MachineTest)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to marshal MachineTest")
|
||||||
|
}
|
||||||
|
body := string(b)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest("POST", "/v1/watchers", strings.NewReader(body))
|
||||||
|
req.Header.Add("User-Agent", UserAgent)
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, 201, w.Code)
|
||||||
|
assert.Equal(t, "", w.Body.String())
|
||||||
|
|
||||||
|
ip, err := GetMachineIP(*MachineTest.MachineID)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Could not get machine IP : %s", err)
|
||||||
|
}
|
||||||
|
//For some reason, the IP is empty when running tests
|
||||||
|
//if no forwarded-for headers are present
|
||||||
|
assert.Equal(t, "", ip)
|
||||||
|
}
|
||||||
|
|
||||||
func TestCreateMachineAlreadyExist(t *testing.T) {
|
func TestCreateMachineAlreadyExist(t *testing.T) {
|
||||||
router, err := NewAPITest()
|
router, err := NewAPITest()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -28,14 +28,15 @@ type LocalApiClientCfg struct {
|
||||||
|
|
||||||
/*local api service configuration*/
|
/*local api service configuration*/
|
||||||
type LocalApiServerCfg struct {
|
type LocalApiServerCfg struct {
|
||||||
ListenURI string `yaml:"listen_uri,omitempty"` //127.0.0.1:8080
|
ListenURI string `yaml:"listen_uri,omitempty"` //127.0.0.1:8080
|
||||||
TLS *TLSCfg `yaml:"tls"`
|
TLS *TLSCfg `yaml:"tls"`
|
||||||
DbConfig *DatabaseCfg `yaml:"-"`
|
DbConfig *DatabaseCfg `yaml:"-"`
|
||||||
LogDir string `yaml:"-"`
|
LogDir string `yaml:"-"`
|
||||||
OnlineClient *OnlineApiClientCfg `yaml:"online_client"`
|
OnlineClient *OnlineApiClientCfg `yaml:"online_client"`
|
||||||
ProfilesPath string `yaml:"profiles_path,omitempty"`
|
ProfilesPath string `yaml:"profiles_path,omitempty"`
|
||||||
Profiles []*ProfileCfg `yaml:"-"`
|
Profiles []*ProfileCfg `yaml:"-"`
|
||||||
LogLevel *log.Level `yaml:"log_level"`
|
LogLevel *log.Level `yaml:"log_level"`
|
||||||
|
UseForwardedForHeaders bool `yaml:"use_forwarded_for_headers,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TLSCfg struct {
|
type TLSCfg struct {
|
||||||
|
|
|
@ -232,7 +232,8 @@ func NewDefaultConfig() *GlobalConfig {
|
||||||
CredentialsFilePath: "/etc/crowdsec/config/lapi-secrets.yaml",
|
CredentialsFilePath: "/etc/crowdsec/config/lapi-secrets.yaml",
|
||||||
},
|
},
|
||||||
Server: &LocalApiServerCfg{
|
Server: &LocalApiServerCfg{
|
||||||
ListenURI: "127.0.0.1:8080",
|
ListenURI: "127.0.0.1:8080",
|
||||||
|
UseForwardedForHeaders: false,
|
||||||
OnlineClient: &OnlineApiClientCfg{
|
OnlineClient: &OnlineApiClientCfg{
|
||||||
CredentialsFilePath: "/etc/crowdsec/config/online-api-secrets.yaml",
|
CredentialsFilePath: "/etc/crowdsec/config/online-api-secrets.yaml",
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue