Archives pour juin, 2013

OpenSSH : connexion facile à un hôte distant

Cette astuce – d’un de mes collègues – permet de se connecter en SSH en tapant uniquement le nom d’hôte du serveur. Il faut pour cela une entrée pour chaque serveur dans le fichier /etc/hosts, ainsi que ces lignes de configuration pour votre shell :

boris.huisgen@local:~$ cat .bash_aliases
for HOST in `egrep -v "^$|#" /etc/hosts | tail -n +3 | awk '{ print $2 }'`
do
alias $HOST='ssh $USER@'$HOST
done

Reste à taper le nom d’hôte pour se connecter :

boris.huisgen@local:~$ sl-ld6-wp1

Firewall Builder : template pour serveur OpenVZ

Template de firewall pour un serveur OpenVZ : [Téléchargement introuvable].

Pour plus d’informations, je vous renvoie à l’article « OpenVZ : accès IPv6 en mode Virtual Ethernet (veth) ».

Postfix : ajout du support DMARC

Pour prendre en charge DMARC sur un MTA Postfix, il convient d’installer OpenDMARC. Çà tombe bien c’est rapide.

Installation

root@mailsrv:~# cat /etc/apt/apt.conf
APT::Default-Release "stable";
root@mailsrv:~# nano /etc/apt/apt.conf
# stable
deb http://ftp.debian.org/debian wheezy main contrib non-free
deb http://security.debian.org wheezy/updates main contrib non-free

# testing
deb http://ftp.debian.org/debian testing main contrib non-free
deb http://security.debian.org testing/updates main contrib non-free
root@mailsrv:~# apt-get update
root@mailsrv:~# apt-get dist-upgrade
root@mailsrv:~# apt-get -t testing install opendmarc

Configuration de Postfix

root@mailsrv:~# nano /etc/postfix/main.conf
smtpd_milters = inet:127.0.0.1:8893
non_smtpd_milters = inet:127.0.0.1:8893

Rsync : copie SSH avec sudo préalable

root@dst:~# cat /etc/sudoers.d/common
user   ALL=(ALL:ALL) NOPASSWD:ALL
root@src:~# rsync -e "ssh" --rsync-path "sudo rsync" user@dst:/etc

YAPET : Yet Another Password Encryption Tool

Le logiciel ncurses idéal pour stocker les mots de passe là où on en a vraiment besoin…

Site web : http://www.guengel.ch/myapps/yapet/readme.shtml

Dovecot : restriction de l’authentification utilisateur

Je vous présente ici la solution que j’ai retenu pour restreindre les possibilités d’authentification de Dovecot selon le protocole utilisé (POP/IMAP/SMTP/Sieve). La gestion des comptes de messagerie étant effectué en SQL avec PostfixAdmin, le schéma a été repris tel quel ; seule une table a été ajoutée. L’authentification SMTP est déportée de Postfix vers Dovecot (configuration LMTP).

Configuration SQL

La table dédiée à l’authentification est la suivante :

