wk2: account management tooling

This commit is contained in:
ari melody 2026-02-05 14:02:32 +00:00
parent 37eeeb2467
commit f864d9c84e
Signed by: ari
GPG key ID: CF99829C92678188
34 changed files with 379 additions and 350 deletions

View file

@ -121,9 +121,9 @@ auth_mechanisms = plain
#!include auth-deny.conf.ext
#!include auth-master.conf.ext
!include auth-system.conf.ext
#!include auth-system.conf.ext
#!include auth-sql.conf.ext
#!include auth-ldap.conf.ext
#!include auth-passwdfile.conf.ext
!include auth-passwdfile.conf.ext
#!include auth-checkpassword.conf.ext
#!include auth-static.conf.ext

View file

@ -1,60 +0,0 @@
##
## Director-specific settings.
##
# Director can be used by Dovecot proxy to keep a temporary user -> mail server
# mapping. As long as user has simultaneous connections, the user is always
# redirected to the same server. Each proxy server is running its own director
# process, and the directors are communicating the state to each others.
# Directors are mainly useful with NFS-like setups.
# List of IPs or hostnames to all director servers, including ourself.
# Ports can be specified as ip:port. The default port is the same as
# what director service's inet_listener is using.
#director_servers =
# List of IPs or hostnames to all backend mail servers. Ranges are allowed
# too, like 10.0.0.10-10.0.0.30.
#director_mail_servers =
# How long to redirect users to a specific server after it no longer has
# any connections.
#director_user_expire = 15 min
# How the username is translated before being hashed. Useful values include
# %Ln if user can log in with or without @domain, %Ld if mailboxes are shared
# within domain.
#director_username_hash = %Lu
# To enable director service, uncomment the modes and assign a port.
service director {
unix_listener login/director {
#mode = 0666
}
fifo_listener login/proxy-notify {
#mode = 0666
}
unix_listener director-userdb {
#mode = 0600
}
inet_listener {
#port =
}
}
# Enable director for the wanted login services by telling them to
# connect to director socket instead of the default login socket:
service imap-login {
#executable = imap-login director
}
service pop3-login {
#executable = pop3-login director
}
service submission-login {
#executable = submission-login director
}
# Enable director for LMTP proxying:
protocol lmtp {
#auth_socket_path = director-userdb
}

View file

