iRedMail, LDAP i migracja serwera pocztowego, z której się sporo nauczyłem

Przeczytasz to w: 16 minut

Ten post nie będzie do końca taki, jak wcześniej, bo niestety nie mam tyle czasu, by opisać całą instalację iRedMaila i tego, co robiłem. Ogólnie udało mi się zmigrować u klienta serwer pocztowy. Klient miał Debiana 6, trochę stary, użytkownicy lokalni, a nie w SQLu czy LDAPie (OpenLDAP lub AD) i trzeba było coś z tym zrobić. Był to jakiś pakiet, który bazował na Postfixie/Dovecot z interfejsem webowym Squirrel. Trzeba było coś z tym zrobić, więc ostatecznie zdecydowaliśmy się na migrację serwera na nowy, z Debianem 10 i celem był iRedMail – prosty pakiet z Postfixem i Dovecotem z podstawowym panelem administracyjnym. Wdrożyłem go z LDAPem z prostego powodu: jest szansa na to, że w przyszłości taki użytkownik będzie mógł wykorzystać logowanie z Active Directory jeśli przepnie się query z LDAPa do AD oraz każdemu użytkownikowi w AD doda się odpowiedni atrybuty, z których korzysta iRedMail.

Dlatego też jeśli chcecie się nauczyć stawiać iRedMaila – polecam sobie poczytać ich dokumentację. Jest tam naprawdę sporo ciekawych materiałów. Pomimo to są rzeczy, które nawet jeśli są w tej dokumentacji przykuły moją uwagę.

Migracja użytkowników do OpenLDAPa

Użytkownicy byli przechowywani lokalnie, więc mogłem znaleźć ich nazwy użytkowników, nazwy wyświetlane (Gecos) itd w pliku /etc/passwd. Ogólnie ten plik wygląda tak:

root:$6$somesalt$aaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbb/cccccccccccccccccc/ddddddddddd.eeeeee
supra:$6$somesalt$aaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbb/cccccccccccccccccc/ddddddddddd.eeeeeeeeeeee/:17604:0:99999:7:::
anotheruser:$6$somesalt$aaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbb/cccccccccccccccccc/ddddddddddd.eeeeeeeeeeee.:17604:0:99999:7:::
editor:$6$somesalt$aaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbb/cccccccccccccccccc/ddddddddddd.eeeeeeeeeeee.:17668:0:99999:7:::
test:$6$somesalt$aaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbb/cccccccccccccccccc/ddddddddddd.eeeeeeeeeeee.:18314:0:99999:7:::

Tutaj znalazłem informację jak tworzyć użytkowników w LDAPie z odpowiednimi atrybutami. Z tego pliku wyciągnęliśmy listę wszystkich użytkowników oraz Gecos. Wystarczy do tego Excel lub inny program pracujący z arkuszami kalkulacyjnymi, który jest w stanie zczytywać dane z plików, które mają oddzielone wartości za pomocą jakiegoś znaku (np. CSV). Na początku klikamy Z pliku tekstowego/CSV, następnie wskazujemy plik i wybieramy ogranicznik (znak oddzielający wartości, ang. delimiter), powinien być nim dwukropek.

Korzystam z Excel 2019 i on sam wykrył ten znak (musiałem jedynie zmienić kodowanie pliku na UTF-8), zmieniłem też pole Wykrywanie typu danych na Nie wykrywaj typów danych:

Jak widać, wszystko ładnie się zaimportowało:

Następnie należy trochę przerobić te dane. Składnia to:

mydomain.com, user2, plain_password, Michael Jordan, group1:group2,

W praktyce to powinno wyglądać tak:

Jak widać w stosunku do zrzutu ekranu wyżej, usunąłem przecinki po w nazwie wyświetlanej oraz poprzenosiłem wartości w kolejności. Kolumna F jest pusta, bo nie mam zdefiniowanych żadnych grup. W takiej sytuacji trzeba na końcu każdej linii po wyeksportowaniu pliku CSV dopisać spację i przecinek. Dodałem też te liczby i 5368709120 to jest quota (przydział rozmiaru skrzynki) w bajtach. 5368709120 daje nam 5 GB.

Zapisujemy plik w postaci CSV UTF-8 (w ogóle to jest dostępne tylko w Excel 2019, nie wiedzieć czemu):

Otrzymujemy to (otwieram takie pliki w Visual Studio Code):

Podmieńmy sobie najpierw średniki ; na przecinki ze spacją , :

Teraz musimy jakoś dodać te puste pola grup. Z faktu, że przypisałem wszystkim użytkownikom taką samą wielkość skrzynki, wykorzystam to, by dodać dodatkową wartość:

W ten sposób możemy wykorzystać taki plik CSV do zaimportowania użytkowników. Jak widać, nie przenoszę tutaj haseł tylko generuję nowe. Skorzystałem z random.org do wygenerowania hasełek, które wkleiłem do arkusza:

iRedMail dostarcza skrypty, które przygotowują za nas plik LDIF, który potem musimy wykorzystać do dodania użytkowników za pomocą polecenia ldapadd. Powinny być w folderze instalacyjnym iRedMaila, w folderze tools. Są dwa skrypty: create_mail_user_OpenLDAP.py i create_mail_user_OpenLDAP.sh. Ja skorzystałem z tego pierwszego, bo we wdrożeniu chciałem mieć katalogi użytkowników w konkretnym katalogu, a ten bashowy skrypt chyba na to nie pozwalał.

To, co trzeba sobie w tym skrypcie zmienić to:

# LDAP server address.
LDAP_URI = 'ldap://127.0.0.1:389'

# LDAP base dn.
BASEDN = 'o=domains,dc=example,dc=com'

# Bind dn/password
BINDDN = 'cn=Manager,dc=example,dc=com'
BINDPW = 'supersecretpassword'

# Storage base directory.
STORAGE_BASE_DIRECTORY = '/var/vmail/vmail1'

