Skip to content

Commit

Permalink
Merge pull request #261 from NoiseByNorthwest/fix_259
Browse files Browse the repository at this point in the history
Add subnet mask support for IP matching
  • Loading branch information
NoiseByNorthwest authored Sep 14, 2024
2 parents a467ef6 + 11017e0 commit b9dd4c2
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 9 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,8 @@ while ($task = get_next_ready_task()) {
| _spx.http_enabled_ | `0` | _PHP_INI_SYSTEM_ | Whether to enable web UI and HTTP request profiling. |
| _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`. |
| _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. |
| _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`. |
| _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. |
| _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`. |
| _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>. |
| _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. |
| _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). |
| _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). |
Expand All @@ -270,6 +270,7 @@ while ($task = get_next_ready_task()) {
| _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). |
| _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). |

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

#### Private environment

Expand Down
9 changes: 2 additions & 7 deletions src/php_spx.c
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ static int check_access(void)
int found = 0;

SPX_UTILS_TOKENIZE_STRING(SPX_G(http_trusted_proxies), ',', trusted_proxy_ip_str, 64, {
if (0 == strcmp(proxy_ip_str, trusted_proxy_ip_str)) {
if (spx_utils_ip_match(proxy_ip_str, trusted_proxy_ip_str)) {
found = 1;
}
});
Expand Down Expand Up @@ -586,18 +586,13 @@ static int check_access(void)
}

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

return 1;
}
});

if (0 == strcmp(authorized_ips_str, "*")) {
/* all ips authorized */
return 1;
}

spx_php_log_notice(
"access not granted: \"%s\" IP is not in white list (\"%s\")",
ip_str,
Expand Down
60 changes: 60 additions & 0 deletions src/spx_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,67 @@
# include <pthread.h>
#endif

#include <arpa/inet.h>

#include "spx_utils.h"
#include "spx_php.h"

int spx_utils_ip_match(const char * ip_address_str, const char * target)
{
if (
strcmp(target, "*") == 0 ||
strcmp(target, ip_address_str) == 0
) {
return 1;
}

// subnet handling

const char * slash_ptr = strchr(target, '/');
if (slash_ptr == NULL) {
return 0;
}

const size_t slash_pos = slash_ptr - target;
if (! (7 <= slash_pos && slash_pos <= 15)) {
return 0;
}

const size_t target_suffix_len = strlen(slash_ptr);
if (! (2 <= target_suffix_len && target_suffix_len <= 3)) {
return 0;
}

char target_ip_address_str[32];
strncpy(target_ip_address_str, target, sizeof target_ip_address_str);
target_ip_address_str[slash_pos] = 0;

const in_addr_t target_ip_address = inet_addr(target_ip_address_str);
if (target_ip_address == INADDR_NONE) {
return 0;
}

char target_mask_str[32];
snprintf(target_mask_str, sizeof target_mask_str, "%s", slash_ptr + 1);
const long target_mask_bits = strtol(target_mask_str, NULL, 10);

if (! (1 <= target_mask_bits && target_mask_bits <= 31)) {
return 0;
}

const in_addr_t target_mask = (~0) << (32 - target_mask_bits);

const in_addr_t ip_address = inet_addr(ip_address_str);
if (ip_address == INADDR_NONE) {
return 0;
}

if ((ntohl(ip_address) & target_mask) == (ntohl(target_ip_address) & target_mask)) {
return 1;
}

return 0;
}

char * spx_utils_resolve_confined_file_absolute_path(
const char * root_dir,
Expand Down
2 changes: 2 additions & 0 deletions src/spx_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ do { \
} \
} while (0)

int spx_utils_ip_match(const char * ip_address, const char * target);

char * spx_utils_resolve_confined_file_absolute_path(
const char * root_dir,
const char * relative_path,
Expand Down
23 changes: 23 additions & 0 deletions tests/spx_auth_ip_subnet_ko_1.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
--TEST--
Authentication: KO (invalid IP address)
--CGI--
--INI--
spx.http_enabled=1
spx.http_key="dev"
spx.http_ip_whitelist="10.0.0.0/24"
spx.http_ui_assets_dir="{PWD}/../assets/web-ui"
log_errors=on
--ENV--
return <<<END
REMOTE_ADDR=10.0.1.1
REQUEST_URI=/
END;
--GET--
SPX_KEY=dev&SPX_UI_URI=/data/metrics
--FILE--
<?php
echo 'Normal output';
?>
--EXPECT--
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
Normal output
23 changes: 23 additions & 0 deletions tests/spx_auth_ip_subnet_ko_2.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
--TEST--
Authentication: KO (invalid IP address)
--CGI--
--INI--
spx.http_enabled=1
spx.http_key="dev"
spx.http_ip_whitelist="10.0.0.0/0"
spx.http_ui_assets_dir="{PWD}/../assets/web-ui"
log_errors=on
--ENV--
return <<<END
REMOTE_ADDR=10.0.0.1
REQUEST_URI=/
END;
--GET--
SPX_KEY=dev&SPX_UI_URI=/data/metrics
--FILE--
<?php
echo 'Normal output';
?>
--EXPECT--
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
Normal output
23 changes: 23 additions & 0 deletions tests/spx_auth_ip_subnet_ko_3.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
--TEST--
Authentication: KO (invalid IP address)
--CGI--
--INI--
spx.http_enabled=1
spx.http_key="dev"
spx.http_ip_whitelist="10.0.0.0/32"
spx.http_ui_assets_dir="{PWD}/../assets/web-ui"
log_errors=on
--ENV--
return <<<END
REMOTE_ADDR=10.0.0.1
REQUEST_URI=/
END;
--GET--
SPX_KEY=dev&SPX_UI_URI=/data/metrics
--FILE--
<?php
echo 'Normal output';
?>
--EXPECT--
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
Normal output
45 changes: 45 additions & 0 deletions tests/spx_auth_ip_subnet_ok.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
--TEST--
Authentication: OK (valid IP address)
--CGI--
--INI--
spx.http_enabled=1
spx.http_key="dev"
spx.http_ip_whitelist="10.0.0.0/24"
spx.http_ui_assets_dir="{PWD}/../assets/web-ui"
log_errors=on
--ENV--
return <<<END
REMOTE_ADDR=10.0.0.1
REQUEST_URI=/
END;
--GET--
SPX_KEY=dev&SPX_UI_URI=/data/metrics
--FILE--
<?php
echo 'Normal output';
?>
--EXPECT--
{"results": [
{"key": "wt","short_name": "Wall time","name": "Wall time","type": "time","releasable": 0}
,{"key": "ct","short_name": "CPU time","name": "CPU time","type": "time","releasable": 0}
,{"key": "it","short_name": "Idle time","name": "Idle time","type": "time","releasable": 0}
,{"key": "zm","short_name": "ZE memory usage","name": "Zend Engine memory usage","type": "memory","releasable": 1}
,{"key": "zmac","short_name": "ZE alloc count","name": "Zend Engine allocation count","type": "quantity","releasable": 0}
,{"key": "zmab","short_name": "ZE alloc bytes","name": "Zend Engine allocated bytes","type": "memory","releasable": 0}
,{"key": "zmfc","short_name": "ZE free count","name": "Zend Engine free count","type": "quantity","releasable": 0}
,{"key": "zmfb","short_name": "ZE free bytes","name": "Zend Engine freed bytes","type": "memory","releasable": 0}
,{"key": "zgr","short_name": "ZE GC runs","name": "Zend Engine GC run count","type": "quantity","releasable": 0}
,{"key": "zgb","short_name": "ZE GC root buffer","name": "Zend Engine GC root buffer length","type": "quantity","releasable": 1}
,{"key": "zgc","short_name": "ZE GC collected","name": "Zend Engine GC collected cycle count","type": "quantity","releasable": 0}
,{"key": "zif","short_name": "ZE file count","name": "Zend Engine included file count","type": "quantity","releasable": 0}
,{"key": "zil","short_name": "ZE line count","name": "Zend Engine included line count","type": "quantity","releasable": 0}
,{"key": "zuc","short_name": "ZE class count","name": "Zend Engine user class count","type": "quantity","releasable": 0}
,{"key": "zuf","short_name": "ZE func. count","name": "Zend Engine user function count","type": "quantity","releasable": 0}
,{"key": "zuo","short_name": "ZE opcodes count","name": "Zend Engine user opcode count","type": "quantity","releasable": 0}
,{"key": "zo","short_name": "ZE object count","name": "Zend Engine object count","type": "quantity","releasable": 1}
,{"key": "ze","short_name": "ZE error count","name": "Zend Engine error count","type": "quantity","releasable": 0}
,{"key": "mor","short_name": "Own RSS","name": "Process's own RSS","type": "memory","releasable": 1}
,{"key": "io","short_name": "I/O Bytes","name": "I/O Bytes (reads + writes)","type": "memory","releasable": 0}
,{"key": "ior","short_name": "I/O Read Bytes","name": "I/O Read Bytes","type": "memory","releasable": 0}
,{"key": "iow","short_name": "I/O Written Bytes","name": "I/O Written Bytes","type": "memory","releasable": 0}
]}

0 comments on commit b9dd4c2

Please sign in to comment.