1

I use an apache2 server configured as a reverse proxy to access an internal service. To protect this service from unauthorized access I would like to use a certificate-based client authentication.

Current site configuration which works fine without client authentication:

<VirtualHost api.example.com:50002>
    SSLEngine on
    SSLCertificateFile    /srv/ssl/api.example.com.crt
    SSLCertificateKeyFile /srv/ssl/api.example.com.key

    ProxyPass "/" "http://127.0.0.1:50002/"
    ProxyPassReverse "/" "http://127.0.0.1:50002/"
    ProxyPreserveHost on

    LogLevel debug
    ErrorLog  /var/log/apache2/error.log
    CustomLog /var/log/apache2/access.log combined
</VirtualHost>

In order to enable certificate-based client authentication, I add these lines to the site configuration:

SSLCACertificateFile /srv/example.com.crt
SSLVerifyClient require

This works fine but allows ANY client with a certificate signed by the referenced CA (this is too permissive). Furthermore, it requires all possible CA to be known.

I rather want to maintain a local copy of the few allowed clients certificate (some of which may be signed with a known CA and some other may be signed with an unknown CA certificate). The only thing that matters is that the certificate is in the "allow" list.

1 Answer 1

1

This solution uses a basic authentication provider which relies on a .htpasswd file containing all allowed certificates.

<VirtualHost api.example.com:50002>
    SSLEngine on
    SSLCertificateFile    /srv/ssl/api.example.com.crt
    SSLCertificateKeyFile /srv/ssl/api.example.com.key

    ProxyPass "/" "http://127.0.0.1:50002/"
    ProxyPassReverse "/" "http://127.0.0.1:50002/"
    ProxyPreserveHost on

    <Location "/">
        SSLVerifyClient   optional_no_ca
        SSLOptions        +FakeBasicAuth +StrictRequire
        SSLRequireSSL

        AuthName          "Client Certificate Authorization Check"
        AuthType          Basic
        AuthBasicProvider file
        AuthUserFile      /srv/client-certs/allowed.htpasswd

        Require           valid-user
    </Location>

    LogLevel debug
    ErrorLog  /var/log/apache2/error.log
    CustomLog /var/log/apache2/access.log combined
</VirtualHost>

NOTE: The +FakeBasicAuth flag in the SSLOptions tells Apache to give the received client certificate's Subject Distinguished Name (DN) to the authentication provider.

NOTE: The SSLVerifyClient option is set to optional_no_ca in order to allow clients signed by unknown CA. However it should rather be set to required in order to ensure no client has a "faked" username which matches an entry in the .htpasswd.

The file allowed.htpasswd contains the following:

/C=FR/O=ExampleLtd/CN=slave-api-client:xxj31ZMTZzkVA

NOTE: According to the mod_ssl documentation: "Every entry in the user file needs this password: xxj31ZMTZzkVA".

EXTRA: A simple bash script to automatically build the .htpasswd file given a list of client certificates:

# destination file
HTPASSWD_FILE=./allowed.htpasswd
touch ${HTPASSWD_FILE}
truncate --size=0 ${HTPASSWD_FILE}

# default password is "password"
PASSWORD_HASH=$(openssl passwd -crypt -salt xx password)

# extract certificates' Subject Distinguished Name (DN)
CLIENT_CERTIFICATE_FILES=$(find ./allowed -type f)
for fcert in ${CLIENT_CERTIFICATE_FILES};
do
    entry=$(openssl x509 -in ${fcert} -noout -subject \
            | sed 's|subject=|/|' \
            | sed 's| = |=|g' \
            | sed 's|, |/|g')
    echo "${entry}:${PASSWORD_HASH}" >> ${HTPASSWD_FILE}
done

Resource links:

2
  • 1
    Assuming OpenSSL 1.1.0 up (as you apparently already are) you can replace that entire $( ) with openssl x509 -in $fcert -noout -subject | sed -e 's|^subject=||' -e 's| = |=|g' -e 's|, |/|g'. However in 3.0 up (the only versions now supported upstream) passwd -crypt has been removed. Commented Jun 13 at 23:35
  • @dave_thompson_085 Thanks for the feedback, I just edited the command line with your suggested improvements. Is there any replacement for passwd -crypt in 3.0 up?
    – Jib
    Commented Jun 14 at 10:56

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .