Nginx: broken_header with proxy_protocol and ELB - amazon-web-services

I am trying to set up proxy_protocol in my nginx config. My server sits behind an AWS load balancer (ELB), and I have enabled Proxy Protocol on that for both ports 80 and 443.
However, this is what I get when I hit my server:
broken header: "��/��
'���\DW�Vc�A{����
�#��kj98���=5���g#32ED�</A
" while reading PROXY protocol, client: 172.31.12.223, server: 0.0.0.0:443
That is a direct copy paste from the nginx error log - wonky characters and all.
Here is a snip from my nginx config:
server {
listen 80 proxy_protocol;
set_real_ip_from 172.31.0.0/20; # Coming from ELB
real_ip_header proxy_protocol;
return 301 https://$http_host$request_uri;
}
server {
listen 443 ssl proxy_protocol;
server_name *.....com
ssl_certificate /etc/ssl/<....>;
ssl_certificate_key /etc/ssl/<....?;
ssl_prefer_server_ciphers On;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!DSS:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4;
ssl_session_cache shared:SSL:10m;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
ssl_stapling on;
ssl_stapling_verify on;
...
I can't find any help online about this issue. Other people have had broken header issues, but the error with bad headers are always readable - they don't look like they are encoded like they are for me.
Any ideas?

Two suggestions:
Verify that your ELB listener is configured to use TCP as the protocol, not HTTP. I have an LB config like the following that's routing to Nginx with proxy_protocol configured:
{
"LoadBalancerName": "my-lb",
"Listeners": [
{
"Protocol": "TCP",
"LoadBalancerPort": 80,
"InstanceProtocol": "TCP",
"InstancePort": 80
}
],
"AvailabilityZones": [
"us-east-1a",
"us-east-1b",
"us-east-1d",
"us-east-1e"
],
"SecurityGroups": [
"sg-mysg"
]
}
You mentioned that you have enabled Proxy Protocol in the ELB, so I'm assuming you've followed AWS setup steps. If so then the ELB should be crafting the HTTP request correctly with the first line as something like PROXY TCP4 198.51.100.22 203.0.113.7 35646 80\r\n. However if the HTTP request is not coming into Nginx with the PROXY ... line then it could cause the problem you're seeing. You could reproduce that if you hit the EC2 DNS name directly in the browser, or you ssh into the EC2 instance and try something like curl localhost, then you should see a similar broken header error in the Nginx logs.
To find out whether it works with a correctly formed HTTP request you can use telnet:
$ telnet localhost 80
PROXY TCP4 198.51.100.22 203.0.113.7 35646 80
GET /index.html HTTP/1.1
Host: your-nginx-config-server_name
Connection: Keep-Alive
Then check the Nginx logs and see if you have the same broken header error. If not then the ELB is likely not sending the properly formatted PROXY request, and I'd suggest re-doing the ELB Proxy Protocol configuration, maybe with a new LB, to verify it's set up correctly.

I had similar situation, nginx had 'proxy_protocol' on but AWS ELB settings was not on, so I got the similar message.
Solutions to edit settings to turn it on:

I had this error and came across this ticket:
https://trac.nginx.org/nginx/ticket/886
which ultimately led me to figuring out that I had an unneeded proxy_protocol declaration in my nginx.conf file. I removed that and everything was working again.
Oddly enough, everything worked fine with nginx version 1.8.0, but when I upgraded to nginx version 1.8.1 is when I started seeing the error.

I got this unreadable header issue too and here are the cause and how I fixed it.
In my case, Nginx is configured with use-proxy-protocol=true properly. It complains about the broken header solely because AWS ELB did not add the required header (e.g. PROXY TCP4 198.51.100.22 203.0.113.7 35646 80) at all. Nginx sees the encrypted HTTPS payload directly. That's why it prints out all the unreadable characters.
So, why didn't the AWS ELB add the PROXY header? It turned out I used wrong ports in the commands to enable Proxy Protocol policy. Instance ports should be used instead of 80 and 443.
The ELB has the following port mapping.
80 -> 30440
443 -> 31772
The commands should be
aws elb set-load-balancer-policies-for-backend-server \
--load-balancer-name a19235ee9945011e9ac720a6c9a49806 \
--instance-port 30440 \
--policy-names ars-ProxyProtocol-policy
aws elb set-load-balancer-policies-for-backend-server \
--load-balancer-name a19235ee9945011e9ac720a6c9a49806 \
--instance-port 31272 \
--policy-names ars-ProxyProtocol-policy
but I used 80 and 443 by mistake.
Hope this helps somebody.

Stephen Karger's solution above is correct, you must adjust make sure to configure your ELB to support proxy. Here is the AWS docs for doing exactly that: http://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/enable-proxy-protocol.html. The docs are a bit daunting at first, so if you want you can just skip to steps 3 and 4 under the Enable Proxy Protocol Using the AWS CLI section. Those are the only necessary steps for enabling the proxy channeling. Additionally, as Stephen also suggested, you must make sure that your ELB is using TCP instead of http or https, as both of these will not behave properly with ELB's proxy implementation. I suggest moving your socket channel away from common ports like 80 and 443, just so you can still maintain those standardized connections with their default behavior. Of course, making that call is entirely dependent on how your app stack looks.
If it helps, you can use the npm package wscat to debug your websocket connections like so:
$ npm install -g wscat
$ wscat --connect 127.0.0.1
If the connection works in local, then it is for sure your load balancer. However, if it doesn't, there is almost definitely a problem with your socket host.
Additionally, a tool like nmap will aid you in discovering open ports. A nice checklist for debugging:
npm install -g wscat
# can you connect to it from within the server?
ssh ubuntu#69.69.69.69
wscat -c 127.0.0.1:80
# can you connect to it from outside the server?
exit
wscat -c 69.69.69.69:80
# if not, is your socket port open for business?
nmap 69.69.69.69:80
You can also use nmap from within your server to discover open ports. to install nmap on ubuntu, simply sudo apt-get install nmap. on osx, brew install nmap
Here is a working config that i have, although it does not provide ssl support at the moment. In this configuration, I have port 80 feeding my rails app, port 81 feeding a socket connection through my elb, and port 82 open for internal socket connections. Hope this helps somebody!! Anybody using Rails, unicorn, and Faye to deploy should find this helpful. :) happy hacking!
# sets up deployed ruby on rails server
upstream unicorn {
server unix:/path/to/unicorn/unicorn.sock fail_timeout=0;
}
# sets up Faye socket
upstream rack_upstream {
server 127.0.0.1:9292;
}
# sets port 80 to proxy to rails app
server {
listen 80 default_server;
keepalive_timeout 300;
client_max_body_size 4G;
root /path/to/rails/public;
try_files $uri/index.html $uri.html $uri #unicorn;
location #unicorn {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded_Proto $scheme;
proxy_redirect off;
proxy_pass http://unicorn;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
error_page 500 502 503 504 /500.html;
location = /500.html {
root /path/to/rails/public;
}
}
# open 81 to load balancers (external socket connection)
server {
listen 81 proxy_protocol;
server_name _;
charset UTF-8;
location / {
proxy_pass http://rack_upstream;
proxy_redirect off;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
# open 82 to internal network (internal socket connections)
server {
listen 82;
server_name _;
charset UTF-8;
location / {
proxy_pass http://rack_upstream;
proxy_redirect off;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}

Related

NICE DCV behind Nginx reverse proxy, "Failed to communicate with server."

I have Nginx running on an Amazon Linux EC2 instance. It is listening for connections to https://dcv01.example.com and then using proxy_pass with a wildcard cert to serve the DCV client from an internal IP address. The page displays properly with no SSL errors. However, instead of the login prompt, I get a red message that says "Failed to communicate with server.".
I attempted to use the allowed-http-host-regex with the value ^(www\.)?dcv01\.example\.com$, but then I get a 403. I also tried allowed-ws-origin-regex, but it does nothing different (still get Failed to communicate...)
What do I need to do to get DCV working behind a reverse proxy?
Here is the server block in my Nginx config:
server {
listen 443 ssl;
server_name dcv01.example.com;
include wildcard.conf; //just has ssl_certificate and ssl_certificate_key directives
location / {
proxy_redirect off;
proxy_pass $scheme://10.0.10.131:8443;
}
}

Is Django 500 error Invalid HTTP_HOST header a security concern?

I have a custom Django web app sitting behind an NGINX proxy server.
I am seeing occasional errors come through from my Django server with messages like
Invalid HTTP_HOST header: 'my.domain.com:not-a-port-number'. The domain name provided is not valid according to RFC 1034/1035.
where the part after the colon has been a variety of wack garbage like,
my.domain.com:§port§/api/jsonws/invoke
my.domain.com:xrcr0x4a/?non-numeric-port-bypass
my.domain.com:80#+xxxxtestxxxx/
my.domain.com:80#+${12311231}{{12311231}}/
I am able to replicate a similar error using curl where the request url and the host header are different:
curl https://my.domain.com --header 'Host: my.domain.com:not-a-port-number'
I suspect that these are likely coming from our network security scanning software trying to find vulnerabilities in our custom web apps, but I am a little surprised that NGINX allows these requests to make it through to my Django server, especially if, as the 500 error output suggests, these are invalidly formatted headers.
Trying to prepare for the worst, is there anything I should change or be concerned about with this for the sake of security? Is there a best practice for this situation that I am unaware of? Should NGINX be filtering out these sorts of requests?
For my own convenience it would be nice to not to see the noise of these 500 errors coming from Django while I am on the lookout for real app level errors, but simply hiding the alerts seems to be a poor solution.
Additional Info:
I have ALLOWED_HOSTS set to 'my.domain.com' in my Django settings.py file.
NGINX configuration:
server {
listen 80 default_server;
return 444;
}
server {
listen 80;
server_name my.domain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl default_server;
ssl_certificate /path/to/cert.cabundle.pem;
ssl_certificate_key /path/to/cert.key;
return 444;
}
# App https server.
# Serve static files and pass requests for application urls to gunicorn socket.
server {
listen 443 ssl;
server_name my.domain.com;
ssl_certificate /path/to/cert.cabundle.pem;
ssl_certificate_key /path/to/cert.key;
...
location / {
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;
proxy_pass http://unix:/path/to/gunicorn.sock;
}
}
I never met such a situation on practice, however I can guess nginx select your last server block to handle the request according to the information from the SNI Client Hello extension which by some reason (malformed request from scanning software?) is different from the Host header value in the encrypted request.
If you want to filter those requests at the nginx level, you can try this:
server {
listen 443 ssl;
server_name my.domain.com;
ssl_certificate /path/to/cert.cabundle.pem;
ssl_certificate_key /path/to/cert.key;
if ($http_host != my.domain.com) {
return 444;
}
...
}
PS. For the security considerations I never use my real certificate in a stub server block like this one:
server {
listen 443 ssl default_server;
ssl_certificate /path/to/cert.cabundle.pem;
ssl_certificate_key /path/to/cert.key;
return 444;
}
Use a self-signed one as shown here. The reason if obvious - dear hacker, if you don't know what exactly domain you are scanning now, I'm not going to tell you what it is.

AWS Elastic Beanstalk with docker-compose.yml

I am trying to deploy several services using AWS Elastic Beanstalk with Docker running on Amazon Linux 2 platform.
Since, there are two services in my docker-compose.yml file:
version: '3.8'
services:
beanstalk-flask:
image: "anotheruserdocker/beanstalk-flask"
ports:
- "5000:5000"
tasks:
image: "xxxxx.dkr.ecr.us-east-1.amazonaws.com/xxx:xxx"
ports:
- "8080:8080"
I need to change nginx service configuration in order to proxy traffic to specific service.
I was following the documentation, in which it was noted that you can override the default nginx.conf with your own and in order to do it, you need to place your config file in the application source bundle, like so .platform/nginx/nginx.conf.
I have also included this include conf.d/elasticbeanstalk/*.conf; line in order to override it.
nginx.conf file:
# Elastic Beanstalk Nginx Configuration File
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;
worker_rlimit_nofile 32633;
include conf.d/elasticbeanstalk/*.conf;
upstream service_1 {
server 172.17.0.1:8080;
keepalive 256;
}
upstream serivce_2 {
server 172.17.0.1:5000;
keepalive 256;
}
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
include conf.d/*.conf;
map $http_upgrade $connection_upgrade {
default "upgrade";
}
server {
listen 80 default_server;
gzip on;
gzip_comp_level 4;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
access_log /var/log/nginx/access.log main;
location / {
proxy_pass http://service_1;
proxy_http_version 1.1;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /api {
proxy_pass http://service_2;
proxy_http_version 1.1;
}
# Include the Elastic Beanstalk generated locations
include conf.d/elasticbeanstalk/*.conf;
}
}
Once I'm uploading the application source bundle that looks like this:
docker-compose.yml
.platform/nginx/nginx.conf
the configuration doesn't change.
Am I missing something, is it a bug, or are there any other ways to change/modify the default nginx configuration?
Also, I have noticed that upon booting nginx.service isn't in running state, is it possible to start this service upon boot?
Thank you.
Found a possible solution.
During the creation of AWS Elastic Beanstalk environment (if you are using Load Balanced deployment type), you can add processes which Load Balancer will register(?).
Once I've added the processes (that run on 8080 and 5000 ports), I created additional listener for the Application Load Balancer that listens to traffic on port 5000 (I only did this for this port, because by default AWS Elastic Beanstalk environment creates a listener that forwards traffic to the target group of EC2 instance that was running on the specified 8080 port) and forwards it to the target group of the process that runs it on this port.
After doing these steps it worked.
Interestingly enough, I don't really know how this worked, I've connected to the EC2 instance and noticed that nginx.service was in inactive state.
Probably I don't understand clearly how this works behind the scenes, any clarifications would be much appreciated.
Thank you!
P.S.: Once I get enough reputation points, I'll attach some screenshots of the steps taken.
The Nginx service is not configured at all. With AWS Elastic Beanstalk with Docker running on Amazon Linux 2 + docker compose they assume you run Nginx in a container.
It's documented here: https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create_deploy_docker.container.console.html#docker-software-config

Django & Certbot - unauthorized, Invalid response (HTTPS)

I'm trying to configure Certbot (Letsencrypt) with Nginx.
I get this error :
- The following errors were reported by the server:
Domain: koomancomputing.com
Type: unauthorized
Detail: Invalid response from
http://koomancomputing.com/.well-known/acme-challenge/xvDuo8MqaKvUhdDMjE3FFbnP1fqbp9R66ah5_uLdaZk
[2600:3c03::f03c:92ff:fefb:794b]: "<html>\r\n<head><title>404 Not
Found</title></head>\r\n<body bgcolor=\"white\">\r\n<center><h1>404
Not Found</h1></center>\r\n<hr><center>"
Domain: www.koomancomputing.com
Type: unauthorized
Detail: Invalid response from
http://www.koomancomputing.com/.well-known/acme-challenge/T8GQaufb9qhKIRAva-_3IPfdu6qsDeN5wQPafS0mKNA
[2600:3c03::f03c:92ff:fefb:794b]: "<html>\r\n<head><title>404 Not
Found</title></head>\r\n<body bgcolor=\"white\">\r\n<center><h1>404
Not Found</h1></center>\r\n<hr><center>"
To fix these errors, please make sure that your domain name was
entered correctly and the DNS A/AAAA record(s) for that domain
contain(s) the right IP address.
- Your account credentials have been saved in your Certbot
configuration directory at /etc/letsencrypt. You should make a
secure backup of this folder now. This configuration directory will
also contain certificates and private keys obtained by Certbot so
making regular backups of this folder is ideal.
in /etc/nginx/sites-available/koomancomputing :
server {
listen 80;
server_name koomancomputing.com www.koomancomputing.com;
location = /favicon.ico { access_log off; log_not_found off; }
location /staticfiles/ {
root /home/kwaku/koomancomputing;
}
location /media/ {
root /home/kwaku/koomancomputing;
}
location / {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
}
my DNS A/AAAA records :
I didn't know what to do, so I did a search and find django-letsencrypt app, but I don't know hot to use :
Your domain has a proper AAAA record configured to your server over IPv6, and certbot chose that to validate your server.
However, your server block as configured under nginx only listens to port 80 on IPv4 for your domain. When certbot requests Let's Encrypt to access your challenge and issue a certificate, nginx isn't configured to properly respond with the challenge on IPv6. It often in this case returns other things (such as a 404 in your case, or a default site).
You can resolve this by modifying the first two lines to also listen on all IPv6 addresses for your server:
server {
listen 80;
listen [::]:80;
# other configuration
}
After editing, restart nginx and run certbot again.
Your Nginx server is responding with a 404 error because it does not define a route to /.well-known needed by certbot to verify challenges. You need to modify the Nginx config file to tell it how to respond to certbot's challenges.
Certbot can update the Nginx config file for you.
First, make sure your config file is enabled. Run sudo service nginx reload and check for the presence of a file called /etc/nginx/sites-enabled/koomancomputing.
Then, run certbot --nginx -d koomancomputing.com -d www.koomancomputing.com
The --nginx flag tells certbot to find an Nginx config file with a matching server name and update that file with SSL info.
server {
listen 80;
listen [::]:80;
# other configuration
}
Works for both IPV4 and IPV6 after adding this restart nginx.
For me, it worked after I removed and installed the latest certbot version using snapd.
I use cloudflare proxy option and it failed for certbot 0.31.0.
After installing certbot 1.27 and configuring the cert newly, it works fine even proxy toggle is on in cloudflare.

nginx can't listen on port 80

I'm trying to set nginx with gunicorn but I keep getting the "Welcome to nginx!" page. I am able to successfully listen to other ports (like 8080) but port 80 does not work at all.
server {
listen 80;
server_name host.ca www.host.ca;
access_log /var/log/nginx/example2.log;
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://127.0.0.1:8000;
}
}
I'm running the server as root.
I can't seem to see anything running in port 80.
Diagnosing the Problem
Make sure to check your logs (likely /var/log/nginx or some variant).
Check to see what might be hogging port 80
netstat -nlp | grep 80
Sites-enabled, port hogging
Then, make sure you have the Django site enabled in sites-enabled. Delete any old symlinks if you created one first.
rm /etc/nginx/sites-enabled/django
ln -s /etc/nginx/sites-available/django /etc/nginx/sites-enabled/django
Double check your /etc/nginx/nginx.conf to make sure it's loading sites-enabled and not loading some other default.
http {
...
include /etc/nginx/sites-enabled/*;
}
After you do all this, shut down and restart the nginx service.
Either service nginx restart or service nginx stop && service nginx start