Skip to content

Commit

Permalink
Support storing user credentials as bcrypt hashes
Browse files Browse the repository at this point in the history
Instead of storing user credentials as plaintext passwords, support
bcrypt hashes.  This is backwards compatible in that it will only use
the hash if it's available, but will fall back to plaintext if not.
Also, during startup a log entry will be generated if there are any
users that have plaintext passwords configured.
  • Loading branch information
timewasted committed Sep 16, 2024
1 parent 68a434b commit 2207871
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 14 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ ENV HOME="/config"
EXPOSE 9443

RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends cron libio-socket-ssl-perl libmojolicious-perl curl ca-certificates xz-utils \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends cron libcrypt-bcrypt-perl libio-socket-ssl-perl libmojolicious-perl curl ca-certificates xz-utils \
&& rm -rf /var/cache/apt/archives /var/lib/apt/lists/*

RUN curl -sSL https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz -o /tmp/s6-overlay-noarch.tar.xz \
Expand Down
43 changes: 30 additions & 13 deletions acmeproxy.pl
Original file line number Diff line number Diff line change
Expand Up @@ -34,26 +34,33 @@
use Mojo::Util qw(secure_compare);
use POSIX qw(strftime);
use Cwd;
use Crypt::Bcrypt qw(bcrypt_check);
use strict;

die("$0: please install curl.\n") unless (-x `/usr/bin/which curl` =~ s/[\r\n]//r);

# acme.sh uses this log format so we're sort of stuck with it
sub logg ($in) { say strftime("[%a %b %e %I:%M:%S %p %Z %Y] ", localtime()) . $in };

write_config() unless (-f 'acmeproxy.pl.conf');
my $config = plugin 'Config' => {file => cwd().'/acmeproxy.pl.conf', format => 'perl'};

# Early sanity checks
die("acme dnslib provider not found: $config->{dns_provider}\n")
unless (-f "$acme_home/dnsapi/$config->{dns_provider}.sh");
foreach my $auth (@{$config->{auth}}) {
if (exists($auth->{pass})) {
logg "One or more users are defined with plaintext passwords. You should convert them to bcrypt hashes!";
last;
}
}

# Set environment variables from config
foreach (keys %{$config->{env}}) { $ENV{$_} = $config->{env}->{$_}; }

# acme.sh uses this log format so we're sort of stuck with it
sub logg ($in) { say strftime("[%a %b %e %I:%M:%S %p %Z %Y] ", localtime()) . $in };

# Install acme.sh if it isn't installed already
acme_install() unless (-f "$acme_home/acme.sh");

# Early sanity check
die("acme dnslib provider not found: $config->{dns_provider}\n")
unless (-f "$acme_home/dnsapi/$config->{dns_provider}.sh");

# Generate a TLS certificate for ourselves if one doesn't exist
acme_gencert($config->{hostname})
unless (-f "$acme_home/acmeproxy.pl.key" && -f "$acme_home/acmeproxy.pl.crt");
Expand Down Expand Up @@ -133,12 +140,20 @@ ($userpass, $fqdn)
}

# $userpass is in the rather odd format of "username:password". Don't look at me, it's Mojolicious.
my $user = (split(/:/, $userpass, 2))[0];
my ($user, $pass) = split(/:/, $userpass, 2);

foreach my $auth (@{$config->{auth}}) {
if (secure_compare($userpass, "$auth->{user}:$auth->{pass}") && $fqdn =~ /\.$auth->{host}\.?$/) {
logg "auth: $user successfully authenticated for $fqdn";
return 1;
my $auth_check = 0;
if (secure_compare($user, $auth->{user}) && $fqdn =~ /\.$auth->{host}\.?$/) {
if (exists($auth->{hash})) {
$auth_check = bcrypt_check($pass, $auth->{hash});
} else {
$auth_check = secure_compare($pass, $auth->{pass});
}
if ($auth_check) {
logg "auth: $user successfully authenticated for $fqdn";
return 1;
}
}
}

Expand Down Expand Up @@ -232,14 +247,16 @@ ()
# like slackbox.bob.int.example.com
{
'user' => 'bob',
'pass' => 'dobbs',
# Plain text password is: dobbs
'hash' => '$2b$12$ZkfzP1DVcFHSXyrtMRXJR.Ny2fpSixG00oLI2iMkT3yArpzs/921u',
'host' => 'bob.int.example.com',
},
# Bob is hosting two TLS services on his machine with different TLS hostnames
# Allow his credentials to generate certificates for the additional hostname as well
{
'pass' => 'dobbs',
'user' => 'bob',
# Plain text password is: dobbs
'hash' => '$2b$12$ZkfzP1DVcFHSXyrtMRXJR.Ny2fpSixG00oLI2iMkT3yArpzs/921u',
'host' => 'subgenius.int.example.com',
},
],
Expand Down

0 comments on commit 2207871

Please sign in to comment.