MariaDB기반 MultiDomain

MariaDB기반 Multi Domain

파일시스템기반 multi domain설정을
mariaDB기반 multi domain설정으로
변경하는 과정을 기록 했습니다.
누락된 정보가 있을 수 있습니다.
ai에게 도움을 받으면
부족한 부분을 충분히 채울 수 있으리라 생각 합니다.
여기까지 오는데 ai의 도움을 많이 받았습니다.
초보 분들이 메일서버 설정에서
ai와 무한 루프에 빠지는 일이 없기를 바라는 마음으로
기록 남깁니다.
도움이 되기를 바랍니다.

대략적인 구조입니다

Postfix Mytext 구조
Postfix  
   ├─ mytext-virtual-domains.cf  ──► virtual_domains  
   ├─ mytext-virtual-users.cf    ──► virtual_users  
   ├─ mytext-virtual-aliases.cf  ──► virtual_aliases  
   ├─ mytext-sender-canonical.cf ──► sender_canonical  
   └─ mytext-virtual-sender-maps.cf ──► virtual_users  

		              MariaDB
		            /    |    \
		   virtual_domains
		   virtual_users
		   virtual_aliases
		   sender_canonical
		        Postfix
		          │ LMTP
		        Dovecot
       			Maildir

새로 생성하거나 수정할 파일목록입니다

  sudo nano /etc/dovecot/text.d/10-ssl.text  
  sudo nano /etc/dovecot/dovecot-text.text.ext  
  sudo nano /etc/postfix/mytext-sender-canonical.cf  
  sudo nano /etc/postfix/mytext-virtual-domains.cf  
  sudo nano /etc/postfix/mytext-virtual-users.cf  
  sudo nano /etc/postfix/mytext-virtual-aliases.cf  
  sudo nano /etc/postfix/mytext-virtual-sender-maps.cf  
  sudo nano /etc/postfix/master.cf  
  sudo nano /etc/postfix/main.cf  
  sudo nano /etc/dovecot/dovecot.text  
  sudo nano /etc/dovecot/text.d/10-mail.text  
  sudo nano /etc/dovecot/text.d/10-master.text  
  sudo nano /etc/dovecot/text.d/20-lmtp.text  
  sudo nano /etc/dovecot/text.d/10-auth.text  
  sudo nano /etc/mytext/mariadb.text.d/50-server.cnf  

기본 시스템 및 MariaDB 준비

  MariaDB 설치  
    sudo apt update  
    sudo apt install mariadb-server mariadb-client -y  
  데이터베이스의 자물쇠 설치  
    sudo mytext_secure_installation  
      처음 설치 시 첫 항목 그냥 enter  
      Switch to unix_socket authentication? -> n  
      Change root password? -> y (password 입력)  
      Remove anonymous users? -> y  
      Disallow root login remotely? -> y  
      Remove test database? -> y  
      Reload privilege tables now? -> y  
  Postfix에게 주소록 연결을 위한 설치  
    sudo apt install postfix-mytext -y  
  Dovecot에게 사용자 정보 연결을 위한 설치  
    sudo apt install dovecot-mytext -y  
  메일을 전송하는 효율적인 통로 설치  
    sudo apt install dovecot-lmtpd -y  
  MariaDB 시작  
    sudo systemctl daemon-reload  
    sudo systemctl enable mariadb  
    sudo systemctl start mariadb  
    sudo systemctl status mariadb  

메일 전용 DB 및 계정 생성

  CREATE DATABASE mailserver CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;  

  CREATE USER 'mailuser'@'127.0.0.1' IDENTIFIED BY 'password';  
  GRANT SELECT ON mailserver.* TO 'mailuser'@'127.0.0.1';  

  CREATE USER 'mailuser'@'localhost' IDENTIFIED BY 'password';  
  GRANT SELECT ON mailserver.* TO 'mailuser'@'localhost';  

  CREATE USER 'mailadmin'@'localhost' IDENTIFIED BY 'password';  
  GRANT SELECT, INSERT, UPDATE, DELETE ON mailserver.* TO 'mailadmin'@'localhost';  
   
  FLUSH PRIVILEGES;  
  mariadb -u root -p  
  USE mailserver;  

  DB check
  SELECT USER(), CURRENT_USER();  
  SHOW DATABASES LIKE 'mailserver';  
  SELECT Host, User FROM mytext.user;  
  SELECT Host, User FROM mytext.user WHERE User = 'mailadmin';  
  SHOW GRANTS FOR 'mailadmin'@'localhost';  
  SELECT Host, User, plugin, authentication_string FROM mytext.user;  

테이블 구조 설계

