Skip to content

Commit b9dd4c2

Browse files
Merge pull request NoiseByNorthwest#261 from NoiseByNorthwest/fix_259
Add subnet mask support for IP matching
2 parents a467ef6 + 11017e0 commit b9dd4c2

File tree

8 files changed

+181
-9
lines changed

8 files changed

+181
-9
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,8 +260,8 @@ while ($task = get_next_ready_task()) {
260260
| _spx.http_enabled_ | `0` | _PHP_INI_SYSTEM_ | Whether to enable web UI and HTTP request profiling. |
261261
| _spx.http_key_ | | _PHP_INI_SYSTEM_ | The secret key used for authentication (see [security concern](#security-concern) for more details). You can use the following command to generate a 16 bytes random key as an hex string: `openssl rand -hex 16`. |
262262
| _spx.http_ip_var_ | `REMOTE_ADDR` | _PHP_INI_SYSTEM_ | The `$_SERVER` key holding the client IP address used for authentication (see [security concern](#security-concern) for more details). Overriding the default value is required when your application is behind a reverse proxy. |
263-
| _spx.http_trusted_proxies_ | `127.0.0.1` | _PHP_INI_SYSTEM_ | The trusted proxy list as a comma separated list of IP addresses. This setting is ignored when `spx.http_ip_var`'s value is `REMOTE_ADDR`. |
264-
| _spx.http_ip_whitelist_ | | _PHP_INI_SYSTEM_ | The IP address white list used for authentication as a comma separated list of IP addresses, use `*` to allow all IP addresses. |
263+
| _spx.http_trusted_proxies_ | `127.0.0.1` | _PHP_INI_SYSTEM_ | The trusted proxy list as a comma separated list of IP addresses<b>*</b>. This setting is ignored when `spx.http_ip_var`'s value is `REMOTE_ADDR`. |
264+
| _spx.http_ip_whitelist_ | | _PHP_INI_SYSTEM_ | The IP address white list used for authentication as a comma separated list of IP addresses<b>*</b>. |
265265
| _spx.http_ui_assets_dir_ | `/usr/local/share/misc/php-spx/assets/web-ui` | _PHP_INI_SYSTEM_ | The directory where the [web UI](#web-ui) files are installed. In most cases you do not have to change it. |
266266
| _spx.http_profiling_enabled_ | _NULL_ | _PHP_INI_SYSTEM_ | The INI level counterpart of the `SPX_ENABLED` parameter, for HTTP requests only. See [here for more details](#available-parameters). |
267267
| _spx.http_profiling_auto_start_ | _NULL_ | _PHP_INI_SYSTEM_ | The INI level counterpart of the `SPX_AUTO_START` parameter, for HTTP requests only. See [here for more details](#available-parameters). |
@@ -270,6 +270,7 @@ while ($task = get_next_ready_task()) {
270270
| _spx.http_profiling_depth_ | _NULL_ | _PHP_INI_SYSTEM_ | The INI level counterpart of the `SPX_DEPTH` parameter, for HTTP requests only. See [here for more details](#available-parameters). |
271271
| _spx.http_profiling_metrics_ | _NULL_ | _PHP_INI_SYSTEM_ | The INI level counterpart of the `SPX_METRICS` parameter, for HTTP requests only. See [here for more details](#available-parameters). |
272272

273+
_\*: `*` (match all) and subnet masks (e.g. `192.168.1.0/24`) are supported._
273274

274275
#### Private environment
275276

src/php_spx.c

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,7 @@ static int check_access(void)
552552
int found = 0;
553553

554554
SPX_UTILS_TOKENIZE_STRING(SPX_G(http_trusted_proxies), ',', trusted_proxy_ip_str, 64, {
555-
if (0 == strcmp(proxy_ip_str, trusted_proxy_ip_str)) {
555+
if (spx_utils_ip_match(proxy_ip_str, trusted_proxy_ip_str)) {
556556
found = 1;
557557
}
558558
});
@@ -586,18 +586,13 @@ static int check_access(void)
586586
}
587587

588588
SPX_UTILS_TOKENIZE_STRING(authorized_ips_str, ',', authorized_ip_str, 64, {
589-
if (0 == strcmp(ip_str, authorized_ip_str)) {
589+
if (spx_utils_ip_match(ip_str, authorized_ip_str)) {
590590
/* ip authorized (OK, as well as all previous checks) -> granted */
591591

592592
return 1;
593593
}
594594
});
595595

596-
if (0 == strcmp(authorized_ips_str, "*")) {
597-
/* all ips authorized */
598-
return 1;
599-
}
600-
601596
spx_php_log_notice(
602597
"access not granted: \"%s\" IP is not in white list (\"%s\")",
603598
ip_str,

src/spx_utils.c

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,67 @@
2424
# include <pthread.h>
2525
#endif
2626

27+
#include <arpa/inet.h>
28+
2729
#include "spx_utils.h"
30+
#include "spx_php.h"
31+
32+
int spx_utils_ip_match(const char * ip_address_str, const char * target)
33+
{
34+
if (
35+
strcmp(target, "*") == 0 ||
36+
strcmp(target, ip_address_str) == 0
37+
) {
38+
return 1;
39+
}
40+
41+
// subnet handling
42+
43+
const char * slash_ptr = strchr(target, '/');
44+
if (slash_ptr == NULL) {
45+
return 0;
46+
}
47+
48+
const size_t slash_pos = slash_ptr - target;
49+
if (! (7 <= slash_pos && slash_pos <= 15)) {
50+
return 0;
51+
}
52+
53+
const size_t target_suffix_len = strlen(slash_ptr);
54+
if (! (2 <= target_suffix_len && target_suffix_len <= 3)) {
55+
return 0;
56+
}
57+
58+
char target_ip_address_str[32];
59+
strncpy(target_ip_address_str, target, sizeof target_ip_address_str);
60+
target_ip_address_str[slash_pos] = 0;
61+
62+
const in_addr_t target_ip_address = inet_addr(target_ip_address_str);
63+
if (target_ip_address == INADDR_NONE) {
64+
return 0;
65+
}
66+
67+
char target_mask_str[32];
68+
snprintf(target_mask_str, sizeof target_mask_str, "%s", slash_ptr + 1);
69+
const long target_mask_bits = strtol(target_mask_str, NULL, 10);
70+
71+
if (! (1 <= target_mask_bits && target_mask_bits <= 31)) {
72+
return 0;
73+
}
74+
75+
const in_addr_t target_mask = (~0) << (32 - target_mask_bits);
76+
77+
const in_addr_t ip_address = inet_addr(ip_address_str);
78+
if (ip_address == INADDR_NONE) {
79+
return 0;
80+
}
81+
82+
if ((ntohl(ip_address) & target_mask) == (ntohl(target_ip_address) & target_mask)) {
83+
return 1;
84+
}
85+
86+
return 0;
87+
}
2888

2989
char * spx_utils_resolve_confined_file_absolute_path(
3090
const char * root_dir,

src/spx_utils.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ do { \
5050
} \
5151
} while (0)
5252

53+
int spx_utils_ip_match(const char * ip_address, const char * target);
54+
5355
char * spx_utils_resolve_confined_file_absolute_path(
5456
const char * root_dir,
5557
const char * relative_path,

tests/spx_auth_ip_subnet_ko_1.phpt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
Authentication: KO (invalid IP address)
3+
--CGI--
4+
--INI--
5+
spx.http_enabled=1
6+
spx.http_key="dev"
7+
spx.http_ip_whitelist="10.0.0.0/24"
8+
spx.http_ui_assets_dir="{PWD}/../assets/web-ui"
9+
log_errors=on
10+
--ENV--
11+
return <<<END
12+
REMOTE_ADDR=10.0.1.1
13+
REQUEST_URI=/
14+
END;
15+
--GET--
16+
SPX_KEY=dev&SPX_UI_URI=/data/metrics
17+
--FILE--
18+
<?php
19+
echo 'Normal output';
20+
?>
21+
--EXPECT--
22+
Notice: SPX: access not granted: "10.0.1.1" IP is not in white list ("10.0.0.0/24") in Unknown on line 0
23+
Normal output

tests/spx_auth_ip_subnet_ko_2.phpt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
Authentication: KO (invalid IP address)
3+
--CGI--
4+
--INI--
5+
spx.http_enabled=1
6+
spx.http_key="dev"
7+
spx.http_ip_whitelist="10.0.0.0/0"
8+
spx.http_ui_assets_dir="{PWD}/../assets/web-ui"
9+
log_errors=on
10+
--ENV--
11+
return <<<END
12+
REMOTE_ADDR=10.0.0.1
13+
REQUEST_URI=/
14+
END;
15+
--GET--
16+
SPX_KEY=dev&SPX_UI_URI=/data/metrics
17+
--FILE--
18+
<?php
19+
echo 'Normal output';
20+
?>
21+
--EXPECT--
22+
Notice: SPX: access not granted: "10.0.0.1" IP is not in white list ("10.0.0.0/0") in Unknown on line 0
23+
Normal output

tests/spx_auth_ip_subnet_ko_3.phpt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
Authentication: KO (invalid IP address)
3+
--CGI--
4+
--INI--
5+
spx.http_enabled=1
6+
spx.http_key="dev"
7+
spx.http_ip_whitelist="10.0.0.0/32"
8+
spx.http_ui_assets_dir="{PWD}/../assets/web-ui"
9+
log_errors=on
10+
--ENV--
11+
return <<<END
12+
REMOTE_ADDR=10.0.0.1
13+
REQUEST_URI=/
14+
END;
15+
--GET--
16+
SPX_KEY=dev&SPX_UI_URI=/data/metrics
17+
--FILE--
18+
<?php
19+
echo 'Normal output';
20+
?>
21+
--EXPECT--
22+
Notice: SPX: access not granted: "10.0.0.1" IP is not in white list ("10.0.0.0/32") in Unknown on line 0
23+
Normal output

tests/spx_auth_ip_subnet_ok.phpt

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
--TEST--
2+
Authentication: OK (valid IP address)
3+
--CGI--
4+
--INI--
5+
spx.http_enabled=1
6+
spx.http_key="dev"
7+
spx.http_ip_whitelist="10.0.0.0/24"
8+
spx.http_ui_assets_dir="{PWD}/../assets/web-ui"
9+
log_errors=on
10+
--ENV--
11+
return <<<END
12+
REMOTE_ADDR=10.0.0.1
13+
REQUEST_URI=/
14+
END;
15+
--GET--
16+
SPX_KEY=dev&SPX_UI_URI=/data/metrics
17+
--FILE--
18+
<?php
19+
echo 'Normal output';
20+
?>
21+
--EXPECT--
22+
{"results": [
23+
{"key": "wt","short_name": "Wall time","name": "Wall time","type": "time","releasable": 0}
24+
,{"key": "ct","short_name": "CPU time","name": "CPU time","type": "time","releasable": 0}
25+
,{"key": "it","short_name": "Idle time","name": "Idle time","type": "time","releasable": 0}
26+
,{"key": "zm","short_name": "ZE memory usage","name": "Zend Engine memory usage","type": "memory","releasable": 1}
27+
,{"key": "zmac","short_name": "ZE alloc count","name": "Zend Engine allocation count","type": "quantity","releasable": 0}
28+
,{"key": "zmab","short_name": "ZE alloc bytes","name": "Zend Engine allocated bytes","type": "memory","releasable": 0}
29+
,{"key": "zmfc","short_name": "ZE free count","name": "Zend Engine free count","type": "quantity","releasable": 0}
30+
,{"key": "zmfb","short_name": "ZE free bytes","name": "Zend Engine freed bytes","type": "memory","releasable": 0}
31+
,{"key": "zgr","short_name": "ZE GC runs","name": "Zend Engine GC run count","type": "quantity","releasable": 0}
32+
,{"key": "zgb","short_name": "ZE GC root buffer","name": "Zend Engine GC root buffer length","type": "quantity","releasable": 1}
33+
,{"key": "zgc","short_name": "ZE GC collected","name": "Zend Engine GC collected cycle count","type": "quantity","releasable": 0}
34+
,{"key": "zif","short_name": "ZE file count","name": "Zend Engine included file count","type": "quantity","releasable": 0}
35+
,{"key": "zil","short_name": "ZE line count","name": "Zend Engine included line count","type": "quantity","releasable": 0}
36+
,{"key": "zuc","short_name": "ZE class count","name": "Zend Engine user class count","type": "quantity","releasable": 0}
37+
,{"key": "zuf","short_name": "ZE func. count","name": "Zend Engine user function count","type": "quantity","releasable": 0}
38+
,{"key": "zuo","short_name": "ZE opcodes count","name": "Zend Engine user opcode count","type": "quantity","releasable": 0}
39+
,{"key": "zo","short_name": "ZE object count","name": "Zend Engine object count","type": "quantity","releasable": 1}
40+
,{"key": "ze","short_name": "ZE error count","name": "Zend Engine error count","type": "quantity","releasable": 0}
41+
,{"key": "mor","short_name": "Own RSS","name": "Process's own RSS","type": "memory","releasable": 1}
42+
,{"key": "io","short_name": "I/O Bytes","name": "I/O Bytes (reads + writes)","type": "memory","releasable": 0}
43+
,{"key": "ior","short_name": "I/O Read Bytes","name": "I/O Read Bytes","type": "memory","releasable": 0}
44+
,{"key": "iow","short_name": "I/O Written Bytes","name": "I/O Written Bytes","type": "memory","releasable": 0}
45+
]}

0 commit comments

Comments
 (0)