console-remote-server icon indicating copy to clipboard operation
console-remote-server copied to clipboard

NGINX example config is broken

Open elpollodiablo opened this issue 3 years ago • 13 comments

The /socket.io location section is missing, and the braces do not match up. I recommend to only publish the site configuration, not a complete nginx configuration with ssl and whatnot, as many people do not terminate ssl on their backends.

My working config for reference:

root@lxt-prod-invnt-consolere01:~# cat  /etc/nginx/sites-enabled/tty.my.domain
upstream consoleServerBackend {
    ip_hash;
    server 127.0.0.1:8081;
}

map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

server {

    server_name  tty.my.domain;
    listen       8080;
    underscores_in_headers on;
    error_log /var/log/nginx/consolere.error.log;
    keepalive_timeout    60;
    large_client_header_buffers 8 32k;
    charset UTF-8;
    root /srv/app/console-remote-server/build;

    location ~* \.(jpg|jpeg|gif|png|ico|css|zip|tgz|pdf|txt|js|flv|swf|ttf|otf|woff|eot)$ {
        access_log off;
        error_log /var/log/nginx/consolere.static.error.log;
        expires 30d;
    }

    location /socket.io {
        proxy_pass http://consoleServerBackend/socket.io;
        proxy_set_header Proxy '';
        proxy_set_header Host $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;
        proxy_redirect off;
    
        set $CORS_CREDS true;
        set $CORS_ORIGIN $http_origin;
        set $CORS_METHODS 'GET, POST, PUT, DELETE, OPTIONS';
        set $CORS_HEADERS 'Authentication-Token, Cache-Control, Cookie, If-Modified-Since, Range, User-Agent, X-Requested-With';
        # FYI: Always allowed headers: Accept, Accept-Language, Content-Language, Content-Type
        set $CORS_EXPOSE_HEADERS 'Content-Disposition, Content-Length, Content-Range, Set-Cookie';
        # FYI: Always exposed headers: Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma
        set $CORS_PREFLIGHT_CACHE_AGE 600;
        set $X_FRAME_OPTIONS '';
        # set $X_FRAME_OPTIONS "ALLOW FROM $http_origin";
    
        if ($request_method = 'OPTIONS') {
            add_header Access-Control-Allow-Origin $CORS_ORIGIN;
            add_header Access-Control-Allow-Methods $CORS_METHODS;
            add_header Access-Control-Allow-Headers $CORS_HEADERS;
            add_header Access-Control-Allow-Credentials $CORS_CREDS;
    
            add_header Access-Control-Max-Age $CORS_PREFLIGHT_CACHE_AGE;
            add_header Content-Type 'text/plain; charset=utf-8';
            add_header Content-Length 0;
            return 204;
        }
    
        if ($request_method != 'OPTIONS') {
              add_header Access-Control-Allow-Origin $CORS_ORIGIN;
              add_header Access-Control-Allow-Methods $CORS_METHODS;
              add_header Access-Control-Allow-Headers $CORS_HEADERS;
              add_header Access-Control-Allow-Credentials $CORS_CREDS;
    
              add_header Access-Control-Expose-Headers $CORS_EXPOSE_HEADERS;
              add_header X-Frame-Options $X_FRAME_OPTIONS;
        }
    
        # supposedly prevents 502 bad gateway error;
        proxy_buffers 8 32k;
        proxy_buffer_size 64k;
        proxy_read_timeout 120;
        proxy_connect_timeout 120;
    
        # proxy_next_upstream error timeout;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    
        access_log /var/log/nginx/consolere.websockets.log;
        error_log /var/log/nginx/consolere.websockets.error.log;

    }    
    location / {
        access_log /var/log/nginx/consolere.access.log;
        expires 30d;
        index /www/index.html;
        error_log /var/log/nginx/consolere.error.log;
        try_files $uri $uri/ /app/consoleapp.html;
    }
}

elpollodiablo avatar Apr 04 '21 18:04 elpollodiablo

@elpollodiablo Thank you for sharing!