# Append timestamp in maildir path.
APPEND_TIMESTAMP_IN_MAILDIR = True

# Get base directory and storage node.
std = STORAGE_BASE_DIRECTORY.rstrip('/').split('/')
STORAGE_NODE = std.pop()
STORAGE_BASE = '/'.join(std)

# Hashed maildir: True, False.
# Example:
#   domain: domain.ltd,
#   user:   zhang (zhang@domain.ltd)
#
#       - hashed: d/do/domain.ltd/z/zh/zha/zhang/
#       - normal: domain.ltd/zhang/
HASHED_MAILDIR = True

Te zmienne definiują:

  • LDAP_URI – adres serwera LDAP, do którego się łączymy (jest on na serwerze localnie),
  • BASEDN – DN domeny, każdy element oddziela się przecinkiem w miejscu, gdzie jest kropka w nazwie, np. super.domena.com dzieli się na dc=super,dc=domena,dc=com, dopisujemy na początku o=domains wyjątkowo w tym polu,
  • BINDDN – użytkownik, na prawach którego wykonujemy operację, przy czym Manager jest kontem, które ma pełne prawa do modyfikowania wszystkiego, co jest w LDAPie, w dużym uproszczeniu to jest nazwa użytkownika + nazwa domeny, którą przedstawiałem wyżej, w tym polu trzeba poprawić domenę
  • BINDPW – hasło do konta Manager, znajdziemy je w katalogu instalacyjnym w pliku iRedMail.tips,
  • STORAGE_BASE_DIRECTORY – katalog bazowy, w którym znajdują się wszystkie skrzynki, ścieżka do katalogu użytkownika to STORAGE_BASE_DIRECTORY/<domena>/<folder-użytkownika>
  • APPEND_TIMESTAMP_IN_MAILDIR – jeśli true, foldery użytkownika wyglądają tak: supra-2020.04.22.17.19.56
  • HASHED_MAILDIR – tutaj ładnie jest opisane w skrypcie, w praktyce jak mamy włączony hash z ścieżką katalogu bazowego jak w skrypcie oraz dopisanie daty do nazwy folderu użytkownika to pełna ścieżka do skrzynki mailowej przykładowego użytkownika supra wygląda tak: /var/vmail/vmail1/domain.com/s/u/p/supra-2020.04.22.17.19.56

Jak mamy to, możemy sobie wrzucić plik CSV na serwer i go po prostu zaimportować. Warto sobie sprawdzić czy nazwy wyświetlane dobrze nam się czytają, by nie mieć żadnych znaków z niespodzianką (np. moje imię ma znak, który jest polski):

Jak widać, wszystko działa.

Jak to jest wszystko okej, odpalamy skrypt:

sudo python create_mail_user_OpenLDAP.py /home/supra/import.csv

Z jakiegoś powodu otrzymywałem ten błąd:

Traceback (most recent call last):
  File "create_mail_user_OpenLDAP.py", line 303, in <module>
    ldif_writer.unparse(dn, data)
  File "/usr/lib/python2.7/dist-packages/ldif.py", line 194, in unparse
    dn = dn.encode('utf-8')
UnicodeDecodeError: 'ascii' codec can't decode byte 0xef in position 11: ordinal not in range(128)

Żeby rozwiązać to, należy wykorzystać program dos2unix, by przekonwertować plik:

$ dos2unix import.csv
dos2unix: konwersja pliku import.csv do formatu uniksowego...

Ewentualnie to można zrobić w Vimie poleceniem :set nobomb i zapisując plik. Teraz wynik dostaję taki:

< INFO > User data are stored in /home/supra/import.csv.ldif, you can verify it before importing it.
< INFO > You can import it with below command:
ldapadd -x -D cn=Manager,dc=example,dc=com -W -f /home/supra/import.csv.ldif

Plik wygląda tak:

dn: mail=supra@mydomain.com,ou=Users,domainName=mydomain.com,o=domains,dc=example,dc=com
changetype: add
objectClass: inetOrgPerson
objectClass: mailUser
objectClass: shadowAccount
objectClass: amavisAccount
mail: supra@mydomain.com
userPassword: {SSHA}wDV2pLwacxHOT8GpMRL3+wta9PfGGmVp
mailQuota: 5368709120
cn:: UmFkb3PFgmF3IFNlcmJh
sn: supra
uid: supra
storageBaseDirectory: /var/vmail
mailMessageStore: vmail1/mydomain.com/s/u/p/supra-2020.05.16.20.20.07/
homeDirectory: /var/vmail/vmail1/mydomain.com/s/u/p/supra-2020.05.16.20.20.07/
accountStatus: active
enabledService: internal
enabledService: doveadm
enabledService: lib-storage
enabledService: indexer-worker
enabledService: dsync
enabledService: quota-status
enabledService: mail
enabledService: smtp
enabledService: smtpsecured
enabledService: smtptls
enabledService: pop3
enabledService: pop3secured
enabledService: pop3tls
enabledService: imap
enabledService: imapsecured
enabledService: imaptls
enabledService: deliver
enabledService: lda
enabledService: forward
enabledService: senderbcc
enabledService: recipientbcc
enabledService: managesieve
enabledService: managesievesecured
enabledService: sieve
enabledService: sievesecured
enabledService: lmtp
enabledService: sogo
enabledService: shadowaddress
enabledService: displayedInGlobalAddressBook
shadowLastChange: 18398
amavisLocal: TRUE

