We used the nginx as reverse proxy and the gitblit as backend with the client certificate authentication.

Given the nginx configuration as below.

server {
listen       443 ssl http2 default_server;
server_name  _;

charset utf-8;

ssl_certificate      D:\cert.pem;
ssl_certificate_key  D:\key.pem;

ssl_session_cache    off;
ssl_session_timeout  1h;
ssl_buffer_size 8192;

ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers  HIGH:!aNULL:!MD5;

ssl_client_certificate D:\Program\GitBlit\gitblit-1.8.0\data\certs\ca.pem;
ssl_verify_client optional;

ssl_prefer_server_ciphers  on;
proxy_intercept_errors on;          

location /git {
    proxy_pass https://127.0.0.1:1111;
    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_ssl_verify off;
    proxy_ssl_name $host;
    proxy_ssl_server_name on;
    proxy_ssl_session_reuse off;
}
}

The ssl_verify_client optional; syntax has the web client requires the client certificate when navigating to https://my_server/git. But I got 502 bad gateway error. There is no any error log being seen fro gitblti. The nginx error log as below.

2023/10/25 14:48:14 [error] 29404#2072: *43 peer closed connection in SSL handshake while SSL handshaking to upstream, client: 127.0.0.1, server: _, request: "GET /git/ HTTP/2.0", upstream: "https://127.0.0.1:1111/git/", host: "127.0.0.1"

I have make sure that if I navigate to the https://127.0.0.1:1111/git and provide the same client certificate, the authentication works well. The gitblit SSL certificate is signed to my_server by a self-signed CA(The D:\Program\GitBlit\gitblit-1.8.0\data\certs\ca.pem for the ssl_client_certificate syntax).

Is this cause by the nginx failed to recognize the SSL certificate from gitblit or failed to pass the client certificate for the authentication?

1

There are 1 answers

1
Florian On BEST ANSWER

The answer requires a little guesswork. I am assuming that your Gitblit installation is set up to require clients to authenticate with client certificates over HTTPS. (server.requireClientCertificates = required)

According to the configuration you posted, your Nginx proxy does also request client certificates, although optionally. This means that the client, which has a TLS client certificate issued from the CA you specified, will present this to the Nginx server. The server validates the certificate and then redirects the request to the proxied server.

But the Nginx proxy does not pass on the client's certificate to the proxied server. It terminated the TLS connection from the client and will create a new one towards the proxied server, since you specified an HTTPS URL for the proxied server. This is a new connection with the proxy being the TLS client, not the original client anymore. Therefore this connection will not have a client certificate that the proxied server would accept, which makes it reject the connection.


A solution to your problem depends on what you want to achieve with the setup you have. So I am not able to provide the specific correct answer without knowing the details. I can offer one possible answer, based on guessing what you want to achieve.

You proxy to 127.0.0.1, i.e. localhost. So I am guessing that the Gitblit server is not accessible from external connections, only from within the same server. If that is the case (which you need to make sure by binding to the localhost interface), you could set up a scenario in which the Nginx proxy validates the client certificate. If successful, it sets a HTTP header which the Gitblit server can evaluate and log in the user.

Gitblit offers a HTTP headers authentication provider. This is only to be used for certain scenarios where there is no direct access to the Gitblit server, only through a trusted frontend.

Set Gitblit to not use client certificates. (You could actually also use HTTP between the proxy and Gitblit, since it is only on the local server and you need to make sure that there is no way for anyone to directly access the Gitblit server.) Nginx will take care of client certificate validation.

Enable the HTTP Header Authentication Provider and define a header which will carry the user name. When a request comes in with that header and the value is a valid user name, the user is logged in.

realm.authenticationProviders = httpheader
realm.httpheader.userheader = GitblitTlsClientSDN

On Nginx require client certificates to make sure a valid certificate is presented. Extract the user name from the certificates subject DN and set it in the header that you defined in Gitblit to carry the user name.

For example, let's assume that the subject DN in the client certificate is like so CN=Joe,OU=Gitblit,O=Gibtlit,L=Seattle,ST=WA=C=US.

# Map client certificate DN to user name

map $ssl_client_s_dn $gb_username {
    default                                        "";
    "~^CN=(?<cn>[^,]+),OU=Gitblit,O=Gitblit,.*$"  $cn;
}


server {
    listen       443 ssl http2 default_server;
    server_name  _;

    charset utf-8;

    ssl_certificate      D:\cert.pem;
    ssl_certificate_key  D:\key.pem;

    ssl_session_cache    off;
    ssl_session_timeout  1h;
    ssl_buffer_size 8192;

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_ciphers  HIGH:!aNULL:!MD5;

    ssl_client_certificate D:\Program\GitBlit\gitblit-1.8.0\data\certs\ca.pem;
    ssl_verify_client on;

    ssl_prefer_server_ciphers  on;
    proxy_intercept_errors on;          

    location /git {
        proxy_pass https://127.0.0.1:1111;
        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_set_header GitblitTlsClientSDN $gb_username;

        proxy_ssl_verify off;
        proxy_ssl_name $host;
        proxy_ssl_server_name on;
        proxy_ssl_session_reuse off;
    }
}

You also need to take precautions that the Nginx proxy does not allow the client to smuggle the header you use to transport the user name through the proxy.