Nginx: Mutual (Two way) SSL authentication for upstream HTTPS servers



Nginx  is a really good,  high performance reverse proxy server which supports Mutual Authentication  for incoming requests but doesn't support for upstream/backend servers.  In most of the deployments where nginx is used as a reverse proxy, it also acts as a SSL termination point where upstream requests are routed using either non SSL or one-way SSL connections.

Recently, I was working on a prototype to develop Api Gateway + reverse proxy which manages SSL certificates for different upstream backends and route incoming requests based on the routing rules. Some of the upstream backends expect to have their own certificates with mutual authentication.

After spending sometime on google search and seeing questions posted on stack overflow,  I realized that nginx doesn't support mutual/two-way authentications for upstreams so decided to implement support.  

I modified ngx_http_proxy_module.c to add following two new config parameters which allows to specify client certificate pem and client certificate key files. I have submitted my patch to nginx development community here

NOTE: Make sure you do not populate proxy_ssl_trusted_certificate.  As it is supported by nginx for upstream one way ssl, it takes preference over my config parameters.
 proxy_ssl_client_certificate  
 proxy_ssl_client_certificate_key  

1. Apply below patch to your Nginx source code and recompile.  I have verified this patch against nginx-1.4.7 
Download patch here

2.  Configure your nginx.conf as below.  See the comments for more details.

location /  
      {  
       default_type application/json;  
   
       #this enables client verification  
       proxy_ssl_verify on;  
       proxy_ssl_verify_depth 3;  
   
       #client certificate for upstream server  
       proxy_ssl_client_certificate /etc/upstream-a.pem;  
        
       #client key generated from upstream cert  
       proxy_ssl_client_certificate_key /etc/upstream-a.key;  
        
       #configure based on your security requirement  
       proxy_ssl_ciphers ALL;  
       proxy_ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;  
   
       #make sure to match the ssl server name from upstream server certificate   
       proxy_ssl_name "abc.company.com;  
       proxy_pass https://abc.company.com:9900;  
    }  

Hope this help.  Let me know if you have any questions.

Go vs Rust : Productivity vs Performance

Recently, I have been spending some time learning both Go and Rust languages and really excited about these languages evolving differently to solve different problems.

 I think Rust will attract developers from C, C++, Fortran and will be used for developing high performance systems like gaming, browsers, telco servers, distributed computing systems as well as low level, cpu efficient embedded/micro computers.

Go seems to be for the Python, Ruby and Java developers and will be used for enterprise applications, mobile apps and application servers.

With my 10+ years of experience in C++ writing applications for telecom service providers where latency and throughput is very important,  I really like Rust which simplifies C++ , eliminates memory corruption, improves compile time significantly and being claimed as blazing fast.


Today I came across Computer Language Benchmark comparing Rust and Go from one of the blog I was reading. These are microbenchmark and gives rough idea about how these languages perform for specific algorithm implementation.


Program Source CodeCPU secsElapsed secsMemory KBCode B≈ CPU Load
 binary-trees 
Rust22.055.97228,196788  96% 87% 95% 93%
Go68.4118.42266,624814  94% 94% 92% 93%
 pidigits 
Rust1.731.735,3081297  1% 100% 0% 0%
Go3.763.523,668674  4% 48% 7% 51%
 spectral-norm 
Rust7.872.065,1121020  95% 96% 96% 96%
Go15.703.961,816536  99% 99% 99% 99%
 reverse-complement 
Rust0.740.49257,1602015  12% 82% 41% 18%
Go0.930.77250,5641243  10% 69% 11% 35%
 fasta 
Rust4.995.004,8121224  1% 0% 100% 0%
Go7.267.271,0281036  1% 1% 1% 100%
 regex-dna 
Rust35.2611.95228,296763  65% 66% 87% 78%
Go47.4016.27543,868789  86% 64% 64% 78%
 fannkuch-redux 