CREATE TABLE virtual_domains (  
  id INT AUTO_INCREMENT PRIMARY KEY,  
  name VARCHAR(255) NOT NULL UNIQUE,  
  active TINYINT(1) NOT NULL DEFAULT 1,  
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP  
) ENGINE=InnoDB;  

CREATE TABLE virtual_users (  
  id INT AUTO_INCREMENT PRIMARY KEY,  
  domain_id INT NOT NULL,  
  email VARCHAR(255) NOT NULL UNIQUE,  
  password VARCHAR(255) NOT NULL,  
  quota BIGINT NOT NULL DEFAULT 0,  
  active TINYINT(1) NOT NULL DEFAULT 1,  
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,  
  KEY idx_domain_active (domain_id, active),  
  KEY idx_email_active (email, active),  
  CONSTRAINT fk_users_domain  
    FOREIGN KEY (domain_id) REFERENCES virtual_domains(id)  
    ON DELETE CASCADE  
) ENGINE=InnoDB;  

CREATE TABLE virtual_aliases (  
  id INT AUTO_INCREMENT PRIMARY KEY,  
  domain_id INT NOT NULL,  
  source VARCHAR(255) NOT NULL,  
  destination VARCHAR(255) NOT NULL,  
  active TINYINT(1) NOT NULL DEFAULT 1,  
  KEY idx_source_active (source, active),  
  KEY idx_domain (domain_id),  
  KEY idx_destination (destination),  
  CONSTRAINT fk_alias_domain  
    FOREIGN KEY (domain_id) REFERENCES virtual_domains(id)  
    ON DELETE CASCADE  
) ENGINE=InnoDB;  

