I am hosting a Zend Framework 3 site on MS Azure. There is a problem with the session validation as Azure's Application Gateway is acting as a reverse proxy which the remote address validator doesn't like so the session is not read.
I can see from the class reference for remote address that there is a method setTrustedProxies()
which, according to the documenation, I can pass an array of IP addresses to. However I don't really have much of an idea of how to do this.
I am configuring the session_manager in global.php
'session_manager' => [
// Session validators (used for security).
'validators' => [
RemoteAddr::class,
HttpUserAgent::class,
],
],
Then, in Module.php I am instantiating the session manager using
$sessionManager = $serviceManager->get(SessionManager::class);
I am then trying to add the trusted proxies using the following with fake IPs
$sessionManager = $serviceManager->get(SessionManager::class);
$request = $serviceManager->get('Request');
$remAdd = $request->getServer()->get('REMOTE_ADDR');
$remoteAddr = new RemoteAdddress($remAdd);
$remoteAddr->setTrustedProxies(['192.98.98.11', '187.2.2.10']);
$remoteAddr->setProxyHeader('X-Forwarded-For');
$remoteAddr->setUseProxy($useProxy = true);
$chain = $sessionManager->getValidatorChain();
$chain->attach('session.validate', array($remoteAddr, 'isValid'));
I am almost certain this is not the correct way to do this but I can't find any documentation online about setting the trusted proxies.
If I do
$chain = $sessionManager->getValidatorChain();
print_r($chain);
after adding the proxies I don't see any reference to proxies in the output
Zend\Session\ValidatorChain Object
(
[events:protected] => Array
(
[session.validate] => Array
(
[1] => Array
(
[0] => Array
(
[0] => Array
(
[0] => Zend\Session\Validator\RemoteAddr Object
(
[data:protected] => 127.0.0.1
)
[1] => isValid
)
)
)
)
)
[eventPrototype:protected] => Zend\EventManager\Event Object
(
[name:protected] =>
[target:protected] =>
[params:protected] => Array
(
)
[stopPropagation:protected] =>
)
[identifiers:protected] => Array
(
)
[sharedManager:protected] =>
[storage:protected] => Zend\Session\Storage\SessionArrayStorage Object
(
)
)
As I said, I am pretty sure that I am going about this the wrong way so would be very grateful for any help in finding the correct method.
I have stumbled upon the exact same issue, only in my case I am using CF.
Your approach is wrong because you are using Zend\Http\PhpEnvironment\RemoteAddress and you are not replacing the already loaded validator Zend\Session\Validator\RemoteAddr.
Zend\Session\Validator\RemoteAddr is internally using Zend\Http\PhpEnvironment\RemoteAddress so you should not use Zend\Http\PhpEnvironment\RemoteAddress: https://github.com/zendframework/zend-session/blob/master/src/Validator/RemoteAddr.php
Here is the working code, tested:
public function onBootstrap(MvcEvent $event)
{
$application = $event->getApplication();
$serviceManager = $application->getServiceManager();
// The following line instantiates the SessionManager and automatically
// makes the SessionManager the 'default' one to avoid passing the
// session manager as a dependency to other models.
$sessionManager = $serviceManager->get(SessionManager::class);
$remoteAddr = new \Zend\Session\Validator\RemoteAddr();
//$remoteAddr->setTrustedProxies([]);
$remoteAddr->setProxyHeader('CF-Connecting-IP');
$remoteAddr->setUseProxy(true);
$current_chain = $sessionManager->getValidatorChain(\Zend\Session\Validator\RemoteAddr::class);
$current_chain->attach(
'session.validate',
[ new \Zend\Session\Validator\HttpUserAgent(), 'isValid' ]
);
$current_chain->attach(
'session.validate',
[ $remoteAddr, 'isValid' ]
);
$sessionManager->start();
Container::setDefaultManager($sessionManager);
}
Result:
Zend\Session\ValidatorChain Object
(
[events:protected] => Array
(
[session.validate] => Array
(
[1] => Array
(
[0] => Array
(
[0] => Array
(
[0] => Zend\Session\Validator\HttpUserAgent Object
(
[data:protected] => Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36
)
[1] => isValid
)
[1] => Array
(
[0] => Zend\Session\Validator\RemoteAddr Object
(
[data:protected] => MY_REAL_IP
)
[1] => isValid
)
)
)
)
)
)
I also added print_r($remoteAddress); inside https://github.com/zendframework/zend-session/blob/master/src/Validator/RemoteAddr.php function and test with TrustedProxies:
/**
* Returns client IP address.
*
* @return string IP address.
*/
protected function getIpAddress()
{
$remoteAddress = new RemoteAddress();
$remoteAddress->setUseProxy(static::$useProxy);
$remoteAddress->setTrustedProxies(static::$trustedProxies);
$remoteAddress->setProxyHeader(static::$proxyHeader);
print_r($remoteAddress);
return $remoteAddress->getIpAddress();
}
Result:
Zend\Http\PhpEnvironment\RemoteAddress Object
(
[useProxy:protected] => 1
[trustedProxies:protected] => Array
(
[0] => 192.98.98.11
[1] => 187.2.2.10
)
[proxyHeader:protected] => HTTP_CF_CONNECTING_IP
)
The conclusion is that setTrustedProxies is working, you just cannot see it because https://github.com/zendframework/zend-session/blob/master/src/Validator/RemoteAddr.php does not return this for it to be visible since it just passes it to Zend\Http\PhpEnvironment\RemoteAddress.
You can of course overwrite the 'default' SessionManager exactly like your code:
$sessionConfig = new SessionConfig();
$sessionConfig->setOptions($config);
$sessionManager = new SessionManager($sessionConfig);
But this is just optional.