CREATE TABLE IF NOT EXISTS `auth` (
  `username` varchar(255) NOT NULL,
  `allow_smtp` tinyint(1) NOT NULL DEFAULT '1',
  `smtp_nets` varchar(255) NOT NULL DEFAULT '::1,127.0.0.1,0::0/0,0.0.0.0/0',
  `allow_imap` tinyint(1) NOT NULL DEFAULT '1',
  `imap_nets` varchar(255) NOT NULL DEFAULT '::1,127.0.0.1',
  `allow_pop3` tinyint(1) NOT NULL DEFAULT '1',
  `pop3_nets` varchar(255) NOT NULL DEFAULT '::1,127.0.0.1,0::0/0,0.0.0.0/0',
  `allow_sieve` tinyint(1) NOT NULL DEFAULT '1',
  `sieve_nets` varchar(255) NOT NULL DEFAULT '::1,127.0.0.1',
  PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Elle contiendra donc l’ensemble des comptes utilisateurs (username) avec les possibilités de chacun. Les valeurs par défaut autorisent ici :

  • la connexion SMTP quelque soit l’IP source (IPv4 et IPv6)
  • la connexion IMAP uniquement par IP locale (par webmail)
  • la connexion POP3 quelque soit l’IP source
  • la gestion des filtres Sieve uniquement en local (par webmail)

Configuration de Dovecot

Les requêtes SQL sont à modifier de cette façon :

user_query = \
   SELECT \
      CONCAT('/var/spool/vmail/', maildir) AS home, \
      CONCAT('maildir:/var/spool/vmail/', maildir) AS mail, \
      999 AS uid, \
      999 AS gid, \
      'dict:User quota::proxy::quota' AS quota, \
      CONCAT('*:storage=',FLOOR(quota/1000)) AS quota_rule, \
      'Trash:storage=+100M' AS quota_rule2 \
   FROM \
      mailbox \
   WHERE \
      username='%u' AND domain='%d' AND active='1'

password_query = \
   SELECT \
      mailbox.username AS user, \
      mailbox.domain AS domain, \
      password, \
      CONCAT('/var/spool/vmail/', maildir) AS userdb_home, \
      CONCAT('maildir:/var/spool/vmail/', maildir) AS userdb_mail, \
      999 AS userdb_uid, \
      999 AS userdb_gid, \
      'dict:User quota::proxy::quota' AS userdb_quota, \
      CONCAT('*:storage=',FLOOR(quota/1000)) AS userdb_quota_rule, \
      'Trash:storage=+100M' AS userdb_quota_rule2, \
      CASE '%s' \
         WHEN 'smtp' THEN smtp_nets \
         WHEN 'imap' THEN imap_nets \
         WHEN 'pop3' THEN pop3_nets \
         WHEN 'sieve' THEN sieve_nets \
      END AS allow_nets \
   FROM \
      mailbox, auth \
   WHERE \
      mailbox.username=auth.username AND \
      mailbox.username='%u' AND domain='%d' AND active='1' AND \
      ( \
         ('%s'='smtp' AND allow_smtp='1') \
         OR \
         ('%s'='imap' AND allow_imap='1') \
         OR \
         ('%s'='pop3' AND allow_pop3='1') \
         OR \
         ('%s'='sieve' AND allow_sieve='1') \
         OR \
         '%s'='doveadm' \
      )

iterate_query = \
   SELECT \
      username AS user \
   FROM \
      mailbox \
   WHERE \
      active='1'

Scripts complémentaires pour une gestion avec Postfixadmin

Du côté de PostfixAdmin, l’ajout et la suppresion de comptes déclenchent les scripts de modification des enregistrements dans la table auth :

root@mail:~# more mailbox-postcreation.d/10-auth
#!/usr/bin/perl

my $sql_host = 'localhost';                         # SQL server hostname
my $sql_port = '3306';                              # SQL server port
my $sql_database = 'postfix';                       # SQL database name
my $sql_user = 'postfixadmin';                      # SQL user
my $sql_password = 'blablablaaa';                   # SQL password

use strict;
use warnings;
use DBI;
use DBD::mysql;

my ($dsn, $dbh, $qh, $mailbox);

$mailbox = $ARGV[0];
if (!defined($mailbox) || ($mailbox eq '')) {
   exit 1
}

$dsn = "dbi:mysql:$sql_database:$sql_host:$sql_port";
$dbh = DBI->connect($dsn, $sql_user, $sql_password)
	or die "failed to connect to database: $DBI::errstr\n";
$qh = $dbh->prepare("INSERT INTO auth (username) VALUES ('$mailbox')");
$qh->execute();
root@mail:~# more /root/scripts/postfixadmin/mailbox-postdeletion.d/10-auth
#!/usr/bin/perl

my $sql_host = 'localhost';                         # SQL server hostname
my $sql_port = '3306';                              # SQL server port
my $sql_database = 'postfix';                       # SQL database name
my $sql_user = 'postfixadmin';                      # SQL user
my $sql_password = 'blablablaaa';                   # SQL password

use strict;
use warnings;
use DBI;
use DBD::mysql;

my ($dsn, $dbh, $qh, $mailbox);

$mailbox = $ARGV[0];
if (!defined($mailbox) || ($mailbox eq '')) {
   exit 1
}

$dsn = "dbi:mysql:$sql_database:$sql_host:$sql_port";
$dbh = DBI->connect($dsn, $sql_user, $sql_password)
	or die "failed to connect to database: $DBI::errstr\n";
$qh = $dbh->prepare("DELETE FROM auth WHERE username='$mailbox'");
$qh->execute();

Script pour autoconfiguration avec Thunderbird

Je vous renvoie sur mon article présentant l’autoconfiguration d’un compte sous Thunderbird. L’intérêt du script PHP est de récupérer les paramètres adaptés/autorisés pour l’utilisateur concerné.

<?php
/*
 * autoconf.php
 *
 * Boris HUISGEN <bhuisgen@hbis.fr>
 */

   define("SYSLOG_IDENT", "autoconfig");
   define("SYSLOG_OPTIONS", LOG_PID|LOG_NDELAY);
   define("SYSLOG_LOCAL", LOG_USER);
   define("DB_DSN", "mysql:host=localhost;dbname=postfix");
   define("DB_USER", "autoconfig");
   define("DB_PASSWORD", "blablablaaa");
   define("REGEX_ALLOWNETS", "/^(.*,)?(0::0\/0|0\.0\.0\.0\/0)(,.*)$/");

   // script

   openlog(SYSLOG_IDENT, SYSLOG_OPTIONS, SYSLOG_LOCAL);

   if (getenv("HTTP_CLIENT_IP"))
      $ip = getenv("HTTP_CLIENT_IP");
   else if(getenv("HTTP_X_FORWARDED_FOR"))
      $ip = getenv("HTTP_X_FORWARDED_FOR");
   else if(getenv("REMOTE_ADDR"))
      $ip = getenv("REMOTE_ADDR");
   else $ip = "unknown IP";

   if (!isset($_GET["emailaddress"]))
   {
      syslog(LOG_ERR, "[client=$ip] failed to get email address, aborting execution");
      exit(1);
   }

   $username = $_GET["emailaddress"];
   {
      $c = new PDO(DB_DSN, DB_USER, DB_PASSWORD);
      $c->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

      $s = $c->prepare("SELECT allow_smtp, smtp_nets, allow_pop3, pop3_nets, " .
                          "allow_imap, imap_nets FROM auth WHERE username = ?");
      $s->bindColumn(1, $allow_smtp, PDO::PARAM_INT);
      $s->bindColumn(2, $smtp_nets, PDO::PARAM_STR, 255);
      $s->bindColumn(3, $allow_pop3, PDO::PARAM_INT);
      $s->bindColumn(4, $pop3_nets, PDO::PARAM_STR, 255);
      $s->bindColumn(5, $allow_imap, PDO::PARAM_INT);
      $s->bindColumn(6, $imap_nets, PDO::PARAM_STR, 255);
      $s->bindParam(1, $username);
      $s->execute();
      if ($s->rowCount() != 1)
      {
         syslog(LOG_ERR, "[client=$ip] $username not found");
         exit(3);
      }

      $s->fetch(PDO::FETCH_BOUND);
   }
   catch (PDOException $e)
   {
      syslog(LOG_ERR, "PDO exception, aborting execution");
      exit(2);
   }

   syslog(LOG_NOTICE, "[client=$ip] $username configured");

   header("Content-type: text/xml; charset=utf-8");
?>
<?xml version="1.0" encoding="UTF-8"?>
<clientConfig version="1.1">
  <emailProvider id="domaine.fr">
    <domain>domaine.fr</domain>
    <displayName>Test</displayName>
    <displayShortName>Test</displayShortName>
<?php
   if (($allow_imap == 1) &&
       (preg_match(REGEX_ALLOWNETS, $imap_nets) == 1))
   {
?>
    <incomingServer type="imap">
      <hostname>mail.domaine.fr</hostname>
      <port>143</port>
      <socketType>STARTTLS</socketType>
      <username>%EMAILADDRESS%</username>
      <authentication>password-cleartext</authentication>
    </incomingServer>
<?php
   }

   if (($allow_pop3 == 1) &&
       (preg_match(REGEX_ALLOWNETS, $pop3_nets) == 1))
   {
?>
    <incomingServer type="pop3">
      <hostname>mail.domaine.fr</hostname>
      <port>995</port>
      <socketType>SSL</socketType>
      <authentication>password-cleartext</authentication>
      <username>%EMAILADDRESS%</username>
    </incomingServer>
<?php
   }

   if (($allow_smtp == 1) &&
       (preg_match(REGEX_ALLOWNETS, $smtp_nets) == 1))
   {
?>
    <outgoingServer type="smtp">
      <hostname>noreply.domaine.fr</hostname>
      <port>587</port>
      <socketType>STARTTLS</socketType>
      <username>%EMAILADDRESS%</username>
    </outgoingServer>
<?php
   }
?>
  </emailProvider>
</clientConfig>

MySQL : installation multi-instances

Dans le cas ou vous gérez plusieurs serveurs MySQL (distants ou sur différents VLAN), il peut être utile de déporter tout traitement long sur un seul serveur SQL dédié pour peu de regrouper l’ensemble des bases de données.

En attendant MariaDB 10 qui permettra la réplication multi-master, il est nécessaire de mettre en place plusieurs instances MySQL sur la même machine. Une réplication pourra ensuite être lancée sur chaque instance vers chaque serveur SQL source, configuré évidemment en master.

Voici donc un guide rapide de mise en place d’instances multiples MySQL (l’ancienne méthode tout à la main n’est plus nécessaire et c’est tant mieux).

Installation

root@sqlbackend:~# apt-get install mysql-server-5.5

Configuration

Je vais créer ici trois nouvelles instances mysql1, mysql2 et mysql3 ; chacune aura donc sa directive serveur. A noter que je ne supprime pas l’instance principale pour ne pas me ramasser à la prochaine mise à jour OS. Il faut cependant commenter l’option user sinon les instances supplémentaires ne se lanceront pas.

root@sqlbackend:~# nano /etc/mysql/my.cnf
[mysqld]
#user = mysql

[mysqld1]
user            = mysql1
pid-file        = /var/run/mysqld1/mysqld.pid
socket          = /var/run/mysqld1/mysqld.sock
port            = 33061
basedir         = /usr
datadir         = /var/lib/mysql1
tmpdir          = /tmp
lc-messages-dir = /usr/share/mysql
skip-external-locking

bind-address            = 127.0.0.1
general_log_file        = /var/log/mysql1/mysql.log
general_log             = 1
[mysqld2]
user            = mysql2
pid-file        = /var/run/mysqld2/mysqld.pid
socket          = /var/run/mysqld2/mysqld.sock
port            = 33062
basedir         = /usr
datadir         = /var/lib/mysql2
tmpdir          = /tmp
lc-messages-dir = /usr/share/mysql
skip-external-locking

bind-address            = 127.0.0.1
general_log_file        = /var/log/mysql2/mysql.log
general_log             = 1

[mysqld3]
user            = mysql3
pid-file        = /var/run/mysqld3/mysqld.pid
socket          = /var/run/mysqld3/mysqld.sock
port            = 33063
basedir         = /usr
datadir         = /var/lib/mysql3
tmpdir          = /tmp
lc-messages-dir = /usr/share/mysql
skip-external-locking

bind-address            = 127.0.0.1
general_log_file        = /var/log/mysql3/mysql.log
general_log             = 1

Chaque instance s’exécute sous un utilisateur dédié :

root@sqlbackend:~# useradd -r mysql1
root@sqlbackend:~# useradd -r mysql2
root@sqlbackend:~# useradd -r mysql3

Les répertoires de chaque instance sont à créer de cette manière :

root@sqlbackend:~# mkdir /var/lib/mysql{1,2,3}
root@sqlbackend:~# mkdir /var/log/mysql{1,2,3}
root@sqlbackend:~# chown mysql1:adm /var/log/mysql1
root@sqlbackend:~# chown mysql2:adm /var/log/mysql2
root@sqlbackend:~# chown mysql3:adm /var/log/mysql3
root@sqlbackend:~# mkdir /var/run/mysqld{1,2,3}
root@sqlbackend:~# chown mysql1 /var/run/mysqld1
root@sqlbackend:~# chown mysql2 /var/run/mysqld2
root@sqlbackend:~# chown mysql3 /var/run/mysqld3

Il reste à initialiser les datastores :

root@sqlbackend:~# mysql_install_db --datadir=/var/lib/mysql1 --user=mysql1
root@sqlbackend:~# mysql_install_db --datadir=/var/lib/mysql2 --user=mysql2
root@sqlbackend:~# mysql_install_db --datadir=/var/lib/mysql3 --user=mysql3

L’exécution de chaque instance est désormais possible :

root@sqlbackend:~# mysqld_multi --verbose --no-log start 1
root@sqlbackend:~# mysqld_multi --verbose --no-log start 2
root@sqlbackend:~# mysqld_multi --verbose --no-log start 3
Haut de page