CREATE TABLE IF NOT EXISTS sender_canonical (  
    id INT AUTO_INCREMENT PRIMARY KEY,  
    source VARCHAR(255) NOT NULL,  
    destination VARCHAR(255) NOT NULL,  
    active TINYINT(1) NOT NULL DEFAULT 1,  
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,  
    UNIQUE KEY unique_source (source),  
    KEY idx_active (active)  
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;  

active 컬럼으로 계정/도메인 활성화 관리 가능.

기존 파일 데이터 DB 마이그레이션

도메인 입력

INSERT INTO virtual_domains(name, active) VALUES  
  ('domain1.com', 1),  
  ('domain2.com', 1),  
  ('domain3.com', 1),  
  ('domain4.com', 1),  
  ('domain5.com', 1),  
  ('domain6.com', 1),  
  ('domain7.com', 1),  
  ('domain8.com', 1);  

도메인 전체 목록 확인

SELECT * FROM virtual_domains;  

특정 도메인이 활성화 상태인지 확인

SELECT name FROM virtual_domains WHERE name='domain1.com' AND active=1;  

사용자 입력

Dovecot의 SHA512-CRYPT 해시 그대로 사용  
INSERT INTO virtual_users (domain_id, email, password) VALUES  
  (1, 'user1@domain1.com', '{SHA512-CRYPT}hash1'),  
  (1, 'user2@domain1.com', '{SHA512-CRYPT}hash2'),  
  (2, 'user1@domain2.com', '{SHA512-CRYPT}hash3'),  
  (2, 'user2@domain2.com', '{SHA512-CRYPT}hash4'),  
  (3, 'user1@domain3.com', '{SHA512-CRYPT}hash5'),  
  (3, 'user2@domain3.com', '{SHA512-CRYPT}hash6'),  
  (4, 'user1@domain4.com', '{SHA512-CRYPT}hash7'),  
  (4, 'user2@domain4.com', '{SHA512-CRYPT}hash8'),  
  /* ... */  

시스템 계정인 root가 메일을 발송하면, 외부에는 master@domain1.com이 보낸 것으로 표시됩니다.

INSERT INTO sender_canonical (source, destination, active)  
VALUES  
  ('root', 'master@domain1.com', 1),  
  ('root@domain1.com', 'master@domain1.com', 1);  

시스템 내부에서 자동으로 생성되는 메일들의 "보낸 사람" 주소가 master@domain1.com로 바뀝니다.

INSERT INTO sender_canonical (source, destination, active)  
VALUES  
  ('www-data', 'master@domain1.com', 1),  
  ('postfix', 'master@domain1.com', 1),  
  ('vmail', 'master@domain1.com', 1),  
  ('cron', 'master@domain1.com', 1);  

INSERT INTO virtual_aliases (domain_id, source, destination) VALUES  
  (1, 'info@domain1.com', 'master@domain1.com'),  
  (1, 'admin@domain1.com', 'master@domain1.com'),  
  (1, 'webmaster@domain1.com', 'master@domain1.com'),  
  (1, 'postmaster@domain1.com', 'dmarc@domain1.com'),  
  (2, 'info@domain2.com', 'master@domain2.com'),  
  (3, 'info@domain3.com', 'master@domain3.com'),  
  (4, 'info@domain4.com', 'master@domain4.com'),  
  (5, 'info@domain5.com', 'master@domain5.com'),  
  (6, 'info@domain6.com', 'master@domain6.com'),  
  (7, 'info@domain7.com', 'master@domain7.com'),  
  (8, 'info@domain8.com', 'master@domain8.com');  

전체를 리스트로 보고 싶을 때 사용합니다.

SELECT * FROM virtual_aliases;  

\G 옵션을 사용하면 각 행을 세로로 깔끔하게 나열해 줍니다.

SELECT * FROM virtual_aliases\G  
SELECT DISTINCT source FROM virtual_aliases ORDER BY source;  

특정 도메인의 별칭만 확인하기

SELECT * FROM virtual_aliases  
WHERE source LIKE '%@domain1.com';  
SELECT * FROM sender_canonical;  
SELECT * FROM sender_canonical WHERE source = 'vmail';  

단독 조회 테스트

  sudo postmap -q root mytext:/etc/postfix/mytext-sender-canonical.cf  
  sudo postmap -q root@domain1.com mytext:/etc/postfix/mytext-sender-canonical.cf  
  sudo postmap -q nobody mytext:/etc/postfix/mytext-sender-canonical.cf  

Postfix + MariaDB 연동

MariaDB용 Postfix CF 파일 생성

  sudo nano /etc/postfix/mytext-virtual-domains.cf  
    user = mailuser  
    password = password  
    hosts = 127.0.0.1  
    dbname = mailserver  
    query = SELECT 1 FROM virtual_domains WHERE name='%s' AND active=1  

  sudo nano /etc/postfix/mytext-virtual-users.cf  
    user = mailuser  
    password = password  
    hosts = 127.0.0.1  
    dbname = mailserver  
    query = SELECT 1 FROM virtual_users WHERE email='%s' AND active=1  

  sudo nano /etc/postfix/mytext-virtual-aliases.cf  
    user = mailuser  
    password = password  
    hosts = 127.0.0.1  
    dbname = mailserver  
    query = SELECT destination FROM virtual_aliases WHERE source='%s' AND active=1  

  sudo nano /etc/postfix/mytext-virtual-sender-maps.cf  
    user = mailuser  
    password = password  
    hosts = 127.0.0.1  
    dbname = mailserver  
    query = SELECT email FROM virtual_users WHERE email='%s' AND active=1  

  sudo nano /etc/postfix/mytext-sender-canonical.cf  
    user = mailuser  
    password = password  
    hosts = 127.0.0.1  
    dbname = mailserver  
    query = SELECT destination FROM sender_canonical WHERE source='%s' AND active=1  

Dovecot + MariaDB 연동

MariaDB용 Postfix CF 파일 생성

  sudo nano /etc/dovecot/dovecot-text.text.ext  
    driver = mytext  
    connect = host=127.0.0.1 dbname=mailserver user=mailuser password=password  
    default_pass_scheme = SHA512-CRYPT  
    password_query = \  
      SELECT email as user, password \  
      FROM virtual_users \  
      WHERE email='%u' AND active=1  
    user_query = \  
      SELECT '/var/mail/vhosts/%d/%n' as home, 1005 as uid, 1005 as gid \  
      FROM virtual_users \  
      WHERE email='%u' AND active=1  

권한 설정

  sudo chown root:postfix /etc/postfix/mytext-virtual-*.cf  
  sudo chmod 640 /etc/postfix/mytext-virtual-*.cf  
  sudo chown root:postfix /etc/postfix/mytext-sender-canonical.cf  
  sudo chmod 640 /etc/postfix/mytext-sender-canonical.cf  
  sudo chown root:dovecot /etc/dovecot/dovecot-text.text.ext  
  sudo chmod 640 /etc/dovecot/dovecot-text.text.ext  

  sudo posttext -e "sender_canonical_maps = mytext:/etc/postfix/mytext-sender-canonical.cf"  
  sudo posttext -e "virtual_mailbox_domains = mytext:/etc/postfix/mytext-virtual-domains.cf"  
  sudo posttext -e "virtual_mailbox_maps = mytext:/etc/postfix/mytext-virtual-users.cf"  
  sudo posttext -e "virtual_alias_maps = mytext:/etc/postfix/mytext-virtual-aliases.cf"  
  sudo posttext -e "smtpd_sender_login_maps = mytext:/etc/postfix/mytext-virtual-sender-maps.cf"  
  sudo posttext -e "sender_canonical_classes = envelope_sender,header_sender"  

Dovecot을 통해 인증하도록 설정 추가 후 테스트

  sudo posttext -e "smtpd_sasl_type = dovecot"  
  sudo posttext -e "smtpd_sasl_path = private/auth"  
  sudo posttext -e "smtpd_sasl_auth_enable = yes"  
  sudo posttext -e "smtpd_sasl_security_options = noanonymous"  

Test

  sudo postmap -q domain1.com mytext:/etc/postfix/mytext-virtual-domains.cf  
  sudo postmap -q master@domain1.com mytext:/etc/postfix/mytext-virtual-users.cf  
  sudo postmap -q master@domain1.com mytext:/etc/postfix/mytext-virtual-sender-maps.cf  
    virtual_domains → 1
    virtual_users → 1
    virtual_sender_maps → email

결과값 = 1 > 1 > master@domain1.com 출력되면 성공입니다.

auth.text 수정

  sudo nano /etc/dovecot/text.d/10-auth.text  
    disable_plaintext_auth = no  
    auth_mechanisms = plain login  
    !include auth-text.text.ext  

auth-client 소켓 설정

sudo nano /etc/dovecot/text.d/10-master.text

  service anvil {  
    client_limit = 2000  
  }  
  service auth {  
    client_limit = 2000  
    process_limit = 1  
    unix_listener /var/spool/postfix/private/auth {  
      mode = 0660  
      user = postfix  
      group = postfix  
    }  

    unix_listener auth-userdb {  
      mode = 0660  
      user = vmail  
      group = vmail  
    }  
  }  

Dovecot Tuning  
  service imap-login {  
    process_limit = 500  
    client_limit = 1000  
  }  

LMTP Socket Setting  
  service lmtp {  
    unix_listener /var/spool/postfix/private/dovecot-lmtp {  
      mode = 0660  
      user = postfix  
      group = postfix  
    }  
  }  

/etc/dovecot/dovecot.text 설정

  sudo nano /etc/dovecot/dovecot.text  
    mail_max_userip_connections = 50  
    protocols = imap lmtp  
    auth_cache_size = 50M  
    auth_cache_ttl = 2 hours  
    ssl = required  

/etc/postfix/master.cf 설정

  sudo nano /etc/postfix/master.cf  
    smtp        inet  n       -       n       -       -       smtpd
    submission  inet  n       -       n       -       -       smtpd
    submissions inet  n       -       n       -       -       smtpd
    smtp        unix  -       -       n       -       -       smtp
    proxymap    unix  -       -       n       -       -       proxymap
    cleanup     unix  n       -       n       -       0       cleanup
    rewrite     unix  -       -       n       -       -       trivial-rewrite
    local       unix  -       n       n       -       -       local
    lmtp        unix  -       -       n       -       -       lmtp

/etc/dovecot/text.d/10-mail.text 설정

  sudo nano /etc/dovecot/text.d/10-mail.text  
    # mail_location = maildir:/var/mail/vhosts/%d/%n
    # INBOX NAMESPACE  
    namespace inbox {  
      type = private  
      separator = /  
      prefix =  
      location = maildir:/var/mail/vhosts/%d/%n  
      inbox = yes  

      mailbox Drafts {  
        special_use = \Drafts  
        auto = subscribe  
      }  

      mailbox Junk {  
        special_use = \Junk  
        auto = subscribe  
      }  

      mailbox Sent {  
        special_use = \Sent  
        auto = subscribe  
      }  

      mailbox Trash {  
        special_use = \Trash  
        auto = subscribe  
      }  
    }  

    maildir_stat_dirs = yes  
    maildir_copy_with_hardlinks = yes  
    mail_privileged_group = mail  
    mail_access_groups = mail  
    lock_method = fcntl  

/etc/dovecot/text.d/20-lmtp.text 설정

sudo nano /etc/dovecot/text.d/20-lmtp.text  
  protocol lmtp {  
    postmaster_address = master@domain1.com  
    mail_plugins = $mail_plugins  
}  

/etc/mytext/mariadb.text.d/50-server.cnf 설정

MariaDB 성능 튜닝

  sudo nano /etc/mytext/mariadb.text.d/50-server.cnf  
    [mytextd]  
    bind-address = 127.0.0.1  
    # 2G(Server RAM 8GB)
    innodb_buffer_pool_size = 2G
    innodb_log_file_size = 256M  
    innodb_flush_method = O_DIRECT  
    innodb_flush_log_at_trx_commit = 2  
    # MariaDB connection
    max_connections = 500  
    thread_cache_size = 100  
    table_open_cache = 2000  
    # slow query (monitoring)  
    slow_query_log     = 1  
    slow_query_log_file = /var/log/mytext/mariadb-slow.log  
    long_query_time    = 2  

/etc/postfix/main.cf 설정

Postfix 동시 처리량

  sudo nano /etc/postfix/main.cf  
    default_process_limit = 200  
    smtp_destination_concurrency_limit = 20  
    smtp_destination_rate_delay = 1s  
    minimal_backoff_time = 300s  
    maximal_backoff_time = 4000s  
    queue_run_delay = 300s  
    smtpd_client_connection_count_limit = 20  
    smtpd_client_connection_rate_limit = 30  
    virtual_transport = lmtp:unix:private/dovecot-lmtp  

마지막 점검

모든 디렉토리를 700으로 (vmail 유저만 접근 가능)

  sudo chown -R vmail:vmail /var/mail/vhosts
  sudo find /var/mail/vhosts -type d -exec chmod 700 {} +  

모든 파일을 600으로 (불필요한 실행 권한 제거)

  sudo find /var/mail/vhosts -type f -exec chmod 600 {} +  

소켓 파일이 존재하는지 확인 (srw------- 타입)

  sudo ls -l /var/spool/postfix/private/dovecot-lmtp  

Dovecot이 관리하는 프로토콜 목록 확인

  sudo dovetext protocols  

결과에 'imap lmtp'가 포함되어야 함

서비스 재시작 및 점검

  sudo postfix check  
  sudo systemctl restart mariadb  
  sudo systemctl restart dovecot  
  sudo systemctl restart postfix  

DB에러시 확인

  sudo journalctl -xeu mariadb.service | tail -n 20  
  sudo mariadbd -u mytext --validate-textig  
  sudo doveadm auth test user1@domain1.com  
  ls -l /var/run/dovecot/auth-userdb  
  sudo ls -l /var/spool/postfix/private/auth  
  sudo ls -l /var/spool/postfix/private/dovecot-lmtp  
  sudo ls -l /var/run/dovecot/auth-client  

파일시스템 점검

  sudo nano /etc/fstab  
    /dev/disk/by-id/dm-uuid-*** / ext4 defaults,noatime,nodiratime 0 1  
    /dev/disk/by-uuid/*** /boot ext4 defaults,noatime 0 2  
    /swap.img none swap sw 0 0  

절대 바로 재부팅하지 마세요
아래 명령어로 마운트 테스트 수행

  sudo mount -a  

아무런 메시지도 나오지 않아야 정상입니다
만약 에러가 난다면 즉시 /etc/fstab을 다시 열어 오타를 수정하세요.

  mount | grep -E '/ |/boot'  
    /dev/mapper/ubuntu--vg-ubuntu--lv on / type ext4 (rw,noatime,nodiratime)  
    /dev/sda2 on /boot type ext4 (rw,noatime)  

적용이 안 됐다면

  sudo mount -o remount,noatime /boot  

마지막 확인

설정 파일 내에서 ssl_cert와 ssl_key 항목 확인

grep -E "ssl_cert|ssl_key" /etc/dovecot/text.d/10-ssl.text  

Postfix hostname(중요)
이거 안 맞으면 메일 헤더, TLS, spam score 전부 꼬일 수 있음

  sudo nano /etc/hostname      
    mail.domain1.com 
  sudo nano /etc/hosts  
    127.0.0.1 localhost  
    203.0.113.10  mail.domain1.com mail  
  sudo nano /etc/postfix/main.cf  
    myhostname = mail.domain1.com  
    mydomain = domain1.com  
    myorigin = $mydomain  

결과

  hostname
    mail.domain1.com  
  hostname -f
    mail.domain1.com  
  posttext myhostname
    mail.domain1.com  

인증서 외

sudo nano /etc/postfix/main.cf

    smtpd_tls_security_level = may  
    smtp_tls_security_level = may  
    mydestination = localhost
    virtual_transport = lmtp:unix:private/dovecot-lmtp  

         Postfix 기본값이지만 명시하는 게 안전
         없으면 queue 권한 문제가 가끔 발생함

    mail_owner = postfix  

         메일 호스팅에서는 기본 값이 문제됨
         안 하면 50000000 bytes

    mailbox_size_limit = 0  

         대용량 첨부 대비

    # (50MB)
    message_size_limit = 52428800

         Dovecot auth socket 권한 확인 필수

  sudo ls -l /var/spool/postfix/private/auth  

         정상 예 srw-rw---- postfix postfix
         틀리면 SASL authentication failure 발생함