ZNC config without putting password hash in configuration.nix?

I’m setting up the ZNC IRC bouncer daemon, and part of the nixos.wiki setup says to create a password hash and put it in configuration.nix. I’d rather not do that since I share my configuration.nix on github.

Anyone know if there is a way to point to a separate file containing the password hash instead? I do this for my user accounts in configuration.nix, but the ZNC wiki doesn’t mention anything like this.

From the znc wiki it doesn’t seem to.

You might need to use configFile and provide full configuration in textual form.

You then can use git secret or similar to encrypt that file or nix age/sops to do whatever one does with them.

1 Like

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.

1 Like

Thanks for figuring this out and posting, much appreciated!