Rust50.0112.817,0721180  99% 100% 97% 95%
Go67.1616.861,032900  100% 100% 100% 100%
 mandelbrot 
Rust20.145.0956,8241290  98% 99% 100% 99%
Go25.446.3932,276894  100% 100% 100% 100%
 n-body 
Rust20.9920.994,8241371  1% 0% 0% 100%
Go22.9522.951,0361310  0% 0% 100% 1%
 k-nucleotide 
Rust26.069.76152,5362113  42% 83% 43% 99%
Go30.938.42251,0241399  98% 91% 90% 90%

To get the comparative data across all these algorithms,   I calculated average for combined these algorithms for both the languages.

Results:  Average Elapsed seconds and code written for each language:


Language CPU (Elapsed seconds) Code (B)
Rust 7.585 1306.1
Go 10.483 959.5


These microbenchmarks shows Rust is ~30+% faster than Go but has ~30+% more code.


Given the computers are getting faster and cheaper but software becoming more complex and maintenance is expensive, I would use Go for an enterprise application.

Which language would you choose and for what kinds of applications?  



Fibonacci(50): Rust slower than Go ?

I my previous blog post "Fibonnaci (50) : Java > C > C++ > D > Go > Terra (Lua) > LuaJit (Lua) ",  I compared language performance using Fibonnaci algorithm where Rust language was not included.

Rust language is being clamined to  be "a system programming language that runs blazingly fast, prevents almost all crashes*, and eliminates data races".

I thought of running same fibonnaci algorithm against Rust to see how blazingly fast is it.

You can find fib.rs source code here on lang-compare  on my github.  I compared it against fib.go and fib.c 

FIBONACCI - 50
Language C (gcc-4.9.1):
gcc-4.9 -O3 fib.c
/usr/bin/time -lp ./a.out 50
LANGUAGE C: 12586269025
real        52.87
user        52.83
sys          0.01
    557056  maximum resident set size
         0  average shared memory size
         0  average unshared data size
         0  average unshared stack size
       163  page reclaims
         0  page faults
         0  swaps
         0  block input operations
         0  block output operations
         0  messages sent
         0  messages received
         0  signals received
         0  voluntary context switches
       497  involuntary context switches

Language Go (go1.4beta1 darwin/amd64):
go build fib.go
/usr/bin/time -lp ./fib 50
LANGUAGE GO: 12586269025
real        96.56
user        96.47
sys          0.06
   1232896  maximum resident set size
         0  average shared memory size
         0  average unshared data size
         0  average unshared stack size
       300  page reclaims
         0  page faults
         0  swaps
         0  block input operations
         0  block output operations
         0  messages sent
         0  messages received
         0  signals received
      9010  voluntary context switches
      1338  involuntary context switches

Language Rust (0.13.0-nightly (45cbdec41 2014-11-07 00:02:18 +0000)):
/usr/local/bin/rustc --opt-level 3 fib.rs
/usr/bin/time -lp ./fib 50
LANGUAGE Rust: 12586269025
real       101.53
user       101.44
sys          0.01
   1040384  maximum resident set size
         0  average shared memory size
         0  average unshared data size
         0  average unshared stack size
       229  page reclaims
        46  page faults
         0  swaps
         0  block input operations
         0  block output operations
         0  messages sent
         0  messages received
         0  signals received
         1  voluntary context switches
       914  involuntary context switches

Surprisingly results are as below where CPU usage (real) is as C (52.87) > Go (96.56)  > Rust (101.53).

Language CPU(real) Memory(Max resident size)
C 52.87 544K
Go 96.56 1204K
Rust 101.53 1016K




NOTE:  Is there a better way to read command line argument and convert into integer?   I felt it was very painful to figure that out.

E.g.
In C language:
long n = atol(argv[1]);

In Go language:
n, _ := strconv.Atoi(os.Args[1])

In Rust language:
let args = os::args(); let args = args.as_slice(); let n :u64 = from_str::<u64>(args[1].as_slice()).unwrap();