2

I'm running a Golang back end on port 12345 and an Angular front end on port 8080. They communicate via websockets on a page called /consultation. When I open the firewall for both ports and have them communicate via their IP and port addresses, they work well. But with Nginx between them, it fails because Nginx prevents a cookie from being sent from the front end to the back end. For development purposes, I coded the back end to panic and break due to a nil map error if it doesn't receive a cookie from the front end, which it now does. I can't figure out what's preventing the cookie from being received by the back end.

Nginx reverse proxies from http://frontend.mydomain.me to http://localhost:8080. Based on Nginx docs and other solutions online, the conf is:

server {
  listen 80;
  server_name frontend.mydomain.me;

location / {
  ...
}

location /consultation {

  # Tried with and without these.
  proxy_set_header Access-Control-Allow-Headers "*";
  proxy_set_header Access-Control-Allow-Methods "*";
  proxy_set_header Access-Control-Allow-Credentials "true";

  # Tried with and without these.
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header Host $host;
  proxy_set_header X-NginX-Proxy true;

  # With or without these, http is upgraded to ws anyway.
  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection "upgrade";

  proxy_pass http://localhost:8080/consultation;
}
}

As mentioned in my comments above in the code, with or without upgrading the connection in Nginx conf, http is still upgraded to ws as seen in the response headers (displayed in the Network tab in developer tools in both Chrome and Firefox):

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: WfOsnTuctT2GopMFe55WOC7Dwk4=

I don't know if this indicates if it's not necessary to upgrade the connection using Nginx and if it has anything to do with the problem.

I've tried adding these to the Nginx conf but they didn't make any difference:

proxy_set_header Cookie $cookie_client;
proxy_set_header Cookie "$http_cookie;client=testvalue";

Angular front end sets the cookie (without HttpOnly or Secure) the moment the client arrives at the landing page:

this.cookie.set(this.cookieName, this.cookieValue)

Angular front end code that opens a socket connection to the back end for http://frontend.mydomain.me/consultation:

this.socket = new WebSocket("ws://MY_IP:12345/consultation");

The Golang back end is served directly at MY_IP:12345, without Nginx. Here's the code for the relevant page at /consultation, it contains the bit that requests the cookie from the front end:

func consultation(res http.ResponseWriter, req *http.Request) {
    // Upgrade connection to websocket.
    conn, _ := (&websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}).Upgrade(res, req, nil)

    // Print http request headers to console.
    output, _ := httputil.DumpRequest(req, true)
    fmt.Println(string(output))

    // This is the code that requests for the cookie that never arrives.
    var cookieName string
    var cookieValue string
    for _, cookie := range req.Cookies() {
        cookieName = cookie.Name
        cookieValue = cookie.Value
    }
   ...
}

When I open both ports 12345 and 8080 and have front and back ends communicate directly between them, the cookie is successfully sent and received as seen in these request headers dumped by httputil.DumpRequest() in the Golang code above:

GET /consultation HTTP/1.1
Host: MY_IP:12345
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,en-GB;q=0.8
Cache-Control: no-cache
Connection: Upgrade

// Right here.
Cookie: client=f7da8732-6e15-4157-8cf7-a13b7ce2b5cf

Origin: http://MY_IP:8080
Pragma: no-cache
Sec-Websocket-Extensions: permessage-deflate; client_max_window_bits
Sec-Websocket-Key: wgMYe0jAVnutW9LEuSFFCg==
Sec-Websocket-Version: 13
Upgrade: websocket
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36

But with Nginx, it's possibly sent but not received. I'm not sure:

GET /consultation HTTP/1.1
Host: MY_IP:12345
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,en-GB;q=0.8
Cache-Control: no-cache
Connection: Upgrade
Origin: http://frontend.mydomain.me
Pragma: no-cache
Sec-Websocket-Extensions: permessage-deflate; client_max_window_bits
Sec-Websocket-Key: fm2Bkc8ZPoTcGeAZi/n+WA==
Sec-Websocket-Version: 13
Upgrade: websocket
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36

I must still be missing something. What could it be?

As far as I can tell, my Nginx code is correct. It's based on the Nginx docs and also on other people's solutions.

Update 27 Mar 2019: as requested in the comments, here's what the results from sudo nginx -T:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
# configuration file /etc/nginx/nginx.conf:
user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
        worker_connections 768;
        # multi_accept on;
}

http {
        # Basic Settings

        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 65;
        types_hash_max_size 2048;

        server_names_hash_bucket_size 128;

        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        # SSL Settings

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
        ssl_prefer_server_ciphers on;

        # Logging Settings

        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;

        # Gzip Settings

        gzip on;
        gzip_disable "msie6";

        # Virtual Host Configs

        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;
}

