nixos/mailman: don't keep secrets in the Nix store

This replaces all Mailman secrets with ones that are generated the
first time the service is run.  This replaces the hyperkittyApiKey
option, which would lead to a secret in the world-readable store.
Even worse were the secrets hard-coded into mailman-web, which are not
just world-readable, but identical for all users!

services.mailman.hyperkittyApiKey has been removed, and so can no
longer be used to determine whether to enable Hyperkitty.  In its
place, there is a new option, services.mailman.hyperkitty.enable.  For
consistency, services.mailman.hyperkittyBaseUrl has been renamed to
services.mailman.hyperkitty.baseUrl.
This commit is contained in:
Alyssa Ross 2019-10-20 17:41:50 +00:00
parent 112fa077b1
commit c397d1909f
2 changed files with 70 additions and 37 deletions

View File

@ -53,30 +53,42 @@ let
etc_dir: /etc etc_dir: /etc
ext_dir: $etc_dir/mailman.d ext_dir: $etc_dir/mailman.d
pid_file: /run/mailman/master.pid pid_file: /run/mailman/master.pid
'' + optionalString (cfg.hyperkittyApiKey != null) '' '' + optionalString cfg.hyperkitty.enable ''
[archiver.hyperkitty] [archiver.hyperkitty]
class: mailman_hyperkitty.Archiver class: mailman_hyperkitty.Archiver
enable: yes enable: yes
configuration: ${pkgs.writeText "mailman-hyperkitty.cfg" mailmanHyperkittyCfg} configuration: /var/lib/mailman/mailman-hyperkitty.cfg
''; '';
mailmanHyperkittyCfg = '' mailmanHyperkittyCfg = pkgs.writeText "mailman-hyperkitty.cfg" ''
[general] [general]
# This is your HyperKitty installation, preferably on the localhost. This # This is your HyperKitty installation, preferably on the localhost. This
# address will be used by Mailman to forward incoming emails to HyperKitty # address will be used by Mailman to forward incoming emails to HyperKitty
# for archiving. It does not need to be publicly available, in fact it's # for archiving. It does not need to be publicly available, in fact it's
# better if it is not. # better if it is not.
base_url: ${cfg.hyperkittyBaseUrl} base_url: ${cfg.hyperkitty.baseUrl}
# Shared API key, must be the identical to the value in HyperKitty's # Shared API key, must be the identical to the value in HyperKitty's
# settings. # settings.
api_key: ${cfg.hyperkittyApiKey} api_key: @API_KEY@
''; '';
in { in {
###### interface ###### interface
imports = [
(mkRenamedOptionModule [ "services" "mailman" "hyperkittyBaseUrl" ]
[ "services" "mailman" "hyperkitty" "baseUrl" ])
(mkRemovedOptionModule [ "services" "mailman" "hyperkittyApiKey" ] ''
The Hyperkitty API key is now generated on first run, and not
stored in the world-readable Nix store. To continue using
Hyperkitty, you must set services.mailman.hyperkitty.enable = true.
'')
];
options = { options = {
services.mailman = { services.mailman = {
@ -128,24 +140,17 @@ in {
''; '';
}; };
hyperkittyBaseUrl = mkOption { hyperkitty = {
type = types.str; enable = mkEnableOption "the Hyperkitty archiver for Mailman";
default = "http://localhost/hyperkitty/";
description = ''
Where can Mailman connect to Hyperkitty's internal API, preferably on
localhost?
'';
};
hyperkittyApiKey = mkOption { baseUrl = mkOption {
type = types.nullOr types.str; type = types.str;
default = null; default = "http://localhost/hyperkitty/";
description = '' description = ''
The shared secret used to authenticate Mailman's internal Where can Mailman connect to Hyperkitty's internal API, preferably on
communication with Hyperkitty. Must be set to enable support for the localhost?
Hyperkitty archiver. Note that this secret is going to be visible to '';
all local users in the Nix store. };
'';
}; };
}; };
@ -187,13 +192,47 @@ in {
ExecStop = "${mailmanExe}/bin/mailman stop"; ExecStop = "${mailmanExe}/bin/mailman stop";
User = "mailman"; User = "mailman";
Type = "forking"; Type = "forking";
StateDirectory = "mailman";
StateDirectoryMode = "0700";
RuntimeDirectory = "mailman"; RuntimeDirectory = "mailman";
PIDFile = "/run/mailman/master.pid"; PIDFile = "/run/mailman/master.pid";
}; };
}; };
systemd.services.mailman-secrets = {
description = "Generate Hyperkitty API key";
before = [ "mailman.service" "mailman-web.service" "hyperkitty.service" "httpd.service" "uwsgi.service" ];
requiredBy = [ "mailman.service" "mailman-web.service" "hyperkitty.service" "httpd.service" "uwsgi.service" ];
script = ''
mailmanDir=/var/lib/mailman
mailmanWebDir=/var/lib/mailman-web
mailmanCfg=$mailmanDir/mailman-hyperkitty.cfg
hyperkittyCfg=$mailmanWebDir/settings_local.py
[ -e $mailmanCfg -o -e $hyperkittyCfg ] && exit 0
install -m 0700 -o mailman -g nogroup -d $mailmanDir
install -m 0700 -o ${cfg.webUser} -g nogroup -d $mailmanWebDir
hyperkittyApiKey=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64)
secretKey=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64)
hyperkittyCfgTmp=$(mktemp)
echo "MAILMAN_ARCHIVER_KEY='$hyperkittyApiKey'" >>"$hyperkittyCfgTmp"
echo "SECRET_KEY='$secretKey'" >>"$hyperkittyCfgTmp"
chown ${cfg.webUser} "$hyperkittyCfgTmp"
mailmanCfgTmp=$(mktemp)
sed "s/@API_KEY@/$hyperkittyApiKey/g" ${mailmanHyperkittyCfg} >"$mailmanCfgTmp"
chown mailman "$mailmanCfgTmp"
mv -n "$hyperkittyCfgTmp" $hyperkittyCfg
mv -n "$mailmanCfgTmp" $mailmanCfg
'';
serviceConfig = {
Type = "oneshot";
};
};
systemd.services.mailman-web = { systemd.services.mailman-web = {
description = "Init Postorius DB"; description = "Init Postorius DB";
before = [ "httpd.service" ]; before = [ "httpd.service" ];
@ -207,8 +246,6 @@ in {
serviceConfig = { serviceConfig = {
User = cfg.webUser; User = cfg.webUser;
Type = "oneshot"; Type = "oneshot";
StateDirectory = "mailman-web";
StateDirectoryMode = "0700";
WorkingDirectory = "/var/lib/mailman-web"; WorkingDirectory = "/var/lib/mailman-web";
}; };
}; };
@ -223,7 +260,7 @@ in {
}; };
systemd.services.hyperkitty = { systemd.services.hyperkitty = {
enable = cfg.hyperkittyApiKey != null; inherit (cfg.hyperkitty) enable;
description = "GNU Hyperkitty QCluster Process"; description = "GNU Hyperkitty QCluster Process";
after = [ "network.target" ]; after = [ "network.target" ];
wantedBy = [ "mailman.service" "multi-user.target" ]; wantedBy = [ "mailman.service" "multi-user.target" ];
@ -235,7 +272,7 @@ in {
}; };
systemd.services.hyperkitty-minutely = { systemd.services.hyperkitty-minutely = {
enable = cfg.hyperkittyApiKey != null; inherit (cfg.hyperkitty) enable;
description = "Trigger minutely Hyperkitty events"; description = "Trigger minutely Hyperkitty events";
startAt = "minutely"; startAt = "minutely";
serviceConfig = { serviceConfig = {
@ -246,7 +283,7 @@ in {
}; };
systemd.services.hyperkitty-quarter-hourly = { systemd.services.hyperkitty-quarter-hourly = {
enable = cfg.hyperkittyApiKey != null; inherit (cfg.hyperkitty) enable;
description = "Trigger quarter-hourly Hyperkitty events"; description = "Trigger quarter-hourly Hyperkitty events";
startAt = "*:00/15"; startAt = "*:00/15";
serviceConfig = { serviceConfig = {
@ -257,7 +294,7 @@ in {
}; };
systemd.services.hyperkitty-hourly = { systemd.services.hyperkitty-hourly = {
enable = cfg.hyperkittyApiKey != null; inherit (cfg.hyperkitty) enable;
description = "Trigger hourly Hyperkitty events"; description = "Trigger hourly Hyperkitty events";
startAt = "hourly"; startAt = "hourly";
serviceConfig = { serviceConfig = {
@ -268,7 +305,7 @@ in {
}; };
systemd.services.hyperkitty-daily = { systemd.services.hyperkitty-daily = {
enable = cfg.hyperkittyApiKey != null; inherit (cfg.hyperkitty) enable;
description = "Trigger daily Hyperkitty events"; description = "Trigger daily Hyperkitty events";
startAt = "daily"; startAt = "daily";
serviceConfig = { serviceConfig = {
@ -279,7 +316,7 @@ in {
}; };
systemd.services.hyperkitty-weekly = { systemd.services.hyperkitty-weekly = {
enable = cfg.hyperkittyApiKey != null; inherit (cfg.hyperkitty) enable;
description = "Trigger weekly Hyperkitty events"; description = "Trigger weekly Hyperkitty events";
startAt = "weekly"; startAt = "weekly";
serviceConfig = { serviceConfig = {
@ -290,7 +327,7 @@ in {
}; };
systemd.services.hyperkitty-yearly = { systemd.services.hyperkitty-yearly = {
enable = cfg.hyperkittyApiKey != null; inherit (cfg.hyperkitty) enable;
description = "Trigger yearly Hyperkitty events"; description = "Trigger yearly Hyperkitty events";
startAt = "yearly"; startAt = "yearly";
serviceConfig = { serviceConfig = {

View File

@ -39,9 +39,6 @@ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
# Quick-start development settings - unsuitable for production # Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '$!-7^wl#wiifjbh)5@f7ji%x!vp7s1vzbvwt26hxv$idixq0u0'
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False DEBUG = False
@ -64,7 +61,6 @@ ALLOWED_HOSTS = [
MAILMAN_REST_API_URL = 'http://localhost:8001' MAILMAN_REST_API_URL = 'http://localhost:8001'
MAILMAN_REST_API_USER = 'restadmin' MAILMAN_REST_API_USER = 'restadmin'
MAILMAN_REST_API_PASS = 'restpass' MAILMAN_REST_API_PASS = 'restpass'
MAILMAN_ARCHIVER_KEY = "@ARCHIVER_KEY@"
MAILMAN_ARCHIVER_FROM = ('127.0.0.1', '::1') MAILMAN_ARCHIVER_FROM = ('127.0.0.1', '::1')
# Application definition # Application definition