You right, I think I lost one line location /socket.io { in the configuration. I'll update my example.

I would not recommend to use port 8080 on public domain, because some client could have problem to connect, using SSL (with let's encrypt) is better.

kurdin avatar Apr 04 '21 18:04 kurdin

I would not recommend to use port 8080 on public domain, because some client could have problem to connect, using SSL (with let's encrypt) is better.

That's why I suggested leaving the https config out, most serious users will not have something like a console.re server public facing, but rather put it behind a reverse proxy (in addition to the nginx). I terminate my SSL on haproxy, so I had to remove all the ssl references :)

If a user wants SSL/le for their nginx, there are better documentation resources than an nginx example in an unrelated project.

I like the software very much, so please do not take this negatively. Thanks for your work!

elpollodiablo avatar Apr 04 '21 18:04 elpollodiablo

@elpollodiablo Yeah, all good and thanks for the feedback and thank you for using it. I think more or less advance users will know that do it with configuration for public domain, what I have is just an example, it is not like copy and paste code (in my view). Less experienced users will install and use it for local development.

I have clients with private servers and they all pretty much use the nginx only for console server with subdomains like remoteconsole.domain.com. I wondering why do you need haproxy in front of nginx ? What a benefit you can get out of that setup ?

kurdin avatar Apr 04 '21 19:04 kurdin

I wondering why do you need haproxy in front of nginx?

Because it's the central loadbalancer for all my projects, websites and apis alike.

What a benefit you can get out of that setup?

Security (because everything that's public facing is a potential vector), ease of maintenance (only need to update SSL in one place, only need to configure letsencrypt in one place etc) and high reliablity/failover (...that I only need to maintain in one place instead of each project seperately).

I could leave out the local nginxen, but then I'd have to poke around in the haproxy, and I don't want that, because it serves more than one project/client. Each tool has its place :)

elpollodiablo avatar Apr 04 '21 19:04 elpollodiablo

Well, you can load balance with nginx just fine and security is pretty good too. Pretty much everything that haproxy can, nginx could do as well.

I could leave out the local nginxen, but then I'd have to poke around in the haproxy, and I don't want that, because it serves more than one project/client. Each tool has its place :)

This is true, but each additional running tool on server eats resources, that is why I asked, I think if possible to cut down to minimal number of tools on server to serve the same content, you could save some money on your servers. But yeah, it is all depend on many aspects and cases, sometime it is just a very convenient to separate similar functionality between tools.

kurdin avatar Apr 04 '21 19:04 kurdin

Nevermind, I was just trying to point out that the example config is unexpectedly opinionated :) I have my reasons for my setup (all domains are on one ip, seperation of concerns, security, maintainability etc, I've been refining this for 20+ years), but indeed my setup is saving time instead of the last 10% of hardware :)

I found two issues relating to CORS:

The X-consolere header that the client sends needs to be included in the cors headers:

    set $CORS_HEADERS 'Authentication-Token, Cache-Control, Cookie, If-Modified-Since, Range, User-Agent, X-Requested-With, X-consolere';

Also, since both nginx and the node software set the cors origin header, we need to unset it first in nginx, otherwise there'll be two of them, and browsers don't like that:

 proxy_hide_header 'access-control-allow-origin';

Maybe also add a note that this is an "every domain can log to your server" setup and that this would be the place to hard-code the allowed referer:

// this will set the header to the incoming referer,
// there could be a regex condition to validate if one wants to support a wildcard:
// add_header Access-Control-Allow-Origin $CORS_ORIGIN;
// this will set the allowed referers to only https://only.my.domain
add_header Access-Control-Allow-Origin https://only.my.domain;

It would be a little easier for some to configure it in the software, but since nginx is necessary for the deployment, this should be fine too.

elpollodiablo avatar Apr 04 '21 20:04 elpollodiablo

Not to be a nag, but https://github.com/kurdin/console-remote-server/commit/bcc1730acf3fab436e89631883095a4c58e27cfd#diff-b7a041c6d181d81dfa72eda8d8c80e89625d0df3270437ef4f82f6462dcf34b4R43 will also need a /socket.io I think? At least it does in my setup. Sorry I forgot to mention it earlier.

elpollodiablo avatar Apr 04 '21 21:04 elpollodiablo

Not to be a nag, but bcc1730 will also need a /socket.io I think?

I think I added location /socket.io { on line 42 https://github.com/kurdin/console-remote-server/blob/main/config/console.nginx.example.conf#L42 Where did you add that on your setup ?

The X-consolere header that the client sends needs to be included in the cors headers

I don't have x-consolere header on my console.re setup, need to double check that on other server.

proxy_hide_header 'access-control-allow-origin'; Same here, I don't see a problems with that on my setup, will check more

kurdin avatar Apr 05 '21 01:04 kurdin

location /socket.io {
    proxy_pass http://consoleServerBackend/socket.io;

otherwise the backend receives only the urlpart after /socket.io

elpollodiablo avatar Apr 05 '21 01:04 elpollodiablo

I don't have x-consolere header on my console.re setup, need to double check that on other server.

I had to add it because Chrome balked without it in a cross sub domain setup (Version 90.0.4430.51 (Official Build) beta (x86_64))

elpollodiablo avatar Apr 05 '21 01:04 elpollodiablo

otherwise the backend receives only the urlpart after /socket.io

ok, yeah, you right I have rewrite /socket.io/(.*) /socket.io/$1 break; but using proxy_pass http://consoleServerBackend/socket.io; might be a better solution.

kurdin avatar Apr 05 '21 01:04 kurdin

@elpollodiablo I am not Nginx config expert for sure, but strangely if I remove rewrite /socket.io/(.*) /socket.io/$1 break; and change http://consoleServerBackend/socket.io in configuration, Google Chrome starts complaining about x-consolere header needs to be in CORS, with rewrite, it works just fine.

kurdin avatar Apr 05 '21 01:04 kurdin

Maybe also add a note that this is an "every domain can log to your server" setup and that this would be the place to hard-code the allowed referer.

Good idea, I'll add that, thanks

kurdin avatar Apr 05 '21 01:04 kurdin