2017-06-23 07:28:04 +02:00
|
|
|
<?php
|
|
|
|
|
2017-06-25 05:12:29 +02:00
|
|
|
namespace SilverStripe\Control\Middleware;
|
|
|
|
|
|
|
|
use SilverStripe\Control\HTTPRequest;
|
|
|
|
use SilverStripe\Control\Util\IPUtils;
|
2017-06-23 07:28:04 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* This middleware will rewrite headers that provide IP and host details from an upstream proxy.
|
|
|
|
*/
|
|
|
|
class TrustedProxyMiddleware implements HTTPMiddleware
|
|
|
|
{
|
2017-06-25 05:12:29 +02:00
|
|
|
/**
|
|
|
|
* Comma-separated list of IP ranges that are trusted to provide proxy headers.
|
|
|
|
* Can also be 'none' or '*' (all)
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
2017-06-23 07:28:04 +02:00
|
|
|
private $trustedProxyIPs = null;
|
|
|
|
|
2017-06-25 05:12:29 +02:00
|
|
|
/**
|
|
|
|
* Array of headers from which to lookup the hostname
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
2017-06-23 07:28:04 +02:00
|
|
|
private $proxyHostHeaders = [
|
|
|
|
'X-Forwarded-Host'
|
|
|
|
];
|
|
|
|
|
2017-06-25 05:12:29 +02:00
|
|
|
/**
|
|
|
|
* Array of headers from which to lookup the client IP
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
2017-06-23 07:28:04 +02:00
|
|
|
private $proxyIPHeaders = [
|
|
|
|
'Client-IP',
|
|
|
|
'X-Forwarded-For'
|
|
|
|
];
|
|
|
|
|
2017-06-25 05:12:29 +02:00
|
|
|
/**
|
|
|
|
* Array of headers from which to lookup the client scheme (http/https)
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
2017-06-23 07:28:04 +02:00
|
|
|
private $proxySchemeHeaders = [
|
|
|
|
'X-Forwarded-Protocol',
|
|
|
|
'X-Forwarded-Proto',
|
|
|
|
];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the comma-separated list of IP ranges that are trusted to provide proxy headers
|
2017-06-25 05:12:29 +02:00
|
|
|
* Can also be 'none' or '*' (all)
|
2017-06-23 07:28:04 +02:00
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getTrustedProxyIPs()
|
|
|
|
{
|
|
|
|
return $this->trustedProxyIPs;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the comma-separated list of IP ranges that are trusted to provide proxy headers
|
2017-06-25 05:12:29 +02:00
|
|
|
* Can also be 'none' or '*' (all)
|
2017-06-23 07:28:04 +02:00
|
|
|
*
|
2017-06-25 05:12:29 +02:00
|
|
|
* @param string $trustedProxyIPs
|
|
|
|
* @return $this
|
2017-06-23 07:28:04 +02:00
|
|
|
*/
|
|
|
|
public function setTrustedProxyIPs($trustedProxyIPs)
|
|
|
|
{
|
|
|
|
$this->trustedProxyIPs = $trustedProxyIPs;
|
2017-06-25 05:12:29 +02:00
|
|
|
return $this;
|
2017-06-23 07:28:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-06-25 05:12:29 +02:00
|
|
|
* Return the array of headers from which to lookup the hostname
|
2017-06-23 07:28:04 +02:00
|
|
|
*
|
2017-06-25 05:12:29 +02:00
|
|
|
* @return array
|
2017-06-23 07:28:04 +02:00
|
|
|
*/
|
|
|
|
public function getProxyHostHeaders()
|
|
|
|
{
|
|
|
|
return $this->proxyHostHeaders;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-06-27 00:19:51 +02:00
|
|
|
* Set the array of headers from which to lookup the hostname.
|
2017-06-23 07:28:04 +02:00
|
|
|
*
|
2017-06-27 00:19:51 +02:00
|
|
|
* @param array $proxyHostHeaders
|
2017-06-25 05:12:29 +02:00
|
|
|
* @return $this
|
2017-06-23 07:28:04 +02:00
|
|
|
*/
|
|
|
|
public function setProxyHostHeaders($proxyHostHeaders)
|
|
|
|
{
|
2017-06-25 05:12:29 +02:00
|
|
|
$this->proxyHostHeaders = $proxyHostHeaders ?: [];
|
|
|
|
return $this;
|
2017-06-23 07:28:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-06-25 05:12:29 +02:00
|
|
|
* Return the array of headers from which to lookup the client IP
|
2017-06-23 07:28:04 +02:00
|
|
|
*
|
2017-06-25 05:12:29 +02:00
|
|
|
* @return array
|
2017-06-23 07:28:04 +02:00
|
|
|
*/
|
|
|
|
public function getProxyIPHeaders()
|
|
|
|
{
|
|
|
|
return $this->proxyIPHeaders;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-06-27 00:19:51 +02:00
|
|
|
* Set the array of headers from which to lookup the client IP.
|
2017-06-23 07:28:04 +02:00
|
|
|
*
|
2017-06-27 00:19:51 +02:00
|
|
|
* @param array $proxyIPHeaders
|
2017-06-25 05:12:29 +02:00
|
|
|
* @return $this
|
2017-06-23 07:28:04 +02:00
|
|
|
*/
|
|
|
|
public function setProxyIPHeaders($proxyIPHeaders)
|
|
|
|
{
|
2017-06-25 05:12:29 +02:00
|
|
|
$this->proxyIPHeaders = $proxyIPHeaders ?: [];
|
|
|
|
return $this;
|
2017-06-23 07:28:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-06-25 05:12:29 +02:00
|
|
|
* Return the array of headers from which to lookup the client scheme (http/https)
|
2017-06-23 07:28:04 +02:00
|
|
|
*
|
2017-06-25 05:12:29 +02:00
|
|
|
* @return array
|
2017-06-23 07:28:04 +02:00
|
|
|
*/
|
|
|
|
public function getProxySchemeHeaders()
|
|
|
|
{
|
|
|
|
return $this->proxySchemeHeaders;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-06-25 05:12:29 +02:00
|
|
|
* Set array of headers from which to lookup the client scheme (http/https)
|
|
|
|
* Can also specify comma-separated list as a single string.
|
2017-06-23 07:28:04 +02:00
|
|
|
*
|
2017-06-27 00:19:51 +02:00
|
|
|
* @param array $proxySchemeHeaders
|
2017-06-25 05:12:29 +02:00
|
|
|
* @return $this
|
2017-06-23 07:28:04 +02:00
|
|
|
*/
|
|
|
|
public function setProxySchemeHeaders($proxySchemeHeaders)
|
|
|
|
{
|
2017-06-25 05:12:29 +02:00
|
|
|
$this->proxySchemeHeaders = $proxySchemeHeaders ?: [];
|
|
|
|
return $this;
|
2017-06-23 07:28:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function process(HTTPRequest $request, callable $delegate)
|
|
|
|
{
|
|
|
|
// If this is a trust proxy
|
|
|
|
if ($this->isTrustedProxy($request)) {
|
|
|
|
// Replace host
|
2017-06-25 05:12:29 +02:00
|
|
|
foreach ($this->getProxyHostHeaders() as $header) {
|
2017-06-23 07:28:04 +02:00
|
|
|
$hostList = $request->getHeader($header);
|
|
|
|
if ($hostList) {
|
2022-04-14 03:12:59 +02:00
|
|
|
$request->addHeader('Host', strtok($hostList ?? '', ','));
|
2017-06-23 07:28:04 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Replace scheme
|
2017-06-25 05:12:29 +02:00
|
|
|
foreach ($this->getProxySchemeHeaders() as $header) {
|
|
|
|
$headerValue = $request->getHeader($header);
|
|
|
|
if ($headerValue) {
|
2022-04-14 03:12:59 +02:00
|
|
|
$request->setScheme(strtolower($headerValue ?? ''));
|
2017-06-23 07:28:04 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Replace IP
|
|
|
|
foreach ($this->proxyIPHeaders as $header) {
|
2017-06-25 05:12:29 +02:00
|
|
|
$headerValue = $request->getHeader($header);
|
|
|
|
if ($headerValue) {
|
|
|
|
$ipHeader = $this->getIPFromHeaderValue($headerValue);
|
|
|
|
if ($ipHeader) {
|
|
|
|
$request->setIP($ipHeader);
|
|
|
|
break;
|
|
|
|
}
|
2017-06-23 07:28:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $delegate($request);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determine if the current request is coming from a trusted proxy
|
|
|
|
*
|
2017-06-25 05:12:29 +02:00
|
|
|
* @param HTTPRequest $request
|
|
|
|
* @return bool True if the request's source IP is a trusted proxy
|
2017-06-23 07:28:04 +02:00
|
|
|
*/
|
2017-06-25 05:12:29 +02:00
|
|
|
protected function isTrustedProxy(HTTPRequest $request)
|
2017-06-23 07:28:04 +02:00
|
|
|
{
|
2017-06-25 05:12:29 +02:00
|
|
|
$trustedIPs = $this->getTrustedProxyIPs();
|
|
|
|
|
2017-06-23 07:28:04 +02:00
|
|
|
// Disabled
|
2017-06-25 05:12:29 +02:00
|
|
|
if (empty($trustedIPs) || $trustedIPs === 'none') {
|
2017-06-23 07:28:04 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Allow all
|
|
|
|
if ($trustedIPs === '*') {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate IP address
|
2017-06-25 05:12:29 +02:00
|
|
|
$ip = $request->getIP();
|
|
|
|
if ($ip) {
|
2022-04-14 03:12:59 +02:00
|
|
|
return IPUtils::checkIP($ip, preg_split('/\s*,\s*/', $trustedIPs ?? ''));
|
2017-06-23 07:28:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Extract an IP address from a header value that has been obtained.
|
|
|
|
* Accepts single IP or comma separated string of IPs
|
|
|
|
*
|
|
|
|
* @param string $headerValue The value from a trusted header
|
|
|
|
* @return string The IP address
|
|
|
|
*/
|
|
|
|
protected function getIPFromHeaderValue($headerValue)
|
|
|
|
{
|
2017-06-25 05:12:29 +02:00
|
|
|
// Sometimes the IP from a load balancer could be "x.x.x.x, y.y.y.y, z.z.z.z"
|
|
|
|
// so we need to find the most likely candidate
|
2022-04-14 03:12:59 +02:00
|
|
|
$ips = preg_split('/\s*,\s*/', $headerValue ?? '');
|
2017-06-25 05:12:29 +02:00
|
|
|
|
|
|
|
// Prioritise filters
|
|
|
|
$filters = [
|
|
|
|
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE,
|
|
|
|
FILTER_FLAG_NO_PRIV_RANGE,
|
|
|
|
null
|
|
|
|
];
|
|
|
|
foreach ($filters as $filter) {
|
|
|
|
// Find best IP
|
2017-06-23 07:28:04 +02:00
|
|
|
foreach ($ips as $ip) {
|
2022-04-14 03:12:59 +02:00
|
|
|
if (filter_var($ip, FILTER_VALIDATE_IP, $filter ?? 0)) {
|
2017-06-23 07:28:04 +02:00
|
|
|
return $ip;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-06-25 05:12:29 +02:00
|
|
|
return null;
|
2017-06-23 07:28:04 +02:00
|
|
|
}
|
|
|
|
}
|