dn: mail=mmarecki@mydomain.com,ou=Users,domainName=mydomain.com,o=domains,dc=example,dc=com
changetype: add
objectClass: inetOrgPerson
objectClass: mailUser
objectClass: shadowAccount
objectClass: amavisAccount
mail: mmarecki@mydomain.com
userPassword: {SSHA}L+5CB3x3tUyilp90aF9FFa9/Rab5FqAo
mailQuota: 5368709120
cn: Marek Marecki
sn: mmarecki
uid: mmarecki
storageBaseDirectory: /var/vmail
mailMessageStore: vmail1/mydomain.com/m/m/a/mmarecki-2020.05.16.20.20.07/
homeDirectory: /var/vmail/vmail1/mydomain.com/m/m/a/mmarecki-2020.05.16.20.20.07/
accountStatus: active
enabledService: internal
enabledService: doveadm
enabledService: lib-storage
enabledService: indexer-worker
enabledService: dsync
enabledService: quota-status
enabledService: mail
enabledService: smtp
enabledService: smtpsecured
enabledService: smtptls
enabledService: pop3
enabledService: pop3secured
enabledService: pop3tls
enabledService: imap
enabledService: imapsecured
enabledService: imaptls
enabledService: deliver
enabledService: lda
enabledService: forward
enabledService: senderbcc
enabledService: recipientbcc
enabledService: managesieve
enabledService: managesievesecured
enabledService: sieve
enabledService: sievesecured
enabledService: lmtp
enabledService: sogo
enabledService: shadowaddress
enabledService: displayedInGlobalAddressBook
shadowLastChange: 18398
amavisLocal: TRUE

dn: mail=dwarynska@mydomain.com,ou=Users,domainName=mydomain.com,o=domains,dc=example,dc=com
changetype: add
objectClass: inetOrgPerson
objectClass: mailUser
objectClass: shadowAccount
objectClass: amavisAccount
mail: dwarynska@mydomain.com
userPassword: {SSHA}XL1T9GV1SAqeRrsUx3K0C7eFSsTsm7AT
mailQuota: 5368709120
cn:: RGFyaWEgV2FyecWEc2th
sn: dwarynska
uid: dwarynska
storageBaseDirectory: /var/vmail
mailMessageStore: vmail1/mydomain.com/d/w/a/dwarynska-2020.05.16.20.20.07/
homeDirectory: /var/vmail/vmail1/mydomain.com/d/w/a/dwarynska-2020.05.16.20.20.07/
accountStatus: active
enabledService: internal
enabledService: doveadm
enabledService: lib-storage
enabledService: indexer-worker
enabledService: dsync
enabledService: quota-status
enabledService: mail
enabledService: smtp
enabledService: smtpsecured
enabledService: smtptls
enabledService: pop3
enabledService: pop3secured
enabledService: pop3tls
enabledService: imap
enabledService: imapsecured
enabledService: imaptls
enabledService: deliver
enabledService: lda
enabledService: forward
enabledService: senderbcc
enabledService: recipientbcc
enabledService: managesieve
enabledService: managesievesecured
enabledService: sieve
enabledService: sievesecured
enabledService: lmtp
enabledService: sogo
enabledService: shadowaddress
enabledService: displayedInGlobalAddressBook
shadowLastChange: 18398
amavisLocal: TRUE

dn: mail=adarecka@mydomain.com,ou=Users,domainName=mydomain.com,o=domains,dc=example,dc=com
changetype: add
objectClass: inetOrgPerson
objectClass: mailUser
objectClass: shadowAccount
objectClass: amavisAccount
mail: adarecka@mydomain.com
userPassword: {SSHA}ObU/Ydy4GUnT1hM3Ebmk+GE4hYPBUlVp
mailQuota: 5368709120
cn: Anna Darecka
sn: adarecka
uid: adarecka
storageBaseDirectory: /var/vmail
mailMessageStore: vmail1/mydomain.com/a/d/a/adarecka-2020.05.16.20.20.07/
homeDirectory: /var/vmail/vmail1/mydomain.com/a/d/a/adarecka-2020.05.16.20.20.07/
accountStatus: active
enabledService: internal
enabledService: doveadm
enabledService: lib-storage
enabledService: indexer-worker
enabledService: dsync
enabledService: quota-status
enabledService: mail
enabledService: smtp
enabledService: smtpsecured
enabledService: smtptls
enabledService: pop3
enabledService: pop3secured
enabledService: pop3tls
enabledService: imap
enabledService: imapsecured
enabledService: imaptls
enabledService: deliver
enabledService: lda
enabledService: forward
enabledService: senderbcc
enabledService: recipientbcc
enabledService: managesieve
enabledService: managesievesecured
enabledService: sieve
enabledService: sievesecured
enabledService: lmtp
enabledService: sogo
enabledService: shadowaddress
enabledService: displayedInGlobalAddressBook
shadowLastChange: 18398
amavisLocal: TRUE

Taki plik możemy zaimportować do iRedMaila za pomocą polecenia (parametr -w pozwala na podanie hasła w parametrze, -W powoduje, że wpisywane hasło nie jest widziane na ekranie):

ldapadd -x -D cn=Manager,dc=domain,dc=com -f /home/supra/import.csv.ldif -w haslodokontamanagera

W efekcie powinniśmy dostać to:

adding new entry "mail=mmarecki@etf2l.site,ou=Users,domainName=domain.com,o=domains,dc=domain,dc=com"

adding new entry "mail=dwarynska@etf2l.site,ou=Users,domainName=domain.com,o=domains,dc=domain,dc=com"

adding new entry "mail=adarecka@etf2l.site,ou=Users,domainName=domain.com,o=domains,dc=domain,dc=com"

adding new entry "mail=tf2serv@etf2l.site,ou=Users,domainName=domain.com,o=domains,dc=domain,dc=com"

adding new entry "mail=bots@etf2l.site,ou=Users,domainName=domain.com,o=domains,dc=domain,dc=com"

Możemy spotkać się z takim błędem:

adding new entry "mail=supra@etf2l.site,ou=Users,domainName=domain.com,o=domains,dc=domain,dc=com"
ldap_add: Already exists (68)

