Назад | Перейти на главную страницу

Nginx предотвращает отправку файлов cookie из внешнего интерфейса в серверный

Я использую серверную часть Golang на порту 12345 и интерфейсную часть Angular на порту 8080. Они общаются через веб-сокеты на странице с именем / consult. Когда я открываю брандмауэр для обоих портов и заставляю их обмениваться данными через свои IP-адреса и адреса портов, они работают нормально. Но с Nginx между ними это не удается, потому что Nginx предотвращает отправку cookie из внешнего интерфейса в серверный. В целях разработки я закодировал серверную часть так, чтобы она вызывала панику и прерывание из-за ошибки карты nil, если она не получает cookie от передней части, что теперь и происходит. Я не могу понять, что мешает получению cookie серверной частью.

Обратные прокси Nginx от http://frontend.mydomain.me к http: // локальный: 8080. На основе документации Nginx и других онлайн-решений conf:

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;
}
}

Как упоминалось в моих комментариях выше в коде, с обновлением соединения в Nginx conf или без него, http по-прежнему обновляется до ws, как видно из заголовков ответов (отображается на вкладке «Сеть» в инструментах разработчика как в Chrome, так и в Firefox):

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

Я не знаю, указывает ли это на то, что нет необходимости обновлять соединение с помощью Nginx, и имеет ли это какое-либо отношение к проблеме.

Я пробовал добавить их в конфигурацию Nginx, но они не имели никакого значения:

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

Интерфейс Angular устанавливает cookie (без HttpOnly или Secure) в момент, когда клиент переходит на целевую страницу:

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

Угловой код переднего конца, который открывает соединение сокета с серверной частью для http://frontend.mydomain.me/consultation:

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

Серверная часть Golang обслуживается напрямую по адресу MY_IP: 12345, без Nginx. Вот код соответствующей страницы по адресу / консультация, он содержит бит, запрашивающий cookie из внешнего интерфейса:

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
    }
   ...
}

Когда я открываю оба порта 12345 и 8080, и передняя и задняя части взаимодействуют между ними напрямую, cookie успешно отправляется и принимается, как видно в этих заголовках запросов, сброшенных httputil.DumpRequest() в коде Golang выше:

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

Но с Nginx он может быть отправлен, но не получен. Я не уверен:

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

Я все еще что-то упускаю. Что бы это могло быть?

Насколько я могу судить, мой код Nginx правильный. Он основан на документации Nginx, а также на решениях других людей.

Обновление 27 марта 2019 года: как просили в комментариях, вот какие результаты 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;

Оказывается, мы не можем просто передавать файлы cookie между разными доменами и поддоменами.

В моем случае я попытался передать его из frontend.mydomain.me в MY_IP: 12345. На самом деле я также пытался передать его с frontend.mydomain.me на backend.mydomain.me. Ничего из этого не сработало, потому что они находятся в разных доменах.

это прояснил это немного больше для меня.

В конце концов, я настроил Nginx для обслуживания внешнего интерфейса на projectname.mydomain.me/frontend и backend на projectname.mydomain.me/backend. Это сработало, потому что серверная и внешняя части теперь находятся в одном домене, projectname.mydomain.me.

Спасибо Майклу Хэмптону за то, что он указал мне правильное направление в комментариях.