Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SSL Pass-Through in Nginx Reverse proxy?

Tags:

nginx

Is it possible to use Nginx reverse proxy with SSL Pass-through so that it can pass request to a server who require certificate authentication for client.

It means server will need to have certificate of client server and will not need certificate of Nginx reverse proxy server.

like image 507
Samit Jain Avatar asked Jul 14 '16 10:07

Samit Jain


People also ask

What is Nginx SSL passthrough?

With Secure Sockets Layer (SSL) passthrough, encrypted traffic from clients is passed on to web servers without undergoing decryption in a load balancer or proxy server located between client and server. Instead, data packets are decrypted directly on the web server.

What is SSL passthrough?

SSL passthrough feature allows you to pass incoming security sockets layer (SSL) requests directly to a server for decryption rather than decrypting the request using a load balancer. SSL passthrough is widely used for web application security and it uses the TCP mode to pass encrypted data to servers.


2 Answers

Not sure how much it can work in your situation, but newer (1.9.3+) versions of Nginx can pass (encrypted) TLS packets directly to an upstream server, using the stream block :

stream {
  server {
    listen     443;
    proxy_pass backend.example.com:443;
  }
}

If you want to target multiple upstream servers, distinguished by their hostnames, this is possible by using the nginx modules ngx_stream_ssl_preread and ngx_stream_map. The concept behind this is TLS Server Name Indication. Dave T. outlines a solution nicely. See his answer on this network.

like image 158
F.X. Avatar answered Sep 20 '22 19:09

F.X.


From the moment that we want to do ssl pass-through, the ssl termination will take place to the backend nginx server. Also i haven't seen an answer that takes care of the http connections as well.

The optimal solution will be a Nginx that is acting as a Layer 7 + Layer4 proxy at the same time. Something else that is rarely a subject of discussion is the IP Address redirection. When we use a proxy, this must be configured on the proxy, and not to the backend server like usually.

Lastly, the client ip address must be preserved, hence we must use the proxy protocol to do this correctly. Sounds confusing? It's not much.

I came up with a solution that i currently using in production is works flawlessly.

worker_processes  1;
error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;
events {
    worker_connections  1024;
}
http {
  variables_hash_bucket_size 1024;
  variables_hash_max_size 1024;
  map_hash_max_size 1024;
  map_hash_bucket_size 512;
  types_hash_bucket_size 512;
  server_names_hash_bucket_size 512;
  sendfile    on;
  tcp_nodelay on;
  tcp_nopush  on;
  autoindex off;
  server_tokens off;
  keepalive_timeout  15;
  client_max_body_size 100m;

  upstream production_server {
    server backend1:3080;
  }
  upstream staging_server {
    server backend2:3080;
  }
  upstream ip_address {
    server backend1:3080; #or backend2:3080 depending on your preference.
  }
  server {
    server_name server1.tld;
    listen 80;
    listen [::]:80;
    location / {
      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_set_header Host $http_host;
      proxy_set_header X-Forwarded-Host $server_name;
      proxy_set_header Connection "";
      #add_header       X-Upstream $upstream_addr;
      proxy_redirect     off;
      proxy_connect_timeout  300;
      proxy_http_version 1.1;
      proxy_buffers 16 16k;
      proxy_buffer_size 64k;
      proxy_cache_background_update on;
      proxy_pass http://production_server$request_uri;
    }
  }
  server {
    server_name server2.tld;
    listen 80;
    listen [::]:80;
    location / {
      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_set_header Host $http_host;
      proxy_set_header X-Forwarded-Host $server_name;
      proxy_set_header Connection "";
      #add_header       X-Upstream $upstream_addr;
      proxy_redirect     off;
      proxy_connect_timeout  300;
      proxy_http_version 1.1;
      proxy_buffers 16 16k;
      proxy_buffer_size 16k;
      proxy_cache_background_update on;
      proxy_pass http://staging_server$request_uri;
    }
  }
  server {
    server_name 192.168.1.1; #replace with your own main ip address
    listen 80;
    listen [::]:80;
    location / {
      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_set_header Host $http_host;
      proxy_set_header X-Forwarded-Host $server_name;
      proxy_set_header Connection "";
      #add_header       X-Upstream $upstream_addr;
      proxy_redirect     off;
      proxy_connect_timeout  300;
      proxy_http_version 1.1;
      proxy_buffers 16 16k;
      proxy_buffer_size 16k;
      proxy_cache_background_update on;
      proxy_pass http://ip_address$request_uri;
    }
  }
}
stream {
map $ssl_preread_server_name $domain {
    server1.tld  production_server_https;
    server2.tld  staging_server_https;
    192.168.1.1    ip_address_https;
    default staging_server_https;  
   }
  upstream production_server_https {
    server backend1:3443;
  }
  upstream staging_server_https {
    server backend2:3443;
  }
  upstream ip_address_https {
    server backend1:3443;
  }

server {
  ssl_preread on; 
  proxy_protocol on;
  tcp_nodelay on;
  listen 443;
  listen [::]:443;
  proxy_pass $domain;
}
  log_format proxy '$protocol $status $bytes_sent $bytes_received $session_time';
  access_log  /var/log/nginx/access.log proxy;
  error_log /var/log/nginx/error.log debug;
}

Now the only thing is yet to be done is to enable proxy protocol to the backend servers. The example below will get you going:

server {
    real_ip_header proxy_protocol;
    set_real_ip_from proxy;
    server_name www.server1.tld;
    listen 3080;
    listen 3443 ssl http2;
    listen [::]:3080;
    listen [::]:3443 ssl http2;
    include ssl_config;
    # Non-www redirect
    return 301 https://server1.tld$request_uri;
}
server {
    real_ip_header proxy_protocol; 
    set_real_ip_from 1.2.3.4; # <--- proxy ip address, or proxy container hostname for docker
    server_name server1.tld;
    listen 3443 ssl http2 proxy_protocol; #<--- proxy protocol to the listen directive
    listen [::]:3443 ssl http2 proxy_protocol; # <--- proxy protocol to the listen directive
    root /var/www/html;
    charset UTF-8;
    include ssl_config;

    #access_log  logs/host.access.log  main;
    location ~ /.well-known/acme-challenge {
      allow all;
      root /var/www/html;
      default_type "text/plain";
    }

    location / {
    index index.php;
    try_files $uri $uri/ =404;
    }
    error_page  404    /404.php;
    # place rest of the location stuff here
}

Now everything should work like a charm.

like image 26
Skeptic Avatar answered Sep 16 '22 19:09

Skeptic