I was also interested in this, I’m necro-bumping to post a solution that does not rely on storing the whole config file as a secret. ZNC ships with the cyrusauth module that allows you to use the flexible SASL auth framework. With SASL you can authenticate users with PAM (i.e. how an ordinary users.users
authenticate), Kerberos, LDAP, a SQL database of passwords, etc. I implemented this with PAM, so your ZNC password is the account password on the system, and there you have methods to store a hash outside of the store or configuration.nix
.
# system or normal user
users.users."znc-admin" = {
isSystemUser = true;
group = "znc-admin";
password = "foobar"; # for proof of concept only -- use passwordFile instead
};
users.groups."znc-admin" = {};
# cyrusauth module talks to saslauthd, default auth mechanism is PAM
services.saslauthd.enable = true;
environment.etc = {
# need to add a PAM service config, cyrusauth identifies itself as "znc"
# very standard config, copied from others in /etc/pam.d
# just checks that you have a valid account/password
"pam.d/znc" = {
source = pkgs.writeText "znc.pam" ''
# Account management.
account required pam_unix.so
# Authentication management.
auth sufficient pam_unix.so likeauth try_first_pass
auth required pam_deny.so
# Password management.
password sufficient pam_unix.so nullok sha512
# Session management.
session required pam_env.so conffile=/etc/pam/environment readenv=0
session required pam_unix.so
'';
};
};
# znc service config has some hardening options that otherwise block
# interaction with saslauthd's unix socket
systemd.services.znc.serviceConfig.RestrictAddressFamilies = [ "AF_UNIX" ];
services.znc = {
enable = true;
mutable = false;
useLegacyConfig = false;
openFirewall = true;
config = {
LoadModule = [ "adminlog" "cyrusauth saslauthd" ];
Listener.l = {
Port = 48884;
AllowIRC = true;
AllowWeb = true;
SSL = false;
};
User."znc-admin" = {
Admin = true;
# fake hash, auth against this will always fail, user can only login via SASL
# znc compains if you have no Pass
Pass = "md5#::#::#";
};
};
};
No more hashes (just make sure you use passwordFile
for the user). By default, cyrusauth requires that users also be defined in the ZNC config. You can change that so valid system accounts would get a ZNC account created on the fly, see the module’s page above.
PAM also has a facility to only allow users in a particular group to authenticate, check out pam_listfile
if you want to only accept users from, say, znc-users
group.
ZNC also has a couple of additional alternative login methods via modules:
I also played around with writing a custom module to just load a password hash from a text file. Here is a proof of concept module that does that:
#include <znc/main.h>
#include <znc/Modules.h>
#include <znc/User.h>
#include <fstream>
using namespace std;
class CSampleMod : public CModule {
public:
MODCONSTRUCTOR(CSampleMod) {}
bool OnLoad(const CString& sPassFilePath, CString& sMessage) override {
string hash, salt;
ifstream myfile (sPassFilePath);
getline (myfile, hash);
getline (myfile, salt);
myfile.close();
GetUser()->SetPass(hash, CUser::HASH_SHA256, salt);
return true;
}
};
USERMODULEDEFS(CSampleMod, t_s("Load user password from a file"))
And the znc.conf
would have no Pass
section, User
would have LoadModule = mypassmodule /path/to/secret.txt
. I didn’t get as far as building the module into my system, instead settled on the cyrusauth/PAM solution above.
Finally, another solution I’ve used for quasi-sensitive information, i.e. a password hash that I might not mind keeping in the nix store but I wouldn’t publish on github, is to just keep the secret in a separate file and leave it out of the git repo. Such as local.nix
containing { myHash = "1234"; }
and in configuration.nix
I do local = import ./local.nix; services.foobar.passHash = local.myHash;
. Then put local.nix
in .gitignore
.