Using TLS Certificates to authenticate against Zabbix API with Nginx

We build a fair bit of software against the Zabbix API in our products. This means that we have multiple users in several Zabbix instances. Of course, this leads to identity management issues at scale.

Part of this problem is that storing usernames and passwords either in code, or configuration files, is a security nightmare that is doomed to fail.

In this post, I'll go through how to replace username & password authentication with (short lived) TLS client certificates, using Nginx, PHP, FastCGI and the Zabbix API.

Fortunately, Zabbix supports various forms of authentication:

  • Internal, using username + password
  • LDAP
  • HTTP auth

However, rather than using passwords, we want to use short-lived TLS client certificates, as managed by Caramel.

With Apache and Lighttpd, using client side TLS certificates is as simple as to enable authentication. For Apache you set FakeBasicAuth and in Lighttpd you set ssl.verifyclient.username and you're done.

Sadly, that's not the case with Nginx. There, you have to fake it.

First, you set up ssl_verify_client on for the server section of the domain that you want to authenticate with.

Then, you need to get a username, in the http section define the following variable:

    map $ssl_client_s_dn $ssl_client_s_dn_cn {
            default "should_not_happen";
            ~/CN=(?<CN>[^/]+) $CN;
    }

After that, you have to inject this authentication into your PHP process. You do this by adding the PHP_AUTH_USER into the fastcgi environment.

location ~ \.php$ {
    fastcgi_pass unix:/var/run/my.socket;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param SCRIPT_NAME $fastcgi_script_name;
    fastcgi_param PHP_AUTH_USER $ssl_client_s_dn_cn;
}

And that is enough to make PHP believe that you are using HTTP Auth. Now the only thing that's left to do is to make sure your user field in the Zabbix API login matches the CommonName of your certificate ( See the above "map" command )

We do it the simple way, and store the cert in a file matching the CN.

import os
import pyzabbix

secrets = '/my/storage/for/secrets'
public = '/my/storage/for/public'
name = 'Username'
key = os.path.join(secrets, name + ".key")
cert = os.path.join(public, name + ".key")


zapi = pyzabbix.ZabbixAPI("http://zabbixserver.example.com")
zapi.session.
zapi.session.verify = "/etc/pki/tls/certs/ca.caramel.crt"
zapi.session.cert = (cert, key)
zapi.login("http user", "Password is Ignored")

Release 3.8

Release 3.7