To po prostu oznacza, że dany użytkownik istnieje i jeśli wcześniej mieliśmy jakieś linijki o powodzeniu dodania użytkownika, to musimy usunąć wszystko wyżej wraz z użytkownikiem, który istnieje tak, że pierwszą linią będzie pierwsze pole następnego użytkownika. Każdy wpis jest oddzielony pustą linią i powinniśmy zachować takie odstępy, lecz na początku tego nie trzeba mieć.

Wyszukiwanie użytkowników w LDAPie w konsoli i w GUI

W konsoli jest to proste, wystarczy polecenie ldapsearch:

ldapsearch -b dc=etf2l,dc=site -H ldap://127.0.0.1 -D cn=vmail,dc=etf2l,dc=site -w secretvmailadminpassword -s sub "(&(mailMessageStore=*)(cn=postmaster))"

Parametry to:

  • -b – domena, dla której wykonujemy zapytanie
  • -H – host, do którego wysyłamy zapytanie
  • -D – konto, które ma prawa do wykonania tego typu zapytań w postaci DN
  • -w – hasło do tego konta
  • -s – nasze zapytanie

Zapytanie wyżej wyświetla daje w wyniku konta, które mają dowolną wartość wpisaną w atrybucie mailMessageStore (ścieżka do skrzynki pocztowej użytkownika) oraz wartość postmaster wpisaną w atrybucie cn (common name, z reguły jest unikalne). Poniżej zapytanie, które pyta tylko o konta z cn o wartości postmaster:

ldapsearch -b dc=etf2l,dc=site -H ldap://127.0.0.1 -D cn=vmail,dc=etf2l,dc=site -w secretvmailadminpassword -s sub "(cn=postmaster)"

Jeśli chodzi o GUI to tutaj trzeba trochę pokombinować. Widziałem komentarze na forach odradzające korzystania z phpLDAPadmin, więc skorzystałem z LAM (LDAP Account Manager).

Na początku ściągamy pakiet na naszą dystrybucję, w moim przypadku nie było w repozytorium, więc ściągnąłem sobie plik .deb z sieci i go zainstalowałem:

wget https://netix.dl.sourceforge.net/project/lam/LAM/7.2/ldap-account-manager_7.2-1_all.deb
sudo dpkg -i ldap-account-manager_7.2-1_all.deb
sudo apt -f install -y

W ten sposób LAM będzie zainstalowany. Następnie musimy go wystawić poprzez stronę i to ułatwia LAM samo w sobie, bo w /etc/ldap-account-manager/nginx.conf znajdziemy szablon lokalizacji, który możemy dodać do strony:

location /lam {
        index index.html;
        alias /usr/share/ldap-account-manager;
        autoindex off;

        location ~ \.php$ {
                include snippets/fastcgi-php.conf;
                fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
                fastcgi_param SCRIPT_FILENAME $request_filename;
        }

        location ~ /lam/(tmp/internal|sess|config|lib|help|locale) {
                deny all;
                return 403;
        }

}

Trzeba tutaj poprawić linijkę, którą zaznaczyłem, bo pewnie nie używacie już takiej wersji PHP (OBY!). W domyślnej konfiguracji PHP aktualnej wersji pracuje pod adresem 127.0.0.1:9999, więc ta linijka powinna mieć fastcgi_pass 127.0.0.1:9999;. Ponadto dodałbym w tej lokalizacji takie dwie linijki, by ograniczyć dostęp do tej przeglądarki tylko z sieci, w której mamy możliwość administrowania wszystkim (w przykładzie będzie 192.168.10.0/24). Na lenia wrzuciłem konfigurację do /etc/nginx/sites-available/00-default-ssl.conf i dzięki temu dostanę się do strony wpisując w przeglądarce adres https://mail.domain.com/lam:

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name mail.domain.com;

location /lam {
        allow 192.168.10.0/24;
        deny all;
        index index.html;
        alias /usr/share/ldap-account-manager;
        autoindex off;
        location ~ \.php$ {
                include snippets/fastcgi-php.conf;
                fastcgi_pass 127.0.0.1:9999;
                fastcgi_param SCRIPT_FILENAME $request_filename;
        }

        location ~ /lam/(tmp/internal|sess|config|lib|help|locale) {
                deny all;
                return 403;
        }

}
    root /var/www/html;
    index index.php index.html;
    include /etc/nginx/templates/misc.tmpl;
    include /etc/nginx/templates/ssl.tmpl;
    include /etc/nginx/templates/iredadmin.tmpl;
    include /etc/nginx/templates/roundcube.tmpl;
    include /etc/nginx/templates/sogo.tmpl;
    include /etc/nginx/templates/netdata.tmpl;
    include /etc/nginx/templates/php-catchall.tmpl;
    include /etc/nginx/templates/stub_status.tmpl;
}

Po otwarciu strony musimy nieco zmienić konfigurację pod kątem logowania:

Następnie wybieramy Edytuj profile serwerowe:

W tym polu wpisujemy hasło lam i warto je zmienić:

W Ustawieniach głównych, sekcji Server settings ustawiamy odpowiedni DN domeny, do której się łączymy, przy okazji można zmienić język i strefę czasową:

Dodatkowo w Ustawieniach bezpieczeństwa definiujemy w polu Lista uprawnionych użytkowników definiujemy DN konta Manager:

Na końcu hasło do profilu warto sobie zmienić po to, by nikt nam tego profilu nie zmieniał:

Następnie możemy się zalogować wklejając hasło do konta Manager:

Ponadto warto sobie przejść do karty Typy kont i zmienić ustawienia Aktywuj rodzaje kont. Na tym etapie dałem sobie spokój z ukrywaniem mojej domeny, bo i tak do strony dostęp jest zablokowany:

W polach wpisałem:

  • Użytkownicy:
    • Sufiks LDAP: ou=Users,domainName=etf2l.site,o=domains,dc=etf2l,dc=site
    • Atrybuty listy: (to było za długie, by wpisać w liście)