# configuration file /etc/nginx/mime.types:

types {
    # Removed for brevity.
}

# configuration file /etc/nginx/sites-enabled/subdomain1:

server {
listen 80;
server_name subdomain1.mydomain.me subdomain1.org;

listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/subdomain1.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/subdomain1.org/privkey.pem;

Redirect non-https traffic to https
if ($scheme != "https") {
return 301 https://$host$request_uri;
}

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;


location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /home/subdomain1/subdomain1;
}

location / {
include proxy_params;
proxy_pass http://unix:/home/subdomain1/subdomain1.sock;
}
}

# configuration file /etc/nginx/proxy_params:
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

## This is the back end in this question on Server fault.
## It is currently not being used since the front end connects directly to MY_IP:12345.
# configuration file /etc/nginx/sites-enabled/backend:
server {
listen 80;
server_name backend.mydomain.me;

location / {
include proxy_params;
proxy_pass http://localhost:12345/;
}

location /consultation {
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

       if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'POST';
        add_header 'Access-Control-Allow-Headers' 'Content-Type';
        add_header 'Content-Type' 'application/json';
         return 204;
    }
     if ($request_method = 'POST') {
         add_header 'Access-Control-Allow-Origin' '*';
         add_header 'Access-Control-Allow-Methods' 'POST';
         add_header 'Access-Control-Allow-Headers' 'Content-Type';
         add_header 'Content-Type' 'application/json';
     }
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_set_header X-NginX-Proxy true;

   proxy_pass "http://localhost:12345/consultation";
}
}

# configuration file /etc/nginx/proxy_params:
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

## This is the frontend for this question on Server Fault.
# configuration file /etc/nginx/sites-enabled/frontend:
server {
listen 80;
server_name frontend.mydomain.me;

location / {
  include proxy_params;
  proxy_pass http://localhost:8080/;
}

location /consultation {
  include proxy_params;
  proxy_pass http://localhost:8080/consultation;
}
}

# configuration file /etc/nginx/proxy_params:
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

# configuration file /etc/nginx/proxy_params:
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

# Default server configuration

server {
        listen 80 default_server;
        listen [::]:80 default_server;

        root /var/www/html;

        # Add index.php to the list if you are using PHP
        index index.html index.htm index.nginx-debian.html;

        server_name _;

        location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
                try_files $uri $uri/ =404;
        }
        }
}

# configuration file /etc/nginx/sites-enabled/mydomain:
server {
listen 80;
server_name MY_IP mydomain.me;

listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/mydomain.me/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mydomain.me/privkey.pem;

Redirect non-https traffic to https
if ($scheme != "https") {
return 301 https://$host$request_uri;
}

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /home/mydomain/mydomain;
}

location / {
include proxy_params;
proxy_pass http://unix:/home/mydomain/mydomain.sock;
}
}

# configuration file /etc/nginx/proxy_params:
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

# configuration file /etc/nginx/sites-enabled/subdomain2:

server {
listen 80;
server_name subdomain2.mydomain.me;

listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/mydomain.me-0001/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mydomain.me-0001/privkey.pem;

if ($scheme != "https") {
return 301 https://$host$request_uri;
}

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /home/subdomain2/subdomain2;
}

location / {
include proxy_params;
proxy_pass http://unix:/home/subdomain2/subdomain2.sock;
}
}

# configuration file /etc/nginx/proxy_params:
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
nusantara
  • 141
  • 1
  • 5
  • Your nginx configuration looks fine. At this point I can only suggest you go back to looking at how the cookie is created. Did you set secure and httponly? What domain is it for, and is that where you wanted the cookie to be sent? It looks like you opened a websocket to an IP address instead of a name. Obviously the cookie from some other domain won't be sent in that case. – Michael Hampton Mar 27 '19 at 16:05
  • @MichaelHampton Thanks, you pointed me in the right direction. I didn't know we can't pass cookies between two different domains/subdomains. – nusantara Mar 27 '19 at 22:33

1 Answers1

2

It turns out, we can't simply pass cookies between different domains and subdomains.

In my case, I tried to pass it from frontend.mydomain.me to MY_IP:12345. I actually also tried to pass it from frontend.mydomain.me to backend.mydomain.me. None of these worked because they're on different domains.

This clarified it a bit more for me.

In the end, I configured Nginx to serve the front end on projectname.mydomain.me/frontend and the backend on projectname.mydomain.me/backend. This worked because both back and front ends are now on the same domain, projectname.mydomain.me.

Thanks to Michael Hampton for pointing me in the right direction in the comments.

nusantara
  • 141
  • 1
  • 5