@ -27,7 +27,8 @@
#
# <doc/wiki/MailLocation.txt>
#
mail_location = mbox:~/mail:INBOX=/var/mail/%u
mail_home = /var/mail/%d/%n/home/
mail_location = maildir:/var/mail/%u/%n
# If you need to set multiple mailbox locations or want to change default
# namespace settings, you can do it by defining namespace sections.
@ -111,7 +112,7 @@ namespace inbox {
# Group to enable temporarily for privileged operations. Currently this is
# used only with INBOX when either its initial creation or dotlocking fails.
# Typically this is set to "mail" to give access to /var/mail.
mail_privileged_group = mail
mail_privileged_group = jupiter
# Grant access to these supplementary groups for mail processes. Typically
# these are used to set up access to shared mailboxes. Note that it may be
@ -215,7 +216,7 @@ mail_privileged_group = mail
# Space separated list of plugins to load for all services. Plugins specific to
# IMAP, LDA, etc. are added to this list in their own .conf files.
#mail_plugins =
mail_plugins = $mail_plugins quota
##
## Mailbox handling optimizations
@ -282,7 +283,7 @@ protocol !indexer-worker {
# This is done by stat()ing each entry, so it causes more disk I/O.
# (For systems setting struct dirent->d_type, this check is free and it's
# done always regardless of this setting)
#maildir_stat_dirs = no
maildir_stat_dirs = yes
# When copying a message, do it with hard links whenever possible. This makes
# the performance much better, and it's unlikely to have any side effects.

View file

@ -98,15 +98,17 @@ service auth {
# something else than 0666 and Dovecot lets the kernel enforce the
# permissions (e.g. 0777 allows everyone full permissions).
unix_listener auth-userdb {
#mode = 0666
#user =
#group =
mode = 0666
user = jupiter
group = jupiter
}
# Postfix smtp-auth
#unix_listener /var/spool/postfix/private/auth {
# mode = 0666
#}
unix_listener /dev/shm/sasl-auth.sock {
mode = 0666
user = postfix
group = postfix
}
# Auth process is run as this user.
#user = $default_internal_user

View file

@ -10,8 +10,8 @@ ssl = no #yes
# dropping root privileges, so keep the key file unreadable by anyone but
# root. Included doc/mkcert.sh can be used to easily generate self-signed
# certificate, just make sure to update the domains in dovecot-openssl.cnf
ssl_cert = /etc/dovecot/private/dovecot.pem
ssl_key = /etc/dovecot/private/dovecot.key
#ssl_cert = /etc/dovecot/private/dovecot.pem
#ssl_key = /etc/dovecot/private/dovecot.key
# If key file is password protected, give the password here. Alternatively
# give it when starting dovecot with -p parameter. Since this file is often
@ -53,7 +53,7 @@ ssl_client_ca_dir = /etc/ssl/certs
# Generate new params with `openssl dhparam -out /etc/dovecot/dh.pem 4096`
# Or migrate from old ssl-parameters.dat file with the command dovecot
# gives on startup when ssl_dh is unset.
ssl_dh = </usr/share/dovecot/dh.pem
#ssl_dh = </usr/share/dovecot/dh.pem
# Minimum SSL protocol version to use. Potentially recognized values are SSLv3,
# TLSv1, TLSv1.1, TLSv1.2 and TLSv1.3, depending on the OpenSSL version used.

View file

@ -1,14 +0,0 @@
# 10-tcpwrapper.conf
#
# service name for hosts.{allow|deny} are those defined as
# inet_listener in master.conf
#
#login_access_sockets = tcpwrap
#
#service tcpwrap {
# unix_listener login/tcpwrap {
# group = $default_login_user
# mode = 0600
# user = $default_login_user
# }
#}

View file

@ -1,48 +0,0 @@
##
## LDA specific settings (also used by LMTP)
##
# Address to use when sending rejection mails.
# Default is postmaster@%d. %d expands to recipient domain.
#postmaster_address =
# Hostname to use in various parts of sent mails (e.g. in Message-Id) and
# in LMTP replies. Default is the system's real hostname@domain.
#hostname =
# If user is over quota, return with temporary failure instead of
# bouncing the mail.
#quota_full_tempfail = no
# Binary to use for sending mails.
#sendmail_path = /usr/sbin/sendmail
# If non-empty, send mails via this SMTP host[:port] instead of sendmail.
#submission_host =
# Subject: header to use for rejection mails. You can use the same variables
# as for rejection_reason below.
#rejection_subject = Rejected: %s
# Human readable error message for rejection mails. You can use variables:
# %n = CRLF, %r = reason, %s = original subject, %t = recipient
#rejection_reason = Your message to <%t> was automatically rejected:%n%r
# Delimiter character between local-part and detail in email address.
#recipient_delimiter = +
# Header where the original recipient address (SMTP's RCPT TO: address) is taken
# from if not available elsewhere. With dovecot-lda -a parameter overrides this.
# A commonly used header for this is X-Original-To.
#lda_original_recipient_header =
# Should saving a mail to a nonexistent mailbox automatically create it?
#lda_mailbox_autocreate = no
# Should automatically created mailboxes be also automatically subscribed?
#lda_mailbox_autosubscribe = no
protocol lda {
# Space separated list of plugins to load (default is global mail_plugins).
#mail_plugins = $mail_plugins
}

View file

@ -1,19 +0,0 @@
##
## Mailbox access control lists.
##
# vfile backend reads ACLs from "dovecot-acl" file from mail directory.
# You can also optionally give a global ACL directory path where ACLs are
# applied to all users' mailboxes. The global ACL directory contains
# one file for each mailbox, eg. INBOX or sub.mailbox. cache_secs parameter
# specifies how many seconds to wait between stat()ing dovecot-acl file
# to see if it changed.
plugin {
#acl = vfile:/etc/dovecot/global-acls:cache_secs=300
}
# To let users LIST mailboxes shared by other users, Dovecot needs a
# shared mailbox dictionary. For example:
plugin {
#acl_shared_dict = file:/var/lib/dovecot/shared-mailboxes
}

View file

@ -1,21 +0,0 @@
# Authentication for checkpassword users. Included from 10-auth.conf.
#
# <doc/wiki/AuthDatabase.CheckPassword.txt>
passdb {
driver = checkpassword
args = /usr/bin/checkpassword
}
# passdb lookup should return also userdb info
userdb {
driver = prefetch
}
# Standard checkpassword doesn't support direct userdb lookups.
# If you need checkpassword userdb, the checkpassword must support
# Dovecot-specific extensions.
#userdb {
# driver = checkpassword
# args = /usr/bin/checkpassword
#}

View file

@ -1,15 +0,0 @@
# Deny access for users. Included from 10-auth.conf.
# Users can be (temporarily) disabled by adding a passdb with deny=yes.
# If the user is found from that database, authentication will fail.
# The deny passdb should always be specified before others, so it gets
# checked first.
# Example deny passdb using passwd-file. You can use any passdb though.
passdb {
driver = passwd-file
deny = yes
# File contains a list of usernames, one per line
args = /etc/dovecot/deny-users
}

View file

@ -1,16 +0,0 @@
# Authentication via dict backend. Included from 10-auth.conf.
#
# <doc/wiki/AuthDatabase.Dict.txt>
passdb {
driver = dict
# Path for dict configuration file, see
# example-config/dovecot-dict-auth.conf.ext
args = /etc/dovecot/dovecot-dict-auth.conf.ext
}
userdb {
driver = dict
args = /etc/dovecot/dovecot-dict-auth.conf.ext
}

View file

@ -6,12 +6,13 @@
passdb {
driver = passwd-file
mechanisms = plain login
args = scheme=SHA512-CRYPT username_format=%u /etc/dovecot/users
args = scheme=SHA512-CRYPT username_format=%u /etc/dovecot/userdb
}
userdb {
driver = passwd-file
args = username_format=%u /etc/dovecot/users
args = username_format=%u /etc/dovecot/userdb
default_fields = uid=jupiter gid=jupiter home=/var/mail/%d/%u/home/
# Default fields that can be overridden by passwd-file
#default_fields = quota_rule=*:storage=1G

View file

@ -1,30 +0,0 @@
# Authentication for SQL users. Included from 10-auth.conf.
#
# <doc/wiki/AuthDatabase.SQL.txt>
passdb {
driver = sql
# Path for SQL configuration file, see example-config/dovecot-sql.conf.ext
args = /etc/dovecot/dovecot-sql.conf.ext
}
# "prefetch" user database means that the passdb already provided the
# needed information and there's no need to do a separate userdb lookup.
# <doc/wiki/UserDatabase.Prefetch.txt>
#userdb {
# driver = prefetch
#}
userdb {
driver = sql
args = /etc/dovecot/dovecot-sql.conf.ext
}
# If you don't have any user-specific settings, you can avoid the user_query
# by using userdb static instead of userdb sql, for example:
# <doc/wiki/UserDatabase.Static.txt>
#userdb {
#driver = static
#args = uid=vmail gid=vmail home=/var/vmail/%u
#}

View file

@ -1,24 +0,0 @@
# Static passdb. Included from 10-auth.conf.
# This can be used for situations where Dovecot doesn't need to verify the
# username or the password, or if there is a single password for all users:
#
# - proxy frontend, where the backend verifies the password
# - proxy backend, where the frontend already verified the password
# - authentication with SSL certificates
# - simple testing
#passdb {
# driver = static
# args = proxy=y host=%1Mu.example.com nopassword=y
#}
#passdb {
# driver = static
# args = password=test
#}
#userdb {
# driver = static
# args = uid=vmail gid=vmail home=/home/%u
#}

View file

@ -1,74 +0,0 @@
# Authentication for system users. Included from 10-auth.conf.
#
# <doc/wiki/PasswordDatabase.txt>
# <doc/wiki/UserDatabase.txt>
# PAM authentication. Preferred nowadays by most systems.
# PAM is typically used with either userdb passwd or userdb static.
# REMEMBER: You'll need /etc/pam.d/dovecot file created for PAM
# authentication to actually work. <doc/wiki/PasswordDatabase.PAM.txt>
passdb {
driver = pam
# [session=yes] [setcred=yes] [failure_show_msg=yes] [max_requests=<n>]
# [cache_key=<key>] [<service name>]
#args = dovecot
}
# System users (NSS, /etc/passwd, or similar).
# In many systems nowadays this uses Name Service Switch, which is
# configured in /etc/nsswitch.conf. <doc/wiki/AuthDatabase.Passwd.txt>
#passdb {
#driver = passwd
# [blocking=no]
#args =
#}
# Shadow passwords for system users (NSS, /etc/shadow or similar).
# Deprecated by PAM nowadays.
# <doc/wiki/PasswordDatabase.Shadow.txt>
#passdb {
#driver = shadow
# [blocking=no]
#args =
#}
# PAM-like authentication for OpenBSD.
# <doc/wiki/PasswordDatabase.BSDAuth.txt>
#passdb {
#driver = bsdauth
# [blocking=no] [cache_key=<key>]
#args =
#}
##
## User databases
##
# System users (NSS, /etc/passwd, or similar). In many systems nowadays this
# uses Name Service Switch, which is configured in /etc/nsswitch.conf.
userdb {
# <doc/wiki/AuthDatabase.Passwd.txt>
driver = passwd
# [blocking=no]
#args =
# Override fields from passwd
#override_fields = home=/home/virtual/%u
}
# Static settings generated from template <doc/wiki/UserDatabase.Static.txt>
#userdb {
#driver = static
# Can return anything a userdb could normally return. For example:
#
# args = uid=500 gid=500 home=/var/mail/%u
#
# LDA and LMTP needs to look up users only from the userdb. This of course
# doesn't work with static userdb because there is no list of users.
# Normally static userdb handles this by doing a passdb lookup. This works
# with most passdbs, with PAM being the most notable exception. If you do
# the user verification another way, you can add allow_all_users=yes to
# the args in which case the passdb lookup is skipped.
#
#args =
#}

4
target/pam/pam.d/smtp Normal file
View file

@ -0,0 +1,4 @@
# /etc/pam.d/smtp
auth required pam_pgsql.so
account required pam_pgsql.so
password required pam_pgsql.so

12
target/pam/pam_pgsql.conf Normal file
View file

@ -0,0 +1,12 @@
# /etc/pam_pgsql.conf
database = mails
host = localhost
user = mailreader
password = mailreader-secret
table = users
user_column = userid
pwd_column = password
#expired_column = acc_expired
#newtok_column = acc_new_pwreq
pw_type = crypt
#debug

16
target/postfix/main.cf Normal file
View file

@ -0,0 +1,16 @@
# https://doc.dovecot.org/2.4.2/howto/virtual/postgresql.html
transport_maps = pgsql:/etc/postfix/transport.cf
virtual_uid_maps = pgsql:/etc/postfix/uids.cf
virtual_gid_maps = pgsql:/etc/postfix/gids.cf
# Note that we've set virtual_mailbox_base to /home, which means that it's
# prefixed to all home directories in SQL database.
virtual_mailbox_base = /home
virtual_mailbox_maps = pgsql:/etc/postfix/mailboxes.cf
virtual_maps = pgsql:/etc/postfix/virtual.cf
mydestination = $mydomain, $myhostname
smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination
smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous
smtpd_sasl_local_domain = domain.org
smtp_sasl_auth_enable = no

View file

@ -0,0 +1,3 @@
pwcheck_method: saslauthd
saslauthd_path: /etc/mux
mech_list: login plain

View file

@ -0,0 +1,8 @@
user=mailreader
# TODO: remove default password
password=mailreader-secret
dbname=mails
table=transport
select_field=transport
where_field=domain
hosts=localhost

8
target/postfix/uids.cf Normal file
View file

@ -0,0 +1,8 @@
user=mailreader
# TODO: remove default password
password=mailreader-secret
dbname=mails
table=users
select_field=uid
where_field=userid
hosts=localhost

View file

@ -0,0 +1 @@
host all all 127.0.0.1 255.255.255.255 password

4
target/sasl/saslauthd Normal file
View file

@ -0,0 +1,4 @@
# /etc/default/saslauthd
START=yes
MECHANISMS=pam
PARAMS="-r -m /var/spool/postfix/etc"

View file

@ -0,0 +1,4 @@
#!/bin/bash
sudo -u postgres createdb mails
sudo -u postgres psql mails -f ./build/mail-create-tables.sql

View file

@ -0,0 +1,21 @@
#!/bin/bash
set -e
apt install -y build-essential pkg-config m4 libtool automake autoconf libgcrypt20-dev libtool libpam0g-dev libpq-dev
git clone https://github.com/pam-pgsql/pam-pgsql
cd pam-pgsql
autoreconf --install
chmod +x ./configure
./configure
make
# sleep .5
# printf "\n\n\n"
# sleep .5
#
# make install
#
# exit 1

View file

@ -0,0 +1,50 @@
CREATE TABLE transport (
domain VARCHAR(128) NOT NULL,
transport VARCHAR(128) NOT NULL,
PRIMARY KEY (domain)
);
CREATE TABLE users (
userid VARCHAR(128) NOT NULL,
password VARCHAR(128),
realname VARCHAR(128),
uid INTEGER NOT NULL,
gid INTEGER NOT NULL,
home VARCHAR(128),
mail VARCHAR(255),
PRIMARY KEY (userid)
);
CREATE TABLE virtual (
address VARCHAR(255) NOT NULL,
userid VARCHAR(255) NOT NULL,
PRIMARY KEY (address)
);
create view postfix_mailboxes as
select userid, home||'/' as mailbox from users
union all
select domain as userid, 'dummy' as mailbox from transport;
create view postfix_virtual as
select userid, userid as address from users
union all
select userid, address from virtual;
-- TODO: we MUST NOT include default passwords in this script.
-- in future, it would be nice to generate these accounts as part of setup,
-- using credentials provided to us by the user.
-- `mailreader` could be randomly-generated, though.
CREATE USER mailreader PASSWORD 'mailreader-secret';
grant select on transport, users, virtual, postfix_mailboxes, postfix_virtual to mailreader;
create user mailwriter password 'mailwriter-secret';
grant select, insert, update, delete on transport, users, virtual, postfix_mailboxes, postfix_virtual to mailwriter;
-- TODO: remove example users
insert into transport (domain, transport) values ('domain.org', 'virtual:');
insert into transport (domain, transport) values ('foo.org', 'virtual:');
insert into users (userid, uid, gid, home) values ('user@domain.org', 1001, 1001, 'domain.org/mails/user');
insert into users (userid, uid, gid, home) values ('user2@domain.org', 1001, 1001, 'domain.org/mails/user2');
insert into users (userid, uid, gid, home) values ('user@foo.org', 1002, 1002, 'foo.org/mails/user');
insert into virtual (address, userid) values ('foo@foo.org', 'user@foo.org');

View file

@ -1,8 +1,12 @@
#!/bin/bash
set -e
DEBUG_PACKAGES=(procps)
POSTFIX_PACKAGES=(postfix)
DOVECOT_PACKAGES=(dovecot-core dovecot-imapd dovecot-ldap)
# libpam-pgsql is only available in debian sid at the moment; building in compile step
DB_PACKAGES=(sasl2-bin libsasl2-modules postgresql)
POSTFIX_PACKAGES=(postfix postfix-pgsql)
DOVECOT_PACKAGES=(dovecot-core dovecot-imapd dovecot-pgsql)
RSPAMD_PACKAGES=(rspamd redis-server)
FAIL2BAN_PACKAGES=(fail2ban)
@ -10,6 +14,7 @@ PACKAGES=(
tini
supervisor
${DEBUG_PACKAGES[@]}
${DB_PACKAGES[@]}
${POSTFIX_PACKAGES[@]}
${DOVECOT_PACKAGES[@]}
# ${RSPAMD_PACKAGES[@]}

View file

@ -17,9 +17,11 @@ start_daemon () {
# start daemons
log "info" "Starting daemons..."
start_daemon fail2ban-server
start_daemon rspamd
start_daemon rspamd-redis
# start_daemon fail2ban
# start_daemon rspamd
# start_daemon rspamd-redis
start_daemon saslauthd
start_daemon postgresql
start_daemon dovecot
start_daemon postfix

View file

@ -34,10 +34,29 @@ autorestart=true
stdout_logfile=/var/log/supervisor/%(program_name)s.log
stderr_logfile=/var/log/supervisor/%(program_name)s.log
[program:dovecot]
command=/usr/sbin/dovecot
[program:saslauthd]
command=/usr/sbin/saslauthd
autostart=false
startsecs=3
startsecs=0
stopwaitsecs=55
autorestart=true
stdout_logfile=/var/log/supervisor/%(program_name)s.log
stderr_logfile=/var/log/supervisor/%(program_name)s.log
[program:postgresql]
command=/usr/lib/postgresql/15/bin/postgres -D /etc/postgresql/15/main
user=postgres
autostart=false
startsecs=0
stopwaitsecs=55
autorestart=true
stdout_logfile=/var/log/supervisor/%(program_name)s.log
stderr_logfile=/var/log/supervisor/%(program_name)s.log
[program:dovecot]
command=/usr/sbin/dovecot -F -c /etc/dovecot/dovecot.conf
autostart=false
startsecs=0
stopwaitsecs=55
autorestart=true
stdout_logfile=/var/log/supervisor/%(program_name)s.log
@ -46,7 +65,7 @@ stderr_logfile=/var/log/supervisor/%(program_name)s.log
[program:postfix]
command=/usr/sbin/postfix
autostart=false
startsecs=3
startsecs=0
stopwaitsecs=55
autorestart=true
stdout_logfile=/var/log/supervisor/%(program_name)s.log