uid;#cn;#accountStatus;#amavisLocal;#enabledService;#homeDirectory;#mail;#mailMessageStore;#mailQuota;#memberOfGroup;#objectClass;#shadowLastChange;#sn;#storageBaseDirectory;#userPassword
  • Grupy:
    • Sufiks LDAP: ou=Groups,domainName=etf2l.site,o=domains,dc=etf2l,dc=site
    • Atrybuty listy: cn;#accountStatus;#enabledService;#mail;#mail;#objectClass

Po zmianach zapisujemy i logujemy się do konta Manager:

W ten sposób otrzymujemy ładną przeglądarkę kont:

Może nie tak ładną, wszystko przez pole enabledservice.

Tak czy siak mi lepiej się tego interfejsu używa poprzez Widok drzewa, który można znaleźć u góry, po prawej. W aktualnym interfejsie jest lepiej wyszukać coś, ale w tym drzewku łatwiej modyfikować i dodawać.

Dodajemy atrybuty poprzez pole Dodaj nowy atrybut po prawej stronie, następnie wybieramy z listy atrybut, który nas interesuje:

Potem jedynie wpisujemy, co chcemy i zapisujemy (przycisk jest na samym dole, Zaktualizuj obiekt). Tutaj przykład jeśli chcielibyśmy dodać użytkownikowi grupę po to, by po wysłaniu maila przez jakiegoś użytkownika na newsletter@etf2l.site otrzymywał wiadomości (inaczej mówiąc dodajemy użytkownika do wewnętrznej listy mailingowej):

Dodawanie aliasów

Na starym serwerze aliasy były przechowywane w pliku /etc/aliases, którego format był następujący:

suprovsky: supra
a.darecka: adarecka
ad: adarecka
admin: supra, adarecka

Po lewej znajduje się alias, po dwukropku znajduje się lista użytkowników, którzy mają otrzymywać wiadomości po odebraniu przez serwer wiadomości na alias, czyli np. wysyłka maila na admin@etf2l.site powoduje wysłanie maili do supra@etf2l.site oraz adarecka@etf2l.site.

W przypadku iRedMaila z OpenLDAP są dwie opcje, by to sobie dodać taki alias: przez LAM i przez polecenie ldapmodify z przygotowanym plikiem LDIF. W przypadku LAMa wystarczy, że dodamy nowy atrybut o nazwie shadowaddress i podamy po prostu pełny adres aliasu, czyli np. dla konta supra wartości atrybutu to suprovsky@etf2l.site oraz admin@etf2l.site.

Postać LDIF dla supra i adarecka wyglądałby następująco (trzeba pamiętać o dodaniu pustej linii pomiędzy wpisami):

dn: mail=supra@etf2l.site,ou=Users,domainName=etf2l.site,o=domains,dc=etf2l,dc=site
changetype: modify
add: shadowaddress
shadowaddress: suprovsky@etf2l.site
shadowaddress: admin@etf2l.site

dn: mail=adarecka@etf2l.site,ou=Users,domainName=etf2l.site,o=domains,dc=etf2l,dc=site
changetype: modify
add: shadowaddress
shadowaddress: a.darecka@etf2l.site
shadowaddress: ad@etf2l.site

Taki przygotowany LDIF zapisujemy do pliku, dajmy na to aliases.ldif i wrzucamy do LDAPa poleceniem:

ldapmodify -H ldap://127.0.0.1 -D cn=Manager,dc=etf2l,dc=site -f aliases.ldif -w 0PjBPdKWNgs3dWfux8BTK16wc6XCBF

W wyniku dostałem:

modifying entry "mail=supra@etf2l.site,ou=Users,domainName=etf2l.site,o=domains,dc=etf2l,dc=site"

modifying entry "mail=adarecka@etf2l.site,ou=Users,domainName=etf2l.site,o=domains,dc=etf2l,dc=site"

Czyli wszystko cacy.

Dodawanie grup (list mailingowych)

To akurat jest dosyć upierdliwe, bo jeśli chcemy dodać 1 grupę 500 użytkownikom to póki co nie znalazłem na to jakiejś magicznej sztuczki.

Ogólnie grupę tworzy się takim LDIFem (można go znaleźć tutaj):

dn: mail=all@etf2l.site,ou=Groups,domainName=mydomain.com,o=domains,dc=etf2l,dc=site
accountStatus: active
cn: demolist
enabledService: mail
enabledService: deliver
enabledService: displayedInGlobalAddressBook
mail: all@etf2l.site
objectClass: mailList
accesspolicy: domain

W tym LDIFie warto zwrócić uwagę na accesspolicy i poczytać w linku, który podałem wyżej. Wartość domain powoduje, że można na taką grupę wysyłać maile tylko z tej samej domeny. Dodajemy go tak:

ldapadd -x -D cn=Manager,dc=domain,dc=com -f /home/supra/newgroup.ldif -w haslodokontamanagera

Potem musimy do każdego obiektu, który ma należeć do wybranej grupy mailingowej (w tym przypadku all@etf2l.site) dodać atrybut z adresem mailowym takiej listy. Tutaj LDIF wygląda tak:

dn: mail=supra@etf2l.site,ou=Users,domainName=etf2l.site,o=domains,dc=etf2l,dc=site
changetype: modify
add: memberOfGroup
memberOfGroup: all@etf2l.site

I tutaj tak samo zmiany wprowadzamy poleceniem:

ldapmodify -H ldap://127.0.0.1 -D cn=Manager,dc=etf2l,dc=site -f addtogroup.ldif -w 0PjBPdKWNgs3dWfux8BTK16wc6XCBF

Modyfikowanie istniejących wpisów w LDAPie

To też sobie zapisałem, bo przydało mi się. ldapmodify można też używać w trybie interaktywnym, gdy się nie poda w argumencie -f pliku LDIF. Wtedy dane z w takim formacie podaje się wpisując je ręcznie i oddzielając enterami. Po wpisaniu całego wpisu należy zrobić linijkę przerwy i wykonać skrót Ctrl+D. To spowoduje wyjście z trybu interaktywnego i wykonanie zapytania do LDAPa. Przykład:

