vikas027
vikas027

Reputation: 5782

Verify if nginx is working correctly with Proxy Protocol locally

Environment

I have set up Proxy Protocol support on an AWS classic load balancer as shown here which redirects traffic to backend nginx (configured with ModSecurity) instances.

Everything works great and I can hit my websites from the open internet.

Now, since my nginx configuration is done in AWS User Data, I want to do some checks before the instance starts serving traffic which is achievable through AWS Lifecycle hooks.

Problem

Before enabling proxy protocol I used to check whether my nginx instance is healthy, and ModSecurity is working by checking a 403 response from this command

$ curl -ks "https://localhost/foo?username=1'%20or%20'1'%20=%20'"

After enabling Proxy Protocol, I can't do this anymore as the command fails with below error which is expected as per this link.

# curl -k https://localhost -v
* About to connect() to localhost port 443 (#0)
*   Trying ::1...
* Connected to localhost (::1) port 443 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
* NSS error -5938 (PR_END_OF_FILE_ERROR)
* Encountered end of file
* Closing connection 0
curl: (35) Encountered end of file

# cat /var/logs/nginx/error.log
2017/10/26 07:53:08 [error] 45#45: *5348 broken header: "���4"�U�8ۭ򫂱�u��%d�z��mRN�[e��<�,�
�+̩�    �0��/̨��98k�̪32g�5=�/<
" while reading PROXY protocol, client: 172.17.0.1, server: 0.0.0.0:443

What other options do I have to programmatically check nginx apart from curl? Maybe something in some other language?

Upvotes: 9

Views: 17648

Answers (4)

tongueroo
tongueroo

Reputation: 1197

You can use the --haproxy-protocol curl option, which adds the extra proxy protocol info to the request.

curl --haproxy-protocol localhost

So:

curl -ks "https://localhost/foo?username=1'%20or%20'1'%20=%20'"

Upvotes: 17

aka
aka

Reputation: 41

Unfortunately bash version didn't work in my case, so I wrote python3 code:

#!/usr/bin/env python3

import socket
import sys

def check_status(host, port):
    '''Check app status, return True if ok'''
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.settimeout(3)
        s.connect((host, port))
        s.sendall(b'GET /status HTTP/1.1\r\nHost: api.example.com\r\nUser-Agent: curl7.0\r\nAccept: */*\r\n\r\n')
        data = s.recv(1024)
        if data.decode().endswith('OK'):
            return True
        else:
            return False
try:
    status = check_status('127.0.0.1', 80)
except:
    status = False
if status:
    sys.exit(0)
else:
    sys.exit(1)

Upvotes: 0

vikas027
vikas027

Reputation: 5782

Thanks Tarun for the detailed explanation. I discussed within the team and ended up doing creating another nginx virtual host on port 80 and using that to check ModSecurity as below.

curl "http://localhost/foo?username=1'%20or%20'1'%20=%20'"`

Upvotes: 0

Tarun Lalwani
Tarun Lalwani

Reputation: 146630

Proxy Protocol append a plain text line before the streaming anything

PROXY TCP4 127.0.0.1 127.0.0.1 0 8080

Above is an example, but this happens the very first thing. Now if I have a NGINX listening on SSL and http both using proxy_protocol then it expects to see this line first and then any other thing

So if do

$ curl localhost:81
curl: (52) Empty reply from server

And in nginx logs

web_1  | 2017/10/27 06:35:15 [error] 5#5: *2 broken header: "GET / HTTP/1.1

If I do

$ printf "PROXY TCP4 127.0.0.1 127.0.0.1 0 80\r\nGET /test/abc\r\n\r\n" | nc localhost 81
You can reach API /test/abc and args_given = ,

It works. As I am able to send the proxy protocol it accepts

Now in case of SSL if I use below

printf "PROXY TCP4 127.0.0.1 127.0.0.1 0 8080\r\nGET /test/abc\r\n\r\n" | openssl s_client -connect localhost:8080

It would still error out

web_1  | 2017/10/27 06:37:27 [error] 5#5: *1 broken header: ",(��   @_5���_'���/��ߗ

That is because the client is trying to do Handshake first instead of sending proxy protocol first then handshake

So you possible solutions are

  1. Terminate SSL on LB and then handle http on nginx with proxy_protocol and use the the nc command option I posted
  2. Add a listen 127.0.0.1:<randomlargeport> and execute your test using the same. This is still safe as you are listening to localhost only
  3. Add another SSL port and use listen 127.0.0.1:443 ssl and listen <private_ipv4>:443 ssl proxy_protocol

All solutions are in priority order as per my choice, you can make your own choice

Upvotes: 7

Related Questions