Skip to content

Commit bcdc297

Browse files
feat(settings): Add settings to mock IPs and proxy behavior
1 parent 41a01e7 commit bcdc297

File tree

15 files changed

+380
-98
lines changed

15 files changed

+380
-98
lines changed

.github/workflows/test-suite.yml

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ jobs:
1717
name: Test suite
1818
runs-on: ubuntu-latest
1919
if: ${{ !contains(github.event.head_commit.message, 'chore(') }}
20+
env:
21+
EXTENSION_PATH: "my-own-modules/crowdsec-php-lib"
22+
JP_TEST_IP: "210.249.74.42"
23+
IPV6_TEST_IP: "2001:0db8:0000:85a3:0000:0000:ac1f:8001"
24+
IPV6_TEST_PROXY_IP: "2345:0425:2CA1:0000:0000:0567:5673:23b5"
2025

2126
steps:
2227
- name: Clone DDEV files
@@ -70,18 +75,18 @@ jobs:
7075
path: my-own-modules/crowdsec-php-lib
7176

7277
- name: Validate composer.json
73-
run: ddev composer validate --strict --working-dir ./my-own-modules/crowdsec-php-lib
78+
run: ddev composer validate --strict --working-dir ./${{env.EXTENSION_PATH}}
7479

7580
- name: Install CrowdSec lib dependencies
7681
run: |
77-
ddev composer update --working-dir ./my-own-modules/crowdsec-php-lib
82+
ddev composer update --working-dir ./${{env.EXTENSION_PATH}}
7883
7984
- name: Prepare PHP UNIT tests
8085
run: |
8186
ddev create-watcher PhpUnitTestMachine PhpUnitTestMachinePassword
82-
ddev maxmind-download DEFAULT GeoLite2-City /var/www/html/my-own-modules/crowdsec-php-lib/tests
83-
ddev maxmind-download DEFAULT GeoLite2-Country /var/www/html/my-own-modules/crowdsec-php-lib/tests
84-
cd my-own-modules/crowdsec-php-lib/tests
87+
ddev maxmind-download DEFAULT GeoLite2-City /var/www/html/${{env.EXTENSION_PATH}}/tests
88+
ddev maxmind-download DEFAULT GeoLite2-Country /var/www/html/${{env.EXTENSION_PATH}}/tests
89+
cd ${{env.EXTENSION_PATH}}/tests
8590
sha256sum -c GeoLite2-Country.tar.gz.sha256.txt
8691
sha256sum -c GeoLite2-City.tar.gz.sha256.txt
8792
tar -xf GeoLite2-Country.tar.gz
@@ -90,36 +95,38 @@ jobs:
9095
9196
- name: Run PHP UNIT tests (IP verification)
9297
run: |
93-
ddev exec BOUNCER_KEY=${{ env.BOUNCER_KEY }} LAPI_URL=http://crowdsec:8080 MEMCACHED_DSN=memcached://memcached:11211 REDIS_DSN=redis://redis:6379 /usr/bin/php ./my-own-modules/crowdsec-php-lib/vendor/bin/phpunit --testdox --colors --exclude-group ignore ./my-own-modules/crowdsec-php-lib/tests/Integration/IpVerificationTest.php
98+
ddev exec BOUNCER_KEY=${{ env.BOUNCER_KEY }} LAPI_URL=http://crowdsec:8080 MEMCACHED_DSN=memcached://memcached:11211 REDIS_DSN=redis://redis:6379 /usr/bin/php ./${{env.EXTENSION_PATH}}/vendor/bin/phpunit --testdox --colors --exclude-group ignore ./${{env.EXTENSION_PATH}}/tests/Integration/IpVerificationTest.php
9499
95100
- name: Run PHP UNIT tests (Geolocation)
96101
run: |
97-
ddev exec BOUNCER_KEY=${{ env.BOUNCER_KEY }} LAPI_URL=http://crowdsec:8080 /usr/bin/php ./my-own-modules/crowdsec-php-lib/vendor/bin/phpunit --testdox --colors --exclude-group ignore ./my-own-modules/crowdsec-php-lib/tests/Integration/GeolocationTest.php
102+
ddev exec BOUNCER_KEY=${{ env.BOUNCER_KEY }} LAPI_URL=http://crowdsec:8080 /usr/bin/php ./${{env.EXTENSION_PATH}}/vendor/bin/phpunit --testdox --colors --exclude-group ignore ./${{env.EXTENSION_PATH}}/tests/Integration/GeolocationTest.php
98103
99104
- name: Prepare Standalone Bouncer end-to-end tests
100105
run: |
101106
ddev create-watcher
102107
cd ${{ github.workspace }}/.ddev
103108
ddev nginx-config custom_files/crowdsec-prepend-nginx-site.conf
104109
cd ${{ github.workspace }}
105-
cp .ddev/custom_files/crowdsec/cache-actions.php my-own-modules/crowdsec-php-lib/scripts/public/cache-actions.php
106-
cp .ddev/custom_files/crowdsec/geolocation-test.php my-own-modules/crowdsec-php-lib/scripts/public/geolocation-test.php
107-
cp .ddev/custom_files/crowdsec-lib-settings.php crowdsec-lib-settings.php
110+
cp ${{env.EXTENSION_PATH}}/tests/end-to-end/php-scripts/cache-actions.php.dist ${{env.EXTENSION_PATH}}/scripts/public/cache-actions.php
111+
cp ${{env.EXTENSION_PATH}}/tests/end-to-end/php-scripts/geolocation-test.php.dist ${{env.EXTENSION_PATH}}/scripts/public/geolocation-test.php
112+
cp ${{env.EXTENSION_PATH}}/tests/end-to-end/settings/base.php.dist crowdsec-lib-settings.php
108113
sed -i -e 's/REPLACE_API_KEY/${{ env.BOUNCER_KEY }}/g' crowdsec-lib-settings.php
109114
sed -i -e 's/REPLACE_PROXY_IP/${{ env.PROXY_IP }}/g' crowdsec-lib-settings.php
110-
mv crowdsec-lib-settings.php my-own-modules/crowdsec-php-lib/scripts/auto-prepend/settings.php
111-
cd ${{ github.workspace }}/my-own-modules/crowdsec-php-lib/tests/end-to-end/__scripts__
115+
sed -i -e 's/REPLACE_FORCED_IP//g' crowdsec-lib-settings.php
116+
sed -i -e 's/REPLACE_FORCED_FORWARDED_IP//g' crowdsec-lib-settings.php
117+
mv crowdsec-lib-settings.php ${{env.EXTENSION_PATH}}/scripts/auto-prepend/settings.php
118+
cd ${{ github.workspace }}/${{env.EXTENSION_PATH}}/tests/end-to-end/__scripts__
112119
chmod +x test-init.sh
113120
./test-init.sh
114121
chmod +x run-tests.sh
115122
116123
- name: Verify auto_prepend_file directive
117124
run: |
118125
cd ${{ github.workspace }}
119-
cp .ddev/custom_files/phpinfo.php my-own-modules/crowdsec-php-lib/scripts/public/phpinfo.php
120-
curl -v https://${{ env.PHP_VERSION_CODE }}.ddev.site/my-own-modules/crowdsec-php-lib/scripts/public/phpinfo.php
121-
PREPENDVERIF=$(curl https://${{ env.PHP_VERSION_CODE }}.ddev.site/my-own-modules/crowdsec-php-lib/scripts/public/phpinfo.php | grep -o -E "auto_prepend_file=(.*)php(.*)" | sed 's/<\/tr>//g; s/<\/td>//g;' | tr '\n' '#')
122-
if [[ $PREPENDVERIF == "auto_prepend_file=/var/www/html/my-own-modules/crowdsec-php-lib/scripts/auto-prepend/bounce.php#auto_prepend_file=/var/www/html/my-own-modules/crowdsec-php-lib/scripts/auto-prepend/bounce.php#" ]]
126+
cp .ddev/custom_files/phpinfo.php ${{env.EXTENSION_PATH}}/scripts/public/phpinfo.php
127+
curl -v https://${{ env.PHP_VERSION_CODE }}.ddev.site/${{env.EXTENSION_PATH}}/scripts/public/phpinfo.php
128+
PREPENDVERIF=$(curl https://${{ env.PHP_VERSION_CODE }}.ddev.site/${{env.EXTENSION_PATH}}/scripts/public/phpinfo.php | grep -o -E "auto_prepend_file=(.*)php(.*)" | sed 's/<\/tr>//g; s/<\/td>//g;' | tr '\n' '#')
129+
if [[ $PREPENDVERIF == "auto_prepend_file=/var/www/html/${{env.EXTENSION_PATH}}/scripts/auto-prepend/bounce.php#auto_prepend_file=/var/www/html/my-own-modules/crowdsec-php-lib/scripts/auto-prepend/bounce.php#" ]]
123130
then
124131
echo "AUTO PREPEND FILE OK"
125132
else
@@ -130,27 +137,42 @@ jobs:
130137
131138
- name: Run Standalone Bouncer end-to-end test (live mode without geolocation)
132139
run: |
133-
cd ${{ github.workspace }}/my-own-modules/crowdsec-php-lib/tests/end-to-end/__scripts__
140+
cd ${{ github.workspace }}/${{env.EXTENSION_PATH}}
141+
cat scripts/auto-prepend/settings.php
142+
cd ${{ github.workspace }}/${{env.EXTENSION_PATH}}/tests/end-to-end/__scripts__
134143
./run-tests.sh ci "./__tests__/1-live-mode.js"
135144
136145
- name: Run Standalone Bouncer end-to-end test (live mode with geolocation)
137146
run: |
138-
cd ${{ github.workspace }}/my-own-modules/crowdsec-php-lib
147+
cd ${{ github.workspace }}/${{env.EXTENSION_PATH}}
139148
sed -i 's/\x27enabled\x27 => false/\x27enabled\x27 => true/g' scripts/auto-prepend/settings.php
140-
sed -i 's/\x27forced_test_ip\x27 => \x27\x27/\x27forced_test_ip\x27 => \x27210.249.74.42\x27/g' scripts/auto-prepend/settings.php
141-
cd ${{ github.workspace }}/my-own-modules/crowdsec-php-lib/tests/end-to-end/__scripts__
149+
sed -i 's/\x27forced_test_forwarded_ip\x27 => \x27\x27/\x27forced_test_forwarded_ip\x27 => \x27${{env.JP_TEST_IP}}\x27/g' scripts/auto-prepend/settings.php
150+
cat scripts/auto-prepend/settings.php
151+
cd ${{ github.workspace }}/${{env.EXTENSION_PATH}}/tests/end-to-end/__scripts__
142152
./run-tests.sh ci "./__tests__/2-live-mode-with-geolocation.js"
143153
144154
- name: Run Standalone Bouncer end-to-end test (stream mode without geolocation)
145155
run: |
146-
cd ${{ github.workspace }}/my-own-modules/crowdsec-php-lib
156+
cd ${{ github.workspace }}/${{env.EXTENSION_PATH}}
147157
sed -i 's/\x27enabled\x27 => true/\x27enabled\x27 => false/g' scripts/auto-prepend/settings.php
148-
sed -i 's/\x27forced_test_ip\x27 => \x27210.249.74.42\x27/\x27forced_test_ip\x27 => \x27\x27/g' scripts/auto-prepend/settings.php
158+
sed -i 's/\x27forced_test_forwarded_ip\x27 => \x27${{env.JP_TEST_IP}}\x27/\x27forced_test_forwarded_ip\x27 => \x27\x27/g' scripts/auto-prepend/settings.php
149159
sed -i 's/\x27stream_mode\x27 => false/\x27stream_mode\x27 => true/g' scripts/auto-prepend/settings.php
150-
cd ${{ github.workspace }}/my-own-modules/crowdsec-php-lib/tests/end-to-end/__scripts__
160+
cat scripts/auto-prepend/settings.php
161+
cd ${{ github.workspace }}/${{env.EXTENSION_PATH}}/tests/end-to-end/__scripts__
151162
./run-tests.sh ci "./__tests__/3-stream-mode.js"
152163
153164
- name: Run Standalone Bouncer end-to-end test (standalone geolocation)
154165
run: |
155-
cd ${{ github.workspace }}/my-own-modules/crowdsec-php-lib/tests/end-to-end/__scripts__
156-
./run-tests.sh ci "./__tests__/4-geolocation.js"
166+
cd ${{ github.workspace }}/${{env.EXTENSION_PATH}}/tests/end-to-end/__scripts__
167+
./run-tests.sh ci "./__tests__/4-geolocation.js"
168+
169+
- name: Run Standalone Bouncer end-to-end test (live mode with IPv6)
170+
run: |
171+
cd ${{ github.workspace }}/${{env.EXTENSION_PATH}}
172+
sed -i 's/\x27forced_test_forwarded_ip\x27 => \x27\x27/\x27forced_test_forwarded_ip\x27 => \x27${{env.IPV6_TEST_IP}}\x27/g' scripts/auto-prepend/settings.php
173+
sed -i 's/\x27forced_test_ip\x27 => \x27\x27/\x27forced_test_ip\x27 => \x27${{env.IPV6_TEST_PROXY_IP}}\x27/g' scripts/auto-prepend/settings.php
174+
sed -i -e 's/${{ env.PROXY_IP }}/${{env.IPV6_TEST_PROXY_IP}}/g' scripts/auto-prepend/settings.php
175+
sed -i 's/\x27stream_mode\x27 => true/\x27stream_mode\x27 => false/g' scripts/auto-prepend/settings.php
176+
cat scripts/auto-prepend/settings.php
177+
cd ${{ github.workspace }}/${{env.EXTENSION_PATH}}/tests/end-to-end/__scripts__
178+
./run-tests.sh ci "./__tests__/1-live-mode.js"

docs/USER_GUIDE.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,14 @@ Here is the list of available settings:
134134

135135
- `display_errors`: true to stop the process and display errors on browser if any.
136136

137-
- `forced_test_ip`: Only for test or debug purpose. Default to empty. If not empty, it will be used for all remediation and geolocation processes.
137+
- `forced_test_ip`: Only for test or debug purpose. Default to empty. If not empty, it will be used instead of the
138+
real remote ip.
139+
140+
- `forced_test_forwarded_ip`: Only for test or debug purpose. Default to empty. If not empty, it will be used instead of the real forwarded ip.
141+
142+
- `forced_test_never_use_forwarded`: Only for test or debug purpose. Default to false. Set to true if you never
143+
want to use the x-forwarded-for mechanism.
144+
138145
##### Bouncer behavior
139146

140147
- `bouncing_level`: Select from `bouncing_disabled`, `normal_bouncing` or `flex_bouncing`. Choose if you want to apply CrowdSec directives (Normal bouncing) or be more permissive (Flex bouncing). With the `Flex mode`, it is impossible to accidentally block access to your site to people who don’t deserve it. This mode makes it possible to never ban an IP but only to offer a Captcha, in the worst-case scenario.

scripts/auto-prepend/settings.example.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,22 @@
3535

3636
/** Only for test or debug purpose. Default to empty.
3737
*
38-
* If not empty, it will be used for all remediation and geolocation processes.
38+
* If not empty, it will be used instead of the real remote ip.
3939
*/
4040
'forced_test_ip' => '',
4141

42+
/** Only for test or debug purpose. Default to empty.
43+
*
44+
* If not empty, it will be used instead of the real forwarded ip.
45+
*/
46+
'forced_test_forwarded_ip' => '',
47+
48+
/** Only for test or debug purpose. Default to false.
49+
*
50+
* Set to true if you never want to use the x-forwarded-for mechanism.
51+
*/
52+
'forced_test_never_use_forwarded' => false,
53+
4254
/** Select from 'bouncing_disabled', 'normal_bouncing' or 'flex_bouncing'.
4355
*
4456
* Choose if you want to apply CrowdSec directives (Normal bouncing) or be more permissive (Flex bouncing).

src/AbstractBounce.php

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ abstract class AbstractBounce implements IBounce
4444

4545
protected function getIntegerSettings(string $name): int
4646
{
47-
return !empty($this->settings[$name]) ? (int) $this->settings[$name] : 0;
47+
return !empty($this->settings[$name]) ? (int)$this->settings[$name] : 0;
4848
}
4949

5050
protected function getBoolSettings(string $name): bool
@@ -54,12 +54,12 @@ protected function getBoolSettings(string $name): bool
5454

5555
protected function getStringSettings(string $name): string
5656
{
57-
return !empty($this->settings[$name]) ? (string) $this->settings[$name] : '';
57+
return !empty($this->settings[$name]) ? (string)$this->settings[$name] : '';
5858
}
5959

6060
protected function getArraySettings(string $name): array
6161
{
62-
return !empty($this->settings[$name]) ? (array) $this->settings[$name] : [];
62+
return !empty($this->settings[$name]) ? (array)$this->settings[$name] : [];
6363
}
6464

6565
/**
@@ -111,35 +111,56 @@ protected function initLoggerHelper(string $logDirectoryPath, string $loggerName
111111
}
112112
}
113113

114+
/**
115+
* Decide if we use forward (default behavior) or if it depends on test settings
116+
*
117+
* @param $settings
118+
* @return bool
119+
*/
120+
protected function shouldUseForward($settings)
121+
{
122+
return empty($settings['forced_test_never_use_forwarded']);
123+
}
124+
114125
/**
115126
* @throws Exception|InvalidArgumentException
116127
*/
117128
protected function bounceCurrentIp(): void
118129
{
119-
$ip = $this->getRemoteIp();
120-
// X-Forwarded-For override
121-
$XForwardedForHeader = $this->getHttpRequestHeader('X-Forwarded-For');
122-
if (null !== $XForwardedForHeader) {
123-
$ipList = array_map('trim', array_values(array_filter(explode(',', $XForwardedForHeader))));
124-
$forwardedIp = end($ipList);
125-
if ($this->shouldTrustXforwardedFor($ip)) {
126-
$ip = $forwardedIp;
127-
} else {
128-
$this->logger->warning('', [
129-
'type' => 'NON_AUTHORIZED_X_FORWARDED_FOR_USAGE',
130-
'original_ip' => $ip,
131-
'x_forwarded_for_ip' => $forwardedIp,
132-
]);
133-
}
134-
}
135-
136130
try {
137131
if (!$this->bouncer) {
138132
throw new BouncerException('Bouncer must be instantiated to bounce an IP.');
139133
}
140-
$ipToCheck = !empty($this->settings['forced_test_ip']) ? $this->settings['forced_test_ip'] : $ip;
141-
$remediation = $this->bouncer->getRemediationForIp($ipToCheck);
142-
$this->handleRemediation($remediation, $ipToCheck);
134+
// Retrieve the current IP (even if it is a proxy IP) or a testing IP
135+
$ip = !empty($this->settings['forced_test_ip']) ? $this->settings['forced_test_ip'] : $this->getRemoteIp();
136+
if ($this->shouldUseForward($this->settings)) {
137+
// Retrieve the forwarded IP (testing one or real)
138+
if (!empty($this->settings['forced_test_forwarded_ip'])) {
139+
$forwardedIp = $this->settings['forced_test_forwarded_ip'];
140+
} elseif ($XForwardedForHeader = $this->getHttpRequestHeader('X-Forwarded-For')) {
141+
$ipList = array_map('trim', array_values(array_filter(explode(',', $XForwardedForHeader))));
142+
$forwardedIp = end($ipList);
143+
}
144+
if (isset($forwardedIp)) {
145+
if ($this->shouldTrustXforwardedFor($ip)) {
146+
$this->logger->debug('', [
147+
'type' => 'AUTHORIZED_X_FORWARDED_FOR_USAGE',
148+
'original_ip' => $ip,
149+
]);
150+
$ip = $forwardedIp;
151+
} else {
152+
$this->logger->warning('', [
153+
'type' => 'NON_AUTHORIZED_X_FORWARDED_FOR_USAGE',
154+
'original_ip' => $ip,
155+
'x_forwarded_for_ip' => $forwardedIp ?? 'undefined',
156+
]);
157+
}
158+
} else {
159+
$this->logger->debug('', ['type' => 'X_FORWARDED_FOR_NOT_FOUND']);
160+
}
161+
}
162+
$remediation = $this->bouncer->getRemediationForIp($ip);
163+
$this->handleRemediation($remediation, $ip);
143164
} catch (Exception $e) {
144165
$this->logger->warning('', [
145166
'type' => 'UNKNOWN_EXCEPTION_WHILE_BOUNCING',
@@ -185,8 +206,8 @@ protected function displayCaptchaWall(string $ip): void
185206
$ip
186207
);
187208
$body = Bouncer::getCaptchaHtmlTemplate(
188-
(bool) $captchaVariables['crowdsec_captcha_resolution_failed'],
189-
(string) $captchaVariables['crowdsec_captcha_inline_image'],
209+
(bool)$captchaVariables['crowdsec_captcha_resolution_failed'],
210+
(string)$captchaVariables['crowdsec_captcha_inline_image'],
190211
'',
191212
$options
192213
);
@@ -225,7 +246,7 @@ protected function handleCaptchaResolutionForm(string $ip)
225246
}
226247

227248
// Handle image refresh.
228-
if (null !== $this->getPostedVariable('refresh') && (int) $this->getPostedVariable('refresh')) {
249+
if (null !== $this->getPostedVariable('refresh') && (int)$this->getPostedVariable('refresh')) {
229250
// Generate new captcha image for the user
230251
$captchaCouple = Bouncer::buildCaptchaCouple();
231252
$captchaVariables = [
@@ -248,7 +269,7 @@ protected function handleCaptchaResolutionForm(string $ip)
248269
}
249270
if (
250271
$this->bouncer->checkCaptcha(
251-
(string) $cachedCaptchaVariables['crowdsec_captcha_phrase_to_guess'],
272+
(string)$cachedCaptchaVariables['crowdsec_captcha_phrase_to_guess'],
252273
$this->getPostedVariable('phrase'),
253274
$ip
254275
)
@@ -264,7 +285,7 @@ protected function handleCaptchaResolutionForm(string $ip)
264285
'crowdsec_captcha_inline_image',
265286
'crowdsec_captcha_resolution_failed',
266287
'crowdsec_captcha_resolution_redirect',
267-
];
288+
];
268289
$this->unsetIpVariables(Constants::CACHE_TAG_CAPTCHA, $unsetVariables, $ip);
269290
$redirect = $cachedCaptchaVariables['crowdsec_captcha_resolution_redirect'] ?? '/';
270291
header("Location: $redirect");
@@ -306,7 +327,7 @@ protected function handleCaptchaRemediation(string $ip)
306327
'crowdsec_captcha_resolution_failed' => false,
307328
'crowdsec_captcha_resolution_redirect' => 'POST' === $this->getHttpMethod() &&
308329
!empty($_SERVER['HTTP_REFERER'])
309-
? $_SERVER['HTTP_REFERER'] : '/',
330+
? $_SERVER['HTTP_REFERER'] : '/',
310331
];
311332
$this->setIpVariables(Constants::CACHE_TAG_CAPTCHA, $captchaVariables, $ip);
312333
}

src/Configuration.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public function getConfigTreeBuilder(): TreeBuilder
3535
->integerNode('api_timeout')->min(Constants::API_TIMEOUT)->defaultValue(Constants::API_TIMEOUT)->end()
3636
// Debug
3737
->scalarNode('forced_test_ip')->defaultValue('')->end()
38+
->scalarNode('forced_test_forwarded_ip')->defaultValue('')->end()
39+
->booleanNode('forced_test_never_use_forwarded')->defaultValue(false)->end()
3840
->booleanNode('debug_mode')->defaultValue(false)->end()
3941
->scalarNode('log_directory_path')->end()
4042
->booleanNode('display_errors')->defaultValue(false)->end()

0 commit comments

Comments
 (0)