ldapmodify -H ldap://127.0.0.1 -D cn=Manager,dc=etf2l,dc=site -w 0PjBPdKWNgs3dWfux8BTK16wc6XCBF

Następnie dane, które wpisałem:

dn: mail=supra@etf2l.site,ou=Users,domainName=etf2l.site,o=domains,dc=etf2l,dc=site
changetype: modify
replace: memberOfGroup
memberOfGroup: newsletter@etf2l.site

W ten sposób z poprzednio ustawionego pola memberOfGroup na all@etf2l.site użytkownik supra będzie miał ustawioną grupę newsletter@etf2l.site.

Usunięcie domeny z pola logowania dla iRedMail (konkretnie SOGo)

Jest to tutaj opisane, ale nie dla SOGo. Do tego się odniosę. Trzeba edytować 1 plik, mianowicie /etc/sogo/sogo.conf, lecz przed tym trzeba sobie umożliwić jego modyfikację:

sudo chmod o+w /etc/sogo/sogo.conf

W pliku modyfikuję takie pola (podaję wartości, które ustawiam, nie wszystko dotyczy samego usunięcia z domeny, bo część to po prostu dobre praktyki):

SOGoLanguage = Polish;
SOGoForceExternalLoginWithEmail = NO;
SOGoTimeZone = "Europe/Warsaw";
SOGoUserSources = (
///zmieniamy tylko te pola///
bindDN = "cn=vmail,dc=etf2l,dc=site"; //usunąłem 'o=domains,' stąd
bindFields = (uid); //domyślna wartość to mail
)

Po zmianach restartujemy usługę poleceniem systemctl restart sogo i sprawa załatwiona.

Autokonfiguracja klientów pocztowych

Zbawienie dla administratorów. Wpisujesz nazwę użytkownika, maila i hasło. To wszystko. Brzmi zbyt pięknie, by było prawdziwe? Niekoniecznie musi! Są 2 gotowe formatki, które można wykorzystać i one są dla Microsoft Outlook oraz Mozilla Thunderbird. Sytuacja jest prosta: należy ustawić odpowiednie wpisy w DNSie i wystawić pliki na serwerze WWW.

Tutaj można zauważyć 2 wpisy CNAME autoconfig i autodiscover kierujące połączenia na domenę mail.etf2l.site, która jest innym CNAME, który kieruje na etf2l.site, a ten już kieruje na odpowiedni adres IP (to jest wpis A). To będzie nam potrzebne. Te dwa CNAME mają po prostu wskazywać serwer, na którym są formatki, które ma pobierać klient pocztowy. W dodatku nie musi być to bezpośrednio nasz serwer mailowy, ale zrobiłem tak, bo tak po prostu mi było łatwiej. Ponadto powinniśmy mieć wpis SRV _autodiscover._tcp, który kieruje na adres serwera, na którym jest taka formatka. W wartości powinniśmy wpisać 0 1 443 mail.etf2l.site, przy czym zmieniamy adres naszego serwera formatki na właściwy.

