We're having some problems configuring Aphlict for our site that uses reverse SSL proxies for access to Phabricator. It appears that Phabricator does not correctly convert the notification.client-uri scheme (to wss:// instead of ws://) in this case. I'll attach a proposed patch, but it requires some discussion.
In our setup, we have an internal machine called hawaii hosting Phabricator over HTTP. This machine is never accessed directly. Instead, we have configured a machine with an external IP address and hostname, say phabricator.example.org, which runs nginx listening for requests over HTTPS and forwarding them to the HTTP port. This is (part of) the Nginx configuration:
server { listen 443 ssl; [...SSL parameters...] server_name phabricator.example.org; location / { proxy_set_header Host phabricator.example.org; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Front-End-Https on; proxy_pass http://hawaii:8095; } }
This machine is also supposed to relay WebSockets traffic to the Phabricator instance. Therefore, we configured the Aphlict server to listen on client port 22280 and admin port 22281, set the notification.client-uri to https://localhost/ws/ and the notification.server-uri to http://localhost:22281. We added (amongst others) the following block of Nginx configuration to the reverse SSL proxy:
upstream websocket_pool { ip_hash; server hawaii:22280; } server { [..other directives as above..] location = /ws/ { proxy_pass http://websocket_pool; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Front-End-Https on; proxy_read_timeout 9999999999; } }
Even though we could now succesfully open a WebSocket and send messages via https://phabricator.example.org/ws/ that ended up in the Aphlict logs, the Phabricator main page could not connect. Eventually, I traced this down to the config.websocketURI being set to ws://phabricator.example.org/ws/ instead of wss://phabricator.example.org/ws; hotfixing this in Aphlict.js "fixed" the problem and made the notification server work beautifully.
Tracing it further, this (seemingly) wrong choice was made in src/view/page/PhabricatorStandardPageView.php. Here, notification.client-uri is read and the protocol is set to either "ws" or "wss". However, this choice is made based on whether the current connection, according to Phabricator, is HTTPS. It seems to me that this choice should depend on the scheme of notification.client-uri (i.e. $client_uri->getProtocol() == "https"), not on the current connection. And, making things even more interesting, the current connection *was* actually over HTTPS, but AprontRequest.php only checks $_SERVER['HTTPS'] and not the standard X-Forwarded-Proto / Front-End-Https headers. It's possible that this HTTPS choice is made incorrectly in more places because of this.
The attached patch fixes this problem by making the choice of scheme in $client_uri based on the current scheme in $client_uri. The two scenarios where this may break existing setups are:
- Current request is HTTP, notification.client-uri describes a https:// URI but Aphlict doesn't use SSL. In that case, I wonder why notification.client-uri describes a HTTPS URI in the first place.
- Current request is HTTPS, notification.client-uri describes a http:// URI and Aphlict uses SSL. This will probably occur in various places, as https://secure.phabricator.com/book/phabricator/article/notifications/ describes that the notification.client-uri can be set to http://localhost/ws/ if this type of setup is used.
Another possible fix would be to change AphrontRequest::isHTTPS to look at the X-Forwarded-Proto or Front-End-Https headers as well:
- If current request is HTTPS but Aphront listens on HTTP, browser would not allow the connection currently as well, so we're not breaking anything by switching that to HTTPS
- If current request is HTTP, then you're on your own if you set headers indicating your connection is actually HTTPS if it's not.
It does not sound to me like the right solution, but it might prevent breakage for some users.