Using helm to deploy htpasswd secret

Viewed 243

I want to deploy nginx with basic auth via: https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/

I'm in the process of using helm to generate a htpasswd hash, I want to use the following template function htpasswd: https://helm.sh/docs/chart_template_guide/function_list/#cryptographic-and-security-functions

My template is as follow:

{{- $passwd := (htpasswd "foo" "bar") -}}

apiVersion: v1
kind: Secret
metadata:
  name: htpasswd
  namespace: default
type: Opaque
stringData:
    htpasswd: {{ $passwd }}

However when I try to authenticate via nginx i get the following error:

*13 crypt_r() failed (22: Invalid argument)

Does this mean nginx doesn't support bcrypt hash password? Is there a way around this so I can still use helm templates. Can i provide a custom htpasswd function?

1 Answers

Nginx does not have an internal implementation of the bcrypt algorithm. Actually, the only one algorithm implemented by nginx itself is the apache apr1 MD5 version (read this nginx trac ticket), for everything else it relies on a system libc library crypt implementation. Only a few Linux distributions shipped with the tweaked glibc version that has bcrypt support; Alpine Linux is one of the examples. You can check this issue of docker-nginx image, especially this comment, or this one of the ingress-nginx.

Unfortunately I don't have an answer is there a way to provide a custom crypt function using helm. I think helm developers should consider to add at least SHA-256/SHA-512 hashing functions. Too sad there is so many software thinking the world of web servers is limited to apache.

TL;DR

The default POSIX crypt function implementation uses a 56 bit DES, which is definitely too week nowadays and not something anybody should rely on. However GNU glibc library implements some extensions:

The glibc version of this function supports additional encryption algorithms.

If salt is a character string starting with the characters $id$ followed by a string optionally terminated by $, then the result has the form:

$id$salt$encrypted

id identifies the encryption method used instead of DES and this then determines how the rest of the password string is interpreted. The following values of id are supported:

ID Method
1 MD5
2a Blowfish (not in mainline glibc; added in some Linux distributions)
5 SHA-256 (since glibc 2.7)
6 SHA-512 (since glibc 2.7)

Thus, $5$salt$encrypted and $6$salt$encrypted contain the password encrypted with, respectively, functions based on SHA-256 and SHA-512.

salt stands for the up to 16 characters following $id$ in the salt. The encrypted part of the password string is the actual computed password. The size of this string is fixed:

Hash function Length
MD5 22 characters
SHA-256 43 characters
SHA-512 86 characters

The characters in salt and encrypted are drawn from the set [a-zA-Z0-9./]. In the MD5 and SHA implementations the entire key is significant (instead of only the first 8 bytes in DES).

Since glibc 2.7, the SHA-256 and SHA-512 implementations support a user-supplied number of hashing rounds, defaulting to 5000. If the $id$ characters in the salt are followed by rounds=xxx$, where xxx is an integer, then the result has the form

$id$rounds=yyy$salt$encrypted

where yyy is the number of hashing rounds actually used. The number of rounds actually used is 1000 if xxx is less than 1000, 999999999 if xxx is greater than 999999999, and is equal to xxx otherwise.

So, why the glibc doesn't have a Blowfish (bcrypt) support by default? There is a long read from Red Hat. In short, bcrypt considered to have no benefits over SHA-256 or SHA-512 functions using enough hashing rounds. I'm not a cryptographic expert, nevertheless the article seems reasonable to me. For my own purposes, to not be stucked with the weak apache MD5 1000 hashing rounds algorithm, I wrote a script that I'm using to generate passwords based on the SHA-512 default (5000) hashing rounds function, maybe someone will find it useful:

#!/usr/bin/python
import sys, random, string;
from getpass import getpass;
from crypt import crypt;
if len(sys.argv) > 1:
    username = sys.argv[1];
    if len(sys.argv) > 2:
        password = sys.argv[2];
    else:
        password = getpass('Enter password: ');
        confirm = getpass('Re-enter password: ');
        if password != confirm:
            sys.stderr.write('Passwords mismatch!\n');
            exit(1);
    random.seed();
    salt = ''.join(random.sample(string.ascii_letters + string.digits + './', 8));
    print('%s:%s' % (username, crypt(password, '$6$%s$' % salt)));
else:
    sys.stderr.write('Usage: htpasswd.py <username> [<password>]\n');

This one was written to be used on a CentOS 7 (shipped with Python 2.7) and may or may not require some modifications to be used with Python 3. It can be easily tweaked to use more hashing rounds replacing $6$%s$ with lets say $6$rounds=10000$%s$ (to use 10000 hashing rounds instead) and so on.

Related