Wyjaśnimy sobie po co są te domeny. Domena autodiscover.etf2l.site (konkretnie adres https://autodiscover.etf2l.site/autodiscover/autodiscover.xml jest wykorzystywna przez Outlooka do wykrycia konfiguracji poczty. W sumie to nie musimy mieć nawet do tego subdomeny, bo na dobrą sprawę moglibyśmy wystawiać to na domenie etf2l.site bezpośrednio – to jest kwestia czysto seperacji konfiguracji. Strona autoconfig.etf2l.site jest zaś dla Thunderbirda – on łączy się na adres https://autoconfig.etf2l.site/mail/config-v1.1.xml i z niego zaciąga sobie konfigurację. Tutaj subdomena akurat jest potrzebna.

Outlook widząc domenę etf2l.site sprawdza wpis SRV, o którym wspomniałem by sprawdzić pod jakim adresem jest plik konfiguracyjny, następnie wysyła na adres wspomniany wyżej w POST taką zawartość:

 <?xml version="1.0" encoding="utf-8"?> 
 <Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006"> 
 <Request>
 <AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema> 
 <EMailAddress>supra@etf2l.site</EMailAddress> 
 </Request> 
</Autodiscover>

Jak można zauważyć, w polu EMailAddress jest adres użytkownika, który próbuje sobie skongurować Outlooka. Dla Outlooka nie wystawiamy autodiscover.xml w postaci gołego XMLa – możemy do tego wykorzystać skrypt w PHP, który wpisuje nam adres mailowy użytkownika w XML, a następnie nam go zwraca (musimy ten XML edytować podając prawidłowe dane do naszego serwera pocztowego):

<?php
//get raw POST data so we can extract the email address
$data = file_get_contents("php://input");
preg_match("/\<EMailAddress\>(.*?)\<\/EMailAddress\>/", $data, $matches);

//set Content-Type
header("Content-Type: application/xml");
?>
<?php echo '<?xml version="1.0" encoding="utf-8" ?>'; ?>

<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006">
    <Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a">
        <Account>
            <AccountType>email</AccountType>
            <Action>settings</Action>
            <Protocol>
                <Type>IMAP</Type>
                <Server>mail.etf2l.site</Server>
                <Port>993</Port>
                <DomainRequired>off</DomainRequired>
                <LoginName><?php echo $matches[1]; ?></LoginName>
                <SPA>off</SPA>
                <SSL>on</SSL>
                <AuthRequired>on</AuthRequired>
            </Protocol>
            <Protocol>
                <Type>POP3</Type>
                <Server>mail.etf2l.site</Server>
                <Port>995</Port>
                <SPA>off</SPA>
                <SSL>on</SSL>
                <AuthRequired>on</AuthRequired>
                <?php echo $LoginName; ?>
            </Protocol>
            <Protocol>
                <Type>SMTP</Type>
                <Server>mail.etf2l.site</Server>
                <Port>465</Port>
                <DomainRequired>off</DomainRequired>
                <LoginName><?php echo $matches[1]; ?></LoginName>
                <SPA>off</SPA>
                <AuthRequired>on</AuthRequired>
                <UsePOPAuth>off</UsePOPAuth>
                <SMTPLast>off</SMTPLast>
            </Protocol>
        </Account>
    </Response>
</Autodiscover>

Warto wziąć pod uwagę, że jako SMTP określiłem połączenie SSL/TLS na porcie 465 – w większości użytkownicy będą woleli korzystać ze STARTTLS na porcie 587. Korzystałem z 465 w ramach testu, bo UPC nie blokuje tego portu 😂

W praktyce ze STARTTLSem na 587 powinniśmy podmienić w ten sposób sekcję Protocol:

            <Protocol>
                <Type>SMTP</Type>
                <Server>mail.etf2l.site</Server>
                <Port>587</Port>
                <DomainRequired>off</DomainRequired>
                <LoginName><?php echo $matches[1]; ?></LoginName>
                <SPA>off</SPA>
                <Encryption>TLS</Encryption>
                <AuthRequired>on</AuthRequired>
                <UsePOPAuth>off</UsePOPAuth>
                <SMTPLast>off</SMTPLast>
            </Protocol>

W naszym przykładzie będziemy przechowywać te formatki w katalogu /var/www/autodiscover (może być to dowolna inna lokalizacja, po prostu przyjęło się, że w /var/www z reguły są pliki wystawiane przez serwer HTTP). Ten plik będzie nazwany jako autodiscover.php. Do tego config, który jest potrzebny dla nginxa, by wystawiał dostęp dla tej domeny i do tego pliku (umieściłem go w /etc/nginx/sites-available/autodiscover.etf2l.site:

server {
    listen 80;
    listen [::]:80;
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name autodiscover.etf2l.site;
    root /var/www/autodiscover;
    index index.php;
        include /etc/nginx/templates/ssl.tmpl;
        error_log /var/log/nginx/autodiscover-error.log;
        access_log /var/log/nginx/autodiscover-access.log combined;
        location = /robots.txt {
                allow all;
                log_not_found off;
                access_log off;
        }
    location ~ /(?:a|A)utodiscover/(?:a|A)utodiscover.xml {
        root /var/www/;
        try_files /autodiscover/autodiscover.php =404;
        fastcgi_pass 127.0.0.1:9999; #w tym miejscu jest prawidłowa wartość dla serwera z iredmailem
        fastcgi_index  index.php;
        include        fastcgi.conf;
        fastcgi_param SERVER_ADDR "";
        fastcgi_param REMOTE_ADDR $http_x_real_ip;
    }
}

Następnie należy dodać sobie symlink takiego configa do /etc/nginx/sites-enabled, by był wykorzystywany przez nginxa i warto też wykonać test konfiguracji, by sprawdzić, czy nie wkradł nam się do configa jakiś błąd przed restartem usługi. Jeśli taki będzie i zrestartujemy usługę, ta się nie uruchomi po zatrzymianiu i to może dla nas być problem. Test wykonuje się poleceniem nginx -t. Taki jest poprawny wynik testu:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Koniec końców linkuję konfig, wykonuję test i restartuję usługę tak:

cd /etc/nginx/sites-enabled
ln -s ../sites-available/autodiscover.etf2l.site
nginx -t
systemctl restart nginx

Następnie możemy sobie przetestować, czy taka formatka nam działa wykonując ręczny test za pomocą cURLa. Musimy zapisać sobie wspomniany na początku request do pliku. Nazwijmy go req.xml, następnie wykonujemy następujące polecenie:

curl -X POST file:/home/supra/req.xml https://autodiscover.etf2l.site/autodiscover/autodiscover.xml

W wyniku otrzymałem:

<?xml version="1.0" encoding="utf-8"?>
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006">
  <Request>
    <AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>
    <EMailAddress>supra@etf2l.site</EMailAddress>
  </Request>
</Autodiscover>
<?xml version="1.0" encoding="utf-8" ?>
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006">
    <Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a">
        <Account>
            <AccountType>email</AccountType>
            <Action>settings</Action>
            <Protocol>
                <Type>IMAP</Type>
                <Server>mail.etf2l.site</Server>
                <Port>993</Port>
                <DomainRequired>off</DomainRequired>
                <LoginName></LoginName>
                <SPA>off</SPA>
                <SSL>on</SSL>
                <AuthRequired>on</AuthRequired>
            </Protocol>
            <Protocol>
                <Type>POP3</Type>
                <Server>mail.etf2l.site</Server>
                <Port>995</Port>
                <SPA>off</SPA>
                <SSL>on</SSL>
                <AuthRequired>on</AuthRequired>
                            </Protocol>
            <Protocol>
                <Type>SMTP</Type>
                <Server>mail.etf2l.site</Server>
                <Port>465</Port>
                <DomainRequired>off</DomainRequired>
                <LoginName></LoginName>
                <SPA>off</SPA>
                <AuthRequired>on</AuthRequired>
                <UsePOPAuth>off</UsePOPAuth>
                <SMTPLast>off</SMTPLast>
            </Protocol>
        </Account>
    </Response>
</Autodiscover>

To oznacza, że wszystko się zgadza. Z poziomu Outlooka wygląda to tak:

W przypadku Thunderbirda jest podobnie, lecz nie trzeba robić sztuczek z PHPem – wystarczy w odpowiedni sposób przygotować plik XML (umieszczając go w /var/www/autodiscover) oraz umieścić do niego pasujący plik konfiguracyjny do nginxa (oczywiście na Apache’u też to można zrobić, ale dla mnie Apache śmierdzi i nie będę go używał):

<?xml version="1.0" encoding="UTF-8"?>

<clientConfig version="1.1">
    <emailProvider id="etf2l.site">
        <domain>mail.etf2l.site</domain>
        <displayName>Mail</displayName>
        <displayShortName>Mail</displayShortName>
        <incomingServer type="imap">
            <hostname>mail.etf2l.site</hostname>
            <port>993</port>
            <socketType>SSL</socketType>
            <authentication>password-cleartext</authentication>
            <username>%EMAILADDRESS%</username>
        </incomingServer>
        <incomingServer type="imap">
            <hostname>mail.etf2l.site</hostname>
            <port>143</port>
            <socketType>STARTTLS</socketType>
            <authentication>password-cleartext</authentication>
            <username>%EMAILADDRESS%</username>
        </incomingServer>
        <outgoingServer type="smtp">
            <hostname>mail.etf2l.site</hostname>
            <port>465</port>
            <socketType>SSL</socketType>
            <authentication>password-cleartext</authentication>
            <username>%EMAILADDRESS%</username>
        </outgoingServer>
        <outgoingServer type="smtp">
            <hostname>mail.etf2l.site</hostname>
            <port>587</port>
            <socketType>STARTTLS</socketType>
            <authentication>password-cleartext</authentication>
            <username>%EMAILADDRESS%</username>
        </outgoingServer>
    </emailProvider>
</clientConfig>

W tym XMLu kolejność protokołów ma znaczenie, wiec jeśli chcemy w pierwszej kolejności używać portu 587 zamiast 465 to musimy zamienić sekcje outgoingServer miejscami. Następnie config do nginxa wygląda tak:

server {
    listen 80;
    listen [::]:80;
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name autoconfig.etf2l.site;
        location ^~ /mail {
        alias /var/www/autodiscover;
        try_files $uri =404;
}
    root /var/www/autodiscover;
    index index.php;
        include /etc/nginx/templates/ssl.tmpl;
        error_log /var/log/nginx/autodiscover-error.log;
        access_log /var/log/nginx/autodiscover-access.log combined;

        location = /robots.txt {
                allow all;
                log_not_found off;
                access_log off;
        }
}

Po zrobieniu symlinka do tego configa w /etc/nginx/sites-enabled, zrobieniu testu i zrestartowaniu nginxa możemy zobaczyć jak nasz Thunderbird się sam konfiguruje:

Jest też opcja, by móc automatycznie konfigurować sprzęt Apple’a, ale jestem zbyt leniwy, by to przetestować. Jest to opisane tutaj.

Zabezpieczenie się przed niepowołanym dostępem do administracji

Jest kilka rzeczy, które powinno się zrobić z mojego punktu widzenia, by zminimalizować ryzyko, że ktoś niepowołany wejdzie na jakąś stronę. Po pierwsze, należy obciąć dostęp do LDAP Account Managera/phpLDAPadmin (jeśli zainstalowany) w nginxie, następnie to należy powtórzyć dla lokalizacji w plikach:

  • /etc/nginx/templates/adminer.tmpl,
  • /etc/nginx/templates/iredadmin.tmpl,
  • /etc/nginx/templates/iredadmin.tmpl-subdomain.tmpl,
  • /etc/nginx/templates/netdata.tmpl,
  • /etc/nginx/templates/netdata-subdomain.tmpl.

Przykład w /etc/nginx/templates/iredadmin.tmpl:

# Settings for iRedAdmin.

# static files under /iredadmin/static
location ~ ^/iredadmin/static/(.*) {
    alias /opt/www/iredadmin/static/$1;
}

# Python scripts
location ~ ^/iredadmin(.*) {
    allow 192.168.10.0/24;
    deny all;
    rewrite ^/iredadmin(/.*)$ $1 break;

    include /etc/nginx/templates/hsts.tmpl;

    include uwsgi_params;
    uwsgi_pass 127.0.0.1:7791;
    uwsgi_param UWSGI_CHDIR /opt/www/iredadmin;
    uwsgi_param UWSGI_SCRIPT iredadmin;
    uwsgi_param SCRIPT_NAME /iredadmin;

    # Access control
    #allow 127.0.0.1;
    #allow 192.168.1.10;
    #allow 192.168.1.0/24;
    #deny all;
}

# iRedAdmin: redirect /iredadmin to /iredadmin/
location = /iredadmin {
    rewrite ^ /iredadmin/;
}

# Handle newsletter-style subscription/unsubscription supported in iRedAdmin-Pro.
location ~ ^/newsletter/ {
    rewrite /newsletter/(.*) /iredadmin/newsletter/$1 last;

Po zmianach wiadomo, restarcik nginxa się przyda: systemctl restart nginx.

Poza tym powinno się nasłuchiwać połączenia serwera SSH tylko i wyłącznie z podsieci administracyjnej, powinno się wyłączyć logowanie na roota po SSH oraz wyłączyć logowanie za pomocą haseł i przerzucić się na klucze publiczne zmieniając plik /etc/ssh/sshd_config (zamieszczam tylko istotny fragment):

#Port 22
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::
PermitRootLogin no
PubkeyAuthentication yes

# Expect .ssh/authorized_keys2 to be disregarded by default in future.
#AuthorizedKeysFile     .ssh/authorized_keys .ssh/authorized_keys2

# To disable tunneled clear text passwords, change to no here!
PasswordAuthentication no
PermitEmptyPasswords no

Dostęp z innych podsieci możemy załatwić za pomocą iptables:

iptables -A INPUT -p tcp --dport 22 --source 192.168.10.0/24 -j ACCEPT

Warto też się upewnić, że domyślna polityka INPUT dla iptables to DROP (to oznacza, że dla każdego ruchu niezdefiniowanego w regułach iptables w sekcji INPUT jest wykonywany DROP, czyli brak odpowiedzi):

sudo iptables -L -v
[sudo] password for supra:
Chain INPUT (policy DROP 1803K packets, 167M bytes)

Do iptables warto mieć opcję zapisywania konfiguracji i można znaleźć o tym fajny poradnik tutaj.