nyroBlog
Ban NyroBlog, by Nyro
Image by Nyro - ?

Connexions OAuth Multiple avec Symfony 2.3

Voici un long tutorial pour Symfony 2.3 permettant d'ajouter des boutons dec onnexion à des services proposant de l'Oauth (comme Facebook, twitter, google, etc..)

Il existe un très bon Bundle, HWIOAuthBundle qui a toutes les fonctionnalités de connexion à OAuth, mais qui manque cruellement de documentations claires.

Ce tutorial part du principe que vous avez déjà en place :

  • Un firewall dans lequel on peut se connecter via un formulaire de connexion (form_login)
  • Un formulaire d'inscription
  • Les utilisateurs sont enregistrés en base de données (via une entity, nommé User dans ce tuto)
  • La gestion des roles et sécurité restera exactement la même

 

Les fonctionnalités que nosu allons mettre en place vont se greffer à l'existant. Toute la sécurité, gestion d'accès et autre reste à mettre en place (ou le sont peut-être déjà)
L'ajout de la fonctionnalité Connexion Oauth permettra :

  • Pré-remplir le formulaire d'inscription si l'utilisateur n'existe pas
  • Connexion en un seul clic via un service externe
  • Ajout d'un service externe pour un compte déjà existant : Ainsi un utilisateur pourra utiliser plusieurs boutons de connexion externe pour se connecter au même compte de votre site



Installation de HWIOAuthBundle

La première étape consiste donc à installer HWIOAuthBundle
Dans le composer.json, il faut ajouter la ligne suivante de le require:

"hwi/oauth-bundle": "0.3.*@dev"

Un update, et on ajoute le Bundle dans app/AppKernel.php :

public function registerBundles()
{
    $bundles = array(
        // ...
        new HWI\Bundle\OAuthBundle\HWIOAuthBundle(),
    );
}

Jusque là, rien d'anormal. Si on essaie de recharger Symfony à ce moment là, on aura une erreur car le Bundle n'est pas configuré.

 

Configuration du bundle

Donc dans le fichier config.yml, on peut ajouter cette configuration :

#HWIOAuthBundle
hwi_oauth:
    firewall_name: oauth
    resource_owners:
        facebook:
            type: facebook
            client_id: %facebook_client_id%
            client_secret: %facebook_client_secret%
            scope: email
            infos_url:     "https://graph.facebook.com/me?fields=username,name,email,picture.type(large)"
            paths:
                email:          email
                profilepicture: picture.data.url
            options:
                display: popup #dialog is optimized for popup window
        twitter:
            type: twitter
            client_id: %twitter_client_id%
            client_secret: %twitter_client_secret%
            paths:
                profilepicture: profile_image_url
        google:
            type: google
            client_id: %google_client_id%
            client_secret: %google_client_secret%
            scope: "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile"
            paths:
                email:           email
                profilepicture:  picture

Les services facebook, twitter et google sont paramétré pour se connecter, mais aussi récupérer l'email et l'avatar de l'utilisateur

Le paramètre firewall_name est important ; c'est le nom que l'on va donner au firewall spécifique à la connexion oAuth

Vous aurez noté qu'on utilise des paramètres pour les client_id et client_secret. A vous de les placer correctement dans votre fichier parameters.
Les options pour chaque app sur les services externes et la récupération de ces infos diffèrent selon les services, mais on s'y retrouve assez facilement.

 

Configuration du firewall

Il est temps d'ajouter ce firewall.
Notre but est de ne rien touché aux firewall acuels et de créer un espace spécial pour tout ce qui concerne le bundle que l'on vient d'ajouter.
Dans votre fichier app/config/security.yml, ajoter les informations suivantes :

security:
    # Probably other stuff here

    providers:
		# Probably other providers here
        oauth:
            id: my_oauth_members

    firewalls:
		# Probably other firewalls here

        oauth:
            pattern:    ˆ/oauth/.*
            anonymous: ~
            provider: oauth
            oauth:
                resource_owners:
                    facebook: "/oauth/login/check-facebook"
                    twitter: "/oauth/login/check-twitter"
                    google: "/oauth/login/check-google"
                login_path: /oauth/login
                failure_path: /oauth/login
                check_path: /oauth/login_check
                default_target_path: /oauth/target
                oauth_user_provider:
                    service: my_oauth_members

    access_control:
		# Probably other access_control here
        - { path: ˆ/oauth/target, roles: ROLE_OAUTH_USER }
        - { path: ˆ/oauth/, roles: IS_AUTHENTICATED_ANONYMOUSLY }
		# Probably other access_control here

A la vue de ce fichier, vous aurez compris que toutes les URLs commenceront par oauth.
Vous pouvez évidemment changer cela pour ce que vous voulez, en changeant les fichiers routings.
Soyez sûr que l'URL /oauthRegister est accessible derrière votre firewall déjà existant, en étant connecté ou non. C'est cette URL qui fera le pont entre les deux firewalls.

 

Configuration du routing

Pour mettre en place le routing, il suffit d'importer dans un fichier routing extsiant un autre routing, avec le préfixe oauth. On ajouter aussi la route pour le oauthRegister

oauth_register:
    pattern: /oauthRegister
    defaults: { _controller: AcmeMainBundle:Security:oauthRegister }
my_oauth:
    resource: "@AcmeMainBundle/Resources/config/routingSecurityOAuth.yml"
    prefix:   /oauth/

Et le fichier routingSecurityOAuth.yml :

hwi_oauth_login:
    resource: "@HWIOAuthBundle/Resources/config/routing/login.xml"
    prefix:   /login
hwi_oauth_redirect:
    resource: "@HWIOAuthBundle/Resources/config/routing/redirect.xml"
    prefix:   /connect
facebook_login:
    pattern: /login/check-facebook
twitter_login:
    pattern: /login/check-twitter
google_login:
    pattern: /login/check-google
oauth_target:
    pattern: /target
    defaults: { _controller: AcmeMainBundle:Security:oauthTarget }

On commence par importer les routes du bundle, avec des préfixes spécifiques. Pour chaque service, on doit créé une route avec seulement son pattern. C'est le bundle qui intercepttera ces URLs pour réaliser les actions nécessaires. La route target permettra de recevoir les infos de l'utilisateur connecté, puis de l'envoyer vers oauthRegister et faire le pont avec l'autre firewall.

 

Configuration de l'user provider (service)

Dans le firewall, vous avez probablement remarqué que l'on a configuré le provider my_oauth_members. Il est temps de paramétrer ce provider (avant de le coder), dans un fichier services.yml. :

parameters:
    acme_main.oauth_members.class: Acme\MainBundle\Services\OAuthMembersService

services:
    my_oauth_members:
        class: %acme_main.oauth_members.class%

 

Classe OAuthUser

La classe OAuthUser sera l'objet utilisé par Symfony pour connecter l'utilisateur après l'autorisation sur un service externe. Dans notre cas, cet objet est très simple et permet simplement de conserver toutes les informations de l'utilisateur afin de les passer à oauthRegister ensuite.

// Acme/MainBundle/Security/OAuthUser.php
<?php 

namespace Acme\MainBundle\Security;

use HWI\Bundle\OAuthBundle\Security\Core\User\OAuthUser as BaseOAuthUser;
use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;

class OAuthUser extends BaseOAuthUser {
	
	protected $data;
	
	public function __construct(UserResponseInterface $response) {
		parent::__construct($response->getUsername());
		$this->data = array(
			'provider'=>$response->getResourceOwner()->getName(),
			'providerId'=>$response->getUsername()
		);
		$vars = array(
			'nickname',
			'realname',
			'email',
			'profilePicture',
			'accessToken',
			'refreshToken',
			'tokenSecret',
			'expiresIn',
		);
		foreach($vars as $v) {
			$fct = 'get'.ucfirst($v);
			$this->data[$v] = $response->$fct();
		}
	}
	
	public function getData() {
		return $this->data;
	}
	
	/**
     * {@inheritDoc}
     */
    public function getRoles() {
        return array('ROLE_OAUTH_USER');
    }

}

 

Classe OAuthMembersService

Cette classe est l'user provider de notre firewall est n'est utilisé que lorsque l'utilisateur se connecte depuis un service externe. Les autres fonctions sont alors inutile:

// Acme/MainBundle/Services/OAuthMembersService.php
<?php 

namespace Acme\MainBundle\Services;

use Symfony\Component\Security\Core\User\UserProviderInterface;
use HWI\Bundle\OAuthBundle\Security\Core\User\OAuthAwareUserProviderInterface;
use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
use Acme\MainBundle\Security\OAuthUser;

class OAuthMembersService implements UserProviderInterface, OAuthAwareUserProviderInterface {

    public function loadUserByUsername($username) {
		throw new Exception('loadByUsername not implemented');
    }
	
    public function supportsClass($class) {
		return $class === 'Acme\\MainBundle\\Security\\OAuthUser';
    }
	
	public function refreshUser(\Symfony\Component\Security\Core\User\UserInterface $user) {
		if (!$this->supportsClass(get_class($user))) {
			throw new UnsupportedUserException(sprintf('Unsupported user class "%s"', get_class($user)));
		}
		return $user;
	}
	
    public function loadUserByOAuthUserResponse(UserResponseInterface $response) {
		return new OAuthUser($response);
    }
	
}

 

Entity UserOauth

Jusqu'à maintenant, on a tout configuré, et préparé pour envoyer les requêtes oauth sur un controller AcmeMainBundle:Security. Avant de voir ce controller, il faut préparer l'entité responsable de sauvegarder les informations de l'utilisateur en base de données, afin d'être capable de le retrouver lorsqu'il se connectera à nouveau.

L'Entity UserOauth, qui à une relation vers User, la table qui contient vos utilisateurs normaux :

// Acme/MainBundle/Entity/UserOauth.php
<?php 

namespace Acme\MainBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * UserOauth
 *
 * @ORM\Table(name="user_oauth")
 * @ORM\Entity
 */
class UserOauth
{
    /**
     * @var string
     *
     * @ORM\Column(name="provider", type="string", length=250, nullable=false)
     */
    private $provider;

    /**
     * @var string
     *
     * @ORM\Column(name="provider_id", type="string", length=250, nullable=false)
     */
    private $providerId;

    /**
     * @var string
     *
     * @ORM\Column(name="nickname", type="string", length=250, nullable=true)
     */
    private $nickname;

    /**
     * @var string
     *
     * @ORM\Column(name="realname", type="string", length=250, nullable=true)
     */
    private $realname;

    /**
     * @var string
     *
     * @ORM\Column(name="email", type="string", length=250, nullable=true)
     */
    private $email;

    /**
     * @var string
     *
     * @ORM\Column(name="profile_picture", type="string", length=250, nullable=true)
     */
    private $profilePicture;

    /**
     * @var string
     *
     * @ORM\Column(name="access_token", type="string", length=250, nullable=true)
     */
    private $accessToken;

    /**
     * @var string
     *
     * @ORM\Column(name="refresh_token", type="string", length=250, nullable=true)
     */
    private $refreshToken;

    /**
     * @var string
     *
     * @ORM\Column(name="token_secret", type="string", length=250, nullable=true)
     */
    private $tokenSecret;

    /**
     * @var string
     *
     * @ORM\Column(name="expires_in", type="string", length=250, nullable=true)
     */
    private $expiresIn;

    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="bigint")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @var \Acme\MainBundle\Entity\User
     *
     * @ORM\ManyToOne(targetEntity="Acme\MainBundle\Entity\User")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="user_id", referencedColumnName="id")
     * })
     */
    private $user;



    /**
     * Set provider
     *
     * @param string $provider
     * @return UserOauth
     */
    public function setProvider($provider)
    {
        $this->provider = $provider;
    
        return $this;
    }

    /**
     * Get provider
     *
     * @return string 
     */
    public function getProvider()
    {
        return $this->provider;
    }

    /**
     * Set providerId
     *
     * @param string $providerId
     * @return UserOauth
     */
    public function setProviderId($providerId)
    {
        $this->providerId = $providerId;
    
        return $this;
    }

    /**
     * Get providerId
     *
     * @return string 
     */
    public function getProviderId()
    {
        return $this->providerId;
    }

    /**
     * Set token
     *
     * @param string $token
     * @return UserOauth
     */
    public function setToken($token)
    {
        $this->token = $token;
    
        return $this;
    }

    /**
     * Get token
     *
     * @return string 
     */
    public function getToken()
    {
        return $this->token;
    }

    /**
     * Set nickname
     *
     * @param string $nickname
     * @return UserOauth
     */
    public function setNickname($nickname)
    {
        $this->nickname = $nickname;
    
        return $this;
    }

    /**
     * Get nickname
     *
     * @return string 
     */
    public function getNickname()
    {
        return $this->nickname;
    }

    /**
     * Set realname
     *
     * @param string $realname
     * @return UserOauth
     */
    public function setRealname($realname)
    {
        $this->realname = $realname;
    
        return $this;
    }

    /**
     * Get realname
     *
     * @return string 
     */
    public function getRealname()
    {
        return $this->realname;
    }

    /**
     * Set email
     *
     * @param string $email
     * @return UserOauth
     */
    public function setEmail($email)
    {
        $this->email = $email;
    
        return $this;
    }

    /**
     * Get email
     *
     * @return string 
     */
    public function getEmail()
    {
        return $this->email;
    }

    /**
     * Set profilePicture
     *
     * @param string $profilePicture
     * @return UserOauth
     */
    public function setProfilePicture($profilePicture)
    {
        $this->profilePicture = $profilePicture;
    
        return $this;
    }

    /**
     * Get profilePicture
     *
     * @return string 
     */
    public function getProfilePicture()
    {
        return $this->profilePicture;
    }

    /**
     * Set accessToken
     *
     * @param string $accessToken
     * @return UserOauth
     */
    public function setAccessToken($accessToken)
    {
        $this->accessToken = $accessToken;
    
        return $this;
    }

    /**
     * Get accessToken
     *
     * @return string 
     */
    public function getAccessToken()
    {
        return $this->accessToken;
    }

    /**
     * Set refreshToken
     *
     * @param string $refreshToken
     * @return UserOauth
     */
    public function setRefreshToken($refreshToken)
    {
        $this->refreshToken = $refreshToken;
    
        return $this;
    }

    /**
     * Get refreshToken
     *
     * @return string 
     */
    public function getRefreshToken()
    {
        return $this->refreshToken;
    }

    /**
     * Set tokenSecret
     *
     * @param string $tokenSecret
     * @return UserOauth
     */
    public function setTokenSecret($tokenSecret)
    {
        $this->tokenSecret = $tokenSecret;
    
        return $this;
    }

    /**
     * Get tokenSecret
     *
     * @return string 
     */
    public function getTokenSecret()
    {
        return $this->tokenSecret;
    }

    /**
     * Set expiresIn
     *
     * @param string $expiresIn
     * @return UserOauth
     */
    public function setExpiresIn($expiresIn)
    {
        $this->expiresIn = $expiresIn;
    
        return $this;
    }

    /**
     * Get expiresIn
     *
     * @return string 
     */
    public function getExpiresIn()
    {
        return $this->expiresIn;
    }

    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set user
     *
     * @param \Acme\MainBundle\Entity\User $user
     * @return UserOauth
     */
    public function setUser(\Acme\MainBundle\Entity\User $user = null)
    {
        $this->user = $user;
    
        return $this;
    }

    /**
     * Get user
     *
     * @return \Acme\MainBundle\Entity\User 
     */
    public function getUser()
    {
        return $this->user;
    }
}

Les 2 champs importants et obligatoires sont provider et providerId. Provider contiendra le nom du service (facebook ou twitter par exemple) et providerId sera l'identifiant unique de l'utilisateur sur le service externe.

 

Le controller

Le controller est la clé du succès pour faire le lien entre le firewall classique et le firewall oauth.
Ce controller devra sans doute être le même que celui qui vous permet aujourd'hui d'enregistré un utilisateur (même si ce n'est pas obligatoire, c'est plus simple)
Son fonctionnement est le suivant :

  • On reçoit une requête sur targetAction, l'utilisateur vient de se connecter via un service externe
  • On enregistre les informations de la classe OAuthUser en session Flash les infos de l'utilisateur externe
  • On le redirige vers oauthRegister (qui est derrière le firewall normal). Et là, plusieurs possibilités :
    • Le UserOauth n'existe pas et l'utilisateur n'est pas connecté : On l'envoie sur le formulaire d'inscription en enregistrant en session Flash les infos de l'utilisateur externe.
    • Le UserOauth n'existe pas et l'utilisateur est connecté : On enregistre ces infos en Base de données et on le redirige vers la home (il vient d'ajouter un nouveau service à son compte)
    • Le UserOauth existe : On le met à jour et on connecte l'utilisateur correspondant (il vient de se reconnecter)

Le point qui reste à éclairer est le formulaire d'inscription, qui est pour moi dans le même controller. C'est le seul point qui devra être repris sur un projet existant pour bien prendre en comtpe le Oauth.
Son fonctionnement est assez simple :

  • On vérifie si on a des informations en session Flash provenant de l'Oauth.
  • Si ce n'est pas le cas, on est dans une inscription classique et on ne fera rien de plus
  • Si c'est le cas, avant de créer le formulaire, on prérempli le champ email et username que l'on a peut-être provenant de l'Oauth
  • On réenregistre en session Flash les infos de l'oauth pour les avoir de nouveau au post du formulaire
  • Lorsque le formulaire est valide et que l'on peut créer l'utilisateur, on persiste aussi une ligne dans userOauth et le tour est joué

Et voici la classe :

// Acme/MainBundle/Controller/SecurityController.php
<?php 

namespace Acme\MainBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken,
	Symfony\Component\Security\Core\AuthenticationEvents,
	Symfony\Component\Security\Core\Event\AuthenticationEvent;

class SecurityController extends Controller {
	
	protected $oauthDataKey = 'oAuthData';
	
	public function oauthTargetAction() {
		$user = $this->getUser();
		$this->get('session')->getFlashBag()->set($this->oauthDataKey, $user->getData());
		return $this->redirect($this->generateUrl('oauth_register'));
	}
	
	public function oauthRegisterAction() {
		$oAuthData = $this->get('session')->getFlashBag()->get($this->oauthDataKey);
		
		if (!$oAuthData || !is_array($oAuthData) || !isset($oAuthData['provider']) || !isset($oAuthData['providerId']))
			return $this->redirect($this->generateUrl('hwi_oauth_connect'));
		
		// Search for userOauth
		$userOauth = $this->getDoctrine()->getRepository('AcmeMainBundle:UserOauth')->findOneBy(array(
			'provider'=>$oAuthData['provider'],
			'providerId'=>$oAuthData['providerId'],
		));
		if ($userOauth) {
			// We found it, update user oauth data
			$this->setUserOauthData($userOauth, $oAuthData);
			$this->getDoctrine()->getManager()->flush();
			
			// Log the user
			$user = $userOauth->getUser();
			$this->logUser($user);
		} else {
			$user = $this->getUser();
			if ($user && is_object($user)) {
				// User is already connected, just add the UserOauth
				$userOauth = $this->getNewUserOauth($user, $oAuthData);
				$this->getDoctrine()->getManager()->flush();
			} else {
				// Not logged and not existing, redirect to register page
				$this->get('session')->getFlashBag()->set($this->oauthDataKey, $oAuthData);
				return $this->redirect($this->generateUrl('register'));
			}
		}
		return $this->redirect($this->generateUrl('_homepage'));
	}
	
	/**
	 * Get a new UserOauth entity with persisting it
	 *
	 * @param \Acme\MainBundle\Entity\User $user
	 * @param array $oAuthData
	 * @return \Acme\MainBundle\Entity\UserOauth
	 */
	protected function getNewUserOauth($user, $oAuthData) {
		$userOauth = new \Acme\MainBundle\Entity\UserOauth();
		$userOauth->setUser($user);
		$this->setUserOauthData($userOauth, $oAuthData);
		$this->getDoctrine()->getManager()->persist($userOauth);
		return $userOauth;
	}
	
	protected function setUserOauthData($userOauth, $oAuthData) {
		foreach($oAuthData as $k=>$v) {
			$fct = 'set'.ucfirst($k);
			$userOauth->$fct($v);
		}
	}
	
	public function logUser(\Acme\MainBundle\Entity\User $user) {
		// Here, "main" is the name of the firewall in your security.yml
		$token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
		$this->get('security.context')->setToken($token);

		// Fire the login event
		$this->get('event_dispatcher')->dispatch(AuthenticationEvents::AUTHENTICATION_SUCCESS, new AuthenticationEvent($token));
	}
	
	public function registerAction() {
		$user = new \Acme\MainBundle\Entity\User();
		
		$oAuthData = $this->get('session')->getFlashBag()->get($this->oauthDataKey);
		if ($oAuthData && is_array($oAuthData)) {
			if (isset($oAuthData['email']) && $oAuthData['email'])
				$user->setEmail($oAuthData['email']);
			if (isset($oAuthData['nickname']) && $oAuthData['nickname'])
				$user->setUsername($oAuthData['nickname']);
		} else {
			$oAuthData = false;
		}
		
		$form = $this->createFormBuilder($user)
					// Create your form normally
					->getForm();
		
		$form->handleRequest($this->getRequest());
		if ($form->isValid()) {
			// Handle the user normally, preparing for persistence
			$this->getDoctrine()->getManager()->persist($user);
			
			if ($oAuthData && is_array($oAuthData))
				$userOauth = $this->getNewUserOauth($user, $oAuthData);
				
			$this->getDoctrine()->getManager()->flush();
			$this->logUser($user);
			return $this->redirect($this->generateUrl('_homepage'));
		}
		
		// Keep oAuthData in flashbag
		if ($oAuthData)
			$this->get('session')->getFlashBag()->set($this->oauthDataKey, $oAuthData);
		
        return $this->render(
            'AcmeMainBundle:Security:register.html.php',
            array(
				'form'=>$form->createView()
			)
        );
	}
	
}

Et voilà, vous avez tout, en place. Il ne reste maintenant qu'à créer des liens vers les connexions paramétrées

 

Liens vers les connect

Pour créer les liens vers les différents services externes de connexion, il faut récupérer les existants, puis créer les bonnes URLs. Dans un controller, on peut faire ça très simplement :

protected function getOauthUrls() {
	$ret = array();
	foreach($this->get('hwi_oauth.security.oauth_utils')->getResourceOwners() as $name)
		$ret[$name] = $this->generateUrl('hwi_oauth_service_redirect', array('service'=>$name));
	return $ret;
}



Hé bien ce fut bien long... Mais après une absence de près d'un an et demi, il fallait au moins ça !

 

Ca vous a plus ? Des remarques ? Des questions ? Les commentaires sotn là pour ça.

Monitoring du serveur : Monit (Serveur Web sur Debian Squeeze)

Le serveur fonctionne bien. Mais qu'arrive-t-il si pour une raison ou une autre un des services tombe ? Pour l'instant, aucune alerte n'est en place et rien ne sera fait.

C'est là que monit intervient. Après quelques configurations, Monit vérifiera toutes les 10 minutes si l'ensemble des services fonctionnement bien. Si ce n'est pas le cas, une alerte est envoyée par email et il tente de le rédmarrer automatiquement.

L'installation du démon est très simple :

aptitude install monit

Puis déplaçon la configuration par défaut pour ne pas l'utiliser:

mv /etc/monit/monitrc /etc/monit/monitrc_default

Editons une nouvelle configuration dans le fichier /etc/monit/monitrc en y mettant :

set daemon  600
    with start delay 120
set logfile syslog facility log_daemon
set mailserver localhost
set mail-format {
    from: yourSender@email.com
    subject: [monit] $SERVICE: $EVENT
}
set eventqueue basedir /home/var/monit slots 100
set alert you@email.com
set httpd port 9999 and
        allow ADMINUSER:ADMINPASS

include /etc/monit/conf.d/*

Puis configurons chacun des services que nous avons installé dans le dossier /etc/monit/conf.d/.

Le fichier /etc/monit/conf.d/clamav :

check process clamav with pidfile /var/run/clamav/clamd.pid
        group virus
        start program = "/etc/init.d/clamav-daemon start"
        stop  program = "/etc/init.d/clamav-daemon stop"
        if failed host localhost port 3310 then restart
        if 5 restarts within 5 cycles then timeout

check process freshclam with pidfile /var/run/clamav/freshclam.pid
        group virus
        start program = "/etc/init.d/clamav-freshclam start"
        stop  program = "/etc/init.d/clamav-freshclam stop"
        if 5 restarts within 5 cycles then timeout

 

Le fichier /etc/monit/conf.d/cron :

check process cron with pidfile /var/run/crond.pid
        group system
        start program = "/etc/init.d/cron start"
        stop  program = "/etc/init.d/cron stop"
        if 5 restarts within 5 cycles then timeout

 

Le fichier /etc/monit/conf.d/dovecot :

check process dovecot with pidfile /var/run/dovecot/master.pid
        group mail
        start program = "/etc/init.d/dovecot start"
        stop  program = "/etc/init.d/dovecot stop"
        if failed host localhost port 993 type tcpssl sslauto protocol imap then restart
        if failed host localhost port 995 type tcpssl sslauto protocol pop then restart
        if failed host localhost port 143 protocol imap then restart
        if failed host localhost port 110 protocol pop then restart
        if 5 restarts within 5 cycles then timeout

 

Le fichier /etc/monit/conf.d/dspam :

check process dspam with pidfile /var/run/dspam/dspam.pid
        group mail
        start program = "/etc/init.d/dspam start"
        stop program = "/etc/init.d/dspam stop"
        if 5 restarts within 5 cycles then timeout

 

Le fichier /etc/monit/conf.d/fail2ban :

check process fail2ban with pidfile /var/run/fail2ban/fail2ban.pid
        start program = "/etc/init.d/fail2ban start"
        stop  program = "/etc/init.d/fail2ban stop"
        if failed unixsocket /var/run/fail2ban/fail2ban.sock then restart
        if 5 restarts within 5 cycles then timeout

 

Le fichier /etc/monit/conf.d/munin :

check process munin-node with pidfile /var/run/munin/munin-node.pid
        group system
        start program = "/etc/init.d/munin-node start"
        stop program  = "/etc/init.d/munin-node stop"
        if failed host localhost port 4949 then restart
        if 5 restarts within 5 cycles then timeout

 

Le fichier /etc/monit/conf.d/mysql :

check process mysql with pidfile /var/run/mysqld/mysqld.pid
        group database
        start program = "/etc/init.d/mysql start"
        stop program = "/etc/init.d/mysql stop"
        if failed unix "/var/run/mysqld/mysqld.sock" then restart
        if failed host 127.0.0.1 port 3306 then restart
        if 5 restarts within 5 cycles then timeout

 

Le fichier /etc/monit/conf.d/nginx :

check process nginx with pidfile /var/run/nginx.pid
        group www
        start program = "/etc/init.d/nginx start"
        stop program  = "/etc/init.d/nginx stop"
        if failed host localhost port 80 protocol http
                and request "/nginx_status" then restart
        if cpu > 60% for 2 cycles then alert
        if cpu > 90% for 5 cycles then restart
        if totalmem > 50% for 5 cycles then restart
        if children > 250 then restart
        if loadavg(5min) greater than 10 for 8 cycles then stop
        if 3 restarts within 5 cycles then timeout

 

Le fichier /etc/monit/conf.d/phpfpm :

check process phpfpm with pidfile /var/run/php5-fpm.pid
        group www
        start program = "/etc/init.d/php5-fpm start"
        stop program = "/etc/init.d/php5-fpm stop"
        if failed unix "/var/run/php5-fpm.sock" then restart
        if 5 restarts within 5 cycles then timeout

 

Le fichier /etc/monit/conf.d/postfix :

check process postfix with pidfile /var/spool/postfix/pid/master.pid
        group mail
        start program = "/etc/init.d/postfix start"
        stop  program = "/etc/init.d/postfix stop"
        if failed port 10026 protocol smtp for 2 times within 2 cycles then restart
        if 5 restarts within 5 cycles then timeout

 

Le fichier /etc/monit/conf.d/powerdns :

check process powerdns with pidfile /var/run/pdns.pid
        start program = "/etc/init.d/pdns start"
        stop program = "/etc/init.d/pdns stop"
        if failed host localhost port 53 then restart
        if 5 restarts within 5 cycles then timeout

check process powerdns-recursor with pidfile /var/run/pdns_recursor.pid
        start program = "/etc/init.d/pdns-recursor start"
        stop program = "/etc/init.d/pdns-recursor stop"
        if failed host localhost port 54 then restart
        if 5 restarts within 5 cycles then timeout

 

Le fichier /etc/monit/conf.d/rsyslog :

check process rsyslogd with pidfile /var/run/rsyslogd.pid
        group system
        start program = "/etc/init.d/rsyslog start"
        stop  program = "/etc/init.d/rsyslog stop"
        if 5 restarts within 5 cycles then timeout

 

Le fichier /etc/monit/conf.d/sshd :

check process sshd with pidfile /var/run/sshd.pid
        start program  "/etc/init.d/ssh start"
        stop program  "/etc/init.d/ssh stop"
        if failed port 22 protocol ssh then restart
        if 5 restarts within 5 cycles then timeout

 

Pour que monit démarre, il faut aussi modifier le fichier /etc/default/monit pour y mettre :

startup=1

Un redémarrage de monit et le tour est joué :

/etc/init.d/monit force-reload

 

Il ne reste plus que quelques petits éléments à mettre en place.

Retour au sommaire du tutorial complet.

Graphique d'utilisation du serveur : Munin (Serveur Web sur Debian Squeeze)

Maintenant que le serveur fonctionne et a tous les services requis, il serait bon de savoir si tout fonctionne bien, d'avoir des graphes d'utilisation etc...

Pour céer des graphiques d'utilisation de notre serveur, nous allons utiliser munin.

On l'installe avec quelques librairies utiles au bon fonctionnement de certains plugins et de quoi en ajouter des autres (git et zip) :

aptitude install munin munin-node munin-plugins-extra libhtml-parser-perl libhtml-tagset-perl libhtml-tree-perl liburi-perl libwww-perl git zip unzip

Lorsque l'installation est terminée, commençons par ajouter des plugins standard déjà disponibles, et supprimons-en un qui ne fonctionne pas sur Debian apparamment :

ln -s /usr/share/munin/plugins/netstat /etc/munin/plugins/netstat
ln -s /usr/share/munin/plugins/postfix_mailstats /etc/munin/plugins/postfix_mailstats
ln -s /usr/share/munin/plugins/nginx_request /etc/munin/plugins/nginx_request
ln -s /usr/share/munin/plugins/nginx_status /etc/munin/plugins/nginx_status
ln -s /usr/share/munin/plugins/mysql_bytes /etc/munin/plugins/mysql_bytes
ln -s /usr/share/munin/plugins/mysql_queries /etc/munin/plugins/mysql_queries
ln -s /usr/share/munin/plugins/mysql_slowqueries /etc/munin/plugins/mysql_slowqueries
ln -s /usr/share/munin/plugins/mysql_threads /etc/munin/plugins/mysql_threads
rm /etc/munin/plugins/cpuspeed

Pour l'utilisation de PHP, nous utilisons PHP-FPM. Et nous avons déjà installé de quoi connaître le nombre de processus, la mémoire utilisée etc... Il existe sur github un panel de plugins munin pour créer les graphiques correspondant. Installons les en quelques lignes de commande :

cd /usr/share/munin/plugins
git clone git://github.com/bummercloud/php5-fpm-munin-plugins.git
chmod +x php5-fpm-munin-plugins/phpfpm_*
ln -s /usr/share/munin/plugins/php5-fpm-munin-plugins/phpfpm_average /etc/munin/plugins/phpfpm_average
ln -s /usr/share/munin/plugins/php5-fpm-munin-plugins/phpfpm_connections /etc/munin/plugins/phpfpm_connections
ln -s /usr/share/munin/plugins/php5-fpm-munin-plugins/phpfpm_memory /etc/munin/plugins/phpfpm_memory
ln -s /usr/share/munin/plugins/php5-fpm-munin-plugins/phpfpm_status /etc/munin/plugins/phpfpm_status
ln -s /usr/share/munin/plugins/php5-fpm-munin-plugins/phpfpm_processes /etc/munin/plugins/phpfpm_processes

De la même façon, il est intéressant d'avoir des statistiques sur APC, pour savoir ce qu'il a en cache et si la configuration est adapté :

cd /usr/share/munin/plugins
wget http://munin-php-apc.googlecode.com/files/munin_plugin_php_apc-0.1.zip
unzip munin_plugin_php_apc-0.1.zip
rm munin_plugin_php_apc-0.1.zip
cp /usr/share/munin/plugins/php_apc/apc_info.php /home/var/www/
chown www-data:www-data /home/var/www/apc_info.php
ln -s /usr/share/munin/plugins/php_apc/php_apc_ /etc/munin/plugins/php_apc_fragmentation
ln -s /usr/share/munin/plugins/php_apc/php_apc_ /etc/munin/plugins/php_apc_hit_miss
ln -s /usr/share/munin/plugins/php_apc/php_apc_ /etc/munin/plugins/php_apc_purge
ln -s /usr/share/munin/plugins/php_apc/php_apc_ /etc/munin/plugins/php_apc_rates
ln -s /usr/share/munin/plugins/php_apc/php_apc_ /etc/munin/plugins/php_apc_usage

Voilà nos plugins installé et prêt à être utilisé. Il ne reste plus qu'à en paramétré quelques un pour bien fonctionner sur notre installation. Dans le fichier /etc/munin/plugin-conf.d/munin-node ajouter tout en bas :

[netstat]
user root

[cpuspeed]
user root

[phpfpm_*]
user root
env.url http://localhost/php_status
env.ports 80
env.phpbin php-fpm

[php_apc_*]
user root
env.url http://localhost/apc_info.php?auto

Munin a un fonctionnement simple : il enregistre les chiffres d'un côté, et créer les fichiers images et HTML de l'autre. On peut paramétrer le dossier de destination de ces HTML :

mkdir /home/var/www/munin
chown munin:munin /home/var/www/munin

Puis dans le fichier /etc/munin/munin.conf :

htmldir /home/var/www/munin

Pour accéder aux statistiques générés par munin, nous allons définir un nouveau sous-domaine, protégé par login/mot de passe et accessible uniquement en SSL.

Commençons par généré le certificat SSL :

make-ssl-cert /usr/share/ssl-cert/ssleay.cnf /etc/ssl/private/munin.key
cp /etc/ssl/private/munin.key /etc/ssl/certs/munin.pem

A la question qui vous sera posé, vous devrez renseigné l'URL pour accéder à vos stats. Par exemple munin.domain.tld

La première commande permet de généré le certificat, avec la clé privée et la clé publique dans le même et unique fichier. Ensuite, il faut la copier puis éditez chacun des fichiers pour séparer en deux fichiers.

Dans le fichier /etc/ssl/private/munin.key, il faut ne laisser que ce qui est entre -----BEGIN RSA PRIVATE KEY----- et -----END RSA PRIVATE KEY----- (la clé privée).

Dans le fichier /etc/ssl/certs/munin.pem, il faut ne laisser que ce qui est entre -----BEGIN CERTIFICATE----- et -----END CERTIFICATE----- (la clé publique).

Puis, il faut ajouter notre sous-domaine à Nginx. Dans le fichier /etc/nginx/sites-available/munin :

server {
    listen   [::]:443 ssl;
    ssl_certificate /etc/ssl/certs/munin.pem;
    ssl_certificate_key /etc/ssl/private/munin.key;

    server_name munin.domain.tld;
    root /home/var/www/munin/;
    
    auth_basic "RESTRICTED ACCESS";
    auth_basic_user_file /home/var/www/munin/.htpasswd;

    location ~ /\. {
        deny  all;
    }
    access_log /var/log/nginx/munin/access.log;
    error_log /var/log/nginx/munin/error.log;
}

Créeons le dossier des logs et activons le site :

mkdir /var/log/nginx/munin
ln -s /etc/nginx/sites-available/munin /etc/nginx/sites-enabled/munin

Enfin, il faut créer le fichier /home/var/www/munin/.htpasswd : Chaque ligne correspond à un utilisateur suivi de son mot de pass crypté, séparé par :. Pour crypter le ot de passe, vous pouvez utilisez ce formulaire en ligne.

 

Comme d'habitude, on termine en rechargeant les démons modifiés :

/etc/init.d/munin-node force-reload
/etc/init.d/nginx force-reload

 

Plus qu'à prendre son mal en patience pour voir les graphes se remplir. En attendont, passons à l'installation de monit, pour surveiller tous ces services.

 

Retour au sommaire du tutorial complet.

Sécurité du serveur : fail2ban (Serveur Web sur Debian Squeeze)

fail2ban est utilisé pour lire les log de tous les autres démons et bannir des adresses IP s'ils ont échoués à plusieurs reprises de se connecter.

On installe simplement :

aptitude install fail2ban

Puis dans le fichier /etc/fail2ban/jail.conf nous allons paramétré chacun de nos règles pour tous les différents services que propose notre serveur. Certaines sont déjà pre-paramétrées, mais nous allons aussi en ajouter d'autres.

destemail = yoy@email.com
action = %(action_mwl)s

Puis dans le fichier, votre trouver des sections. Il faut simplement mettre enabled = true pour activer un filtre. Dans notre cas, vous pouvez directement activer pam-generic, apache, apache-noscript, apache-overflows, postfix and sasl. Bien sûr, vous pouvez modifier le nombre d'essai, le temps de ban, etc...

Pour apache, vous aurez compris qu'on ne peut pas utiliser la configuration par défaut. En effet, nous n'utilisons pas apache ! Mais les logs de Nginx sont similaire à ceux d'Apache. Il suffit simplement d'indiquer le bon chemin vers les fichiers de logs. Selon la configuration de chacun de vos virtualhost, utilisez :

logpath = /var/log/nginx/*error.log /var/log/nginx/*/*error.log

D'autre part, pour postfix, je désactive l'envoi de l'alerte par email car il y en aurait beaucoup trop. Dans la section de postfix :

action   = %(action_)s

Enfin, le chemin du sasl n'est pas bon car on utilise postix. Comme indiqué dans le fichier en commentaire, il faut utiliser :

logpath  = /var/log/warn.log

 

Puis ajoutons quelques règles qui ne sont pas par défaut dans fail2ban, mais que j'ai trouvé ici et là.

 

A la fin du fichier, ajoutez :

[apache-badbots]
enabled = true
port    = http,https
filter  = apache-badbots
logpath = /var/log/nginx/*error.log /var/log/nginx/*/*error.log
bantime = 86400
maxretry = 1

[apache-w00tw00t]
enabled = true
filter  = apache-w00tw00t
port = all
banaction = iptables-allports
port     = anyport
logpath  = /var/log/nginx/*error.log /var/log/nginx/*/*error.log
maxretry = 1
bantime = 86400

[apache-404]
enabled = true
filter  = apache-404
port = all
banaction = iptables-allports
port     = anyport
logpath  = /var/log/nginx/*error.log /var/log/nginx/*/*error.log
maxretry = 10
bantime = 86400

[pdns]
enabled = true
filter = pdns
port = all
banaction = iptables-allports
port = anyport
logpath = /var/log/pdns.log
maxretry = 1
bantime = 86400

[pdns-recursion]
enabled = true
filter = pdns-recursion
port = all
banaction = iptables-allports
port = anyport
logpath = /var/log/pdns.log
maxretry = 1
bantime = 86400

Et ensuite, il faut créer les fichiers filter de chacun de ces nouveaux filtres.

Le fichier /etc/fail2ban/filter.d/apache-404.conf :

[Definition]
failregex = ˆ<HOST> - - .* 404 .* "-" .*
ignoreregex =

Le fichier /etc/fail2ban/filter.d/apache-w00tw00t.conf :

[Definition]
failregex = ˆ<HOST> -.*"GET \/w00tw00t\.at\.ISC\.SANS\.DFind\:\).*".*
ignoreregex =

Le fichier /etc/fail2ban/filter.d/pdns.conf :

[Definition]
failregex = .*AXFR of domain '.*' denied to <HOST>
ignoreregex =

Le fichier /etc/fail2ban/filter.d/pdns-recursion.conf :

[Definition]
failregex = .*Not authoritative for '.*' sending servfail to <HOST> (recursion was desired)
ignoreregex =

 

Et voilà, il ne reste plus qu'à redémarrer le démon :

/etc/init.d/fail2ban force-reload

 

Et on passe à la suite, Munin pour créer des graphiques d'utilisation du serveur.

Retour au sommaire du tutorial complet.

Serveur email : Postfix, Dovecot, Dspam et ClamAV (Serveur Web sur Debian Squeeze)

Cette partie du tutorial est la plus longue car elle nécessite beaucoup de configuration. En effet, c'est pas moins de 4 démons que nous allons installé et configuré pour fonctionner parfaitement avec le reste de notre serveur. De plus notre serveur d'emails incluera un antispam pour tous les emails entrant, qui en plus vérifiera si les emails ne contiennent pas des virus grâce à ClamAV. Enfin, il sera possible d'entrâiner l'antispam simplement en déplacement un email du dossier enrant vers le dossier Spam et inversement.

Vous êtes prêt ?

 

On commence par désinstaller les logiciels d'emails énventuellement présents, puis par installer ceux qu'on désire :

aptitude purge exim4 exim4-base exim4-config exim4-daemon-light bsd-mailx
aptitude install postfix postfix-mysql dovecot-imapd dovecot-pop3d dovecot-dev dspam dspam-doc libdspam7-drv-mysql bzip2 clamav-daemon make sudo

Durant l'installation, il vous sera demandé le type de configuration pour postfix. Sélectionnez Sites internet. Puis pour le nom, le champ par défaut sera sûrement très bien : il s'agit du reverseIP de votre serveur (nsXXX.ovh.net pour un serveur OVH par exemple)

Puis quelques questions arrivent pour la configuration du stockage de données de Dspam, votre antispam. Il faudra répondre que vous voulez que la base de données soit créés, puis renseigné votre mot de passe root MySQL. Enfin le mot de passe de la librairei, vus pouvez laisser vide pour qu'il en génére un aléatoirement pour vous. Comme le fichier de configuration est changé par ce choix de mot de passe, il vous demandera si vous souhaitez conserver la version de la configuration. Il faudra répondre : Installer la version du responsable du paquet.

 

Dans l'étape précédente, nous avons mis en place une base de données pour configurer les DNS de notre serveur. Cette base de données contient l'ensemble des noms de domaines qui sont gérer par notre serveur. Nous allons y ajouter 2 tables pour gérer les alias emails et une autre pour configurer les adresse emails, grâce au fichier de création SQL. Comme tout à l'heure, on l'exécute de cette façon :

mysql -h localhost -u root --password=MySQL serverconf < /PATH/TO/postfix.sql

Pour utiliser cette base de données, postfix a besoin de savoir comment y accéder. Nous allons mettre ces fichiers dans le dossier /etc/postfix/mysqlà créer :/p>

mkdir /etc/postfix/mysql

Puis y placer nos différents fichiers de configurations.

/etc/postfix/mysql/email2email.cf est utilisé pour connaître les adresse email existantes :

hosts = 127.0.0.1
dbname = serverconf
user = serverconfuser
password = serverconfpass
query = SELECT CONCAT(user.user, '@', domain.name) AS email FROM domain,user WHERE user.domain_id=domain.id AND CONCAT(user.user, '@', domain.name)='%s'

/etc/postfix/mysql/virtual-alias-maps.cf est utilisé pour connaître les alias (les redirection d'emails) :

hosts = 127.0.0.1
dbname = serverconf
user = serverconfuser
password = serverconfpass
query = SELECT destination FROM alias, domain WHERE alias.domain_id=domain.id AND CONCAT(alias.source, '@', domain.name)='%s'

/etc/postfix/mysql/virtual-mailbox-domains.cf est utilisé pour connaître les domaines gérés :

hosts = 127.0.0.1
dbname = serverconf
user = serverconfuser
password = serverconfpass
query = SELECT 1 FROM domain WHERE name='%s'

/etc/postfix/mysql/virtual-mailbox-maps.cf est utilisé pour vérifier si une adresse email est bien géré par le serveur :

hosts = 127.0.0.1
dbname = serverconf
user = serverconfuser
password = serverconfBlaBlu00
query = SELECT 1 FROM user,domain WHERE user.domain_id=domain.id AND CONCAT(user.user, '@', domain.name)='%s'

 

Pour terminer la configuration de postfix, il faut paramétrer quelques port à écouter et comment il doit les interpréter. Dans le fichier /etc/postfix/master.cf, commenter la ligne commençant par smtp et la remplacer par :

smtp inet n - - - - smtpd -o content_filter=lmtp:unix:/var/run/dspam.sock
smtps inet n - - - - smtpd -o smtpd_tls_wrappermode=yes -o content_filter=lmtp:unix:/var/run/dspam.sock

On voit que la configuration de Dspam est déjà présente. Puis en bas du fichier /etc/postfix/master.cf, ajouter les lignes :

127.0.0.1:10026 inet n - - - - smtpd
-o content_filter=
-o receive_override_options=no_unknown_recipient_checks,no_header_body_checks
-o smtpd_helo_restrictions=
-o smtpd_client_restrictions=
-o smtpd_sender_restrictions=
-o smtpd_recipient_restrictions=permit_mynetworks,reject
-o mynetworks=127.0.0.0/8
-o smtpd_authorized_xforward_hosts=127.0.0.0/8

dovecot unix - n n - - pipe
flags=DRhu user=vmail:vmail argv=/usr/bin/sudo /usr/lib/dovecot/deliver -d ${recipient}

slow unix - - n - 5 smtp
-o syslog_name=postfix-slow
-o smtp_destination_concurrency_limit=3
-o slow_destination_rate_delay=1

La première partie permet à Dspam de délivrer les emails. La seconde permet à postfix de savori comment parler avec dovecot. La dernière permet de délivrer les emails pour orange beaucoup plus rapidement (cf cette page)

Pour bien paramétré ce fonctionnement spécial d'orange et consors, créer le fichier /etc/postfix/transport pour y mettre :

wanadoo.com slow:
wanadoo.fr slow:
orange.com slow:
orange.fr slow:

Puis transformer le en fichier utilsiable par postfix :

postmap /etc/postfix/transport

Pour bien fonctionner, ces éléments vont voir besoin d'exécuter des commandes en tant que root. Bien sûr, comme ces démons ne doivent pas connaître le mot de passe root pour des raisons évidentes de sécurité, nous allons les ajoutés dans le fichier /etc/sudoers.d/mail :

Defaults:postfix !syslog
postfix ALL=NOPASSWD:/usr/lib/dovecot/deliver
Defaults:clamav !syslog
clamav ALL=NOPASSWD:/usr/lib/dovecot/deliver
Defaults:dovecot !syslog
dovecot ALL=NOPASSWD:/usr/lib/dovecot/deliver
Defaults:dspam !syslog
dspam ALL=NOPASSWD:/usr/lib/dovecot/deliver
Defaults:vmail !syslog
vmail ALL=NOPASSWD:/usr/lib/dovecot/deliver

Et on applique les bons droits d'accès à ce fichier :

chmod 0440 /etc/sudoers.d/mail

Et voici la configuration général de postfix à ajouter dans le fichier /etc/postfix/main.cf :

virtual_mailbox_domains = mysql:/etc/postfix/mysql/virtual-mailbox-domains.cf
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000
virtual_mailbox_maps = mysql:/etc/postfix/mysql/virtual-mailbox-maps.cf
virtual_alias_maps = mysql:/etc/postfix/mysql/virtual-alias-maps.cf,mysql:/etc/postfix/mysql/email2email.cf
virtual_transport = dovecot
dovecot_destination_recipient_limit = 1
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_recipient_restrictions = permit_mynetworks,permit_auth_destination,permit_sasl_authenticated,reject_unauth_destination
smtpd_tls_auth_only = yes
transport_maps = hash:/etc/postfix/transport
maximal_queue_lifetime = 3d
bounce_queue_lifetime = 3d
broken_sasl_auth_clients = yes
message_size_limit = 20480000
slow_destination_recipient_limit = 20
slow_destination_concurrency_limit = 2

 

S'en est fini de la configuration de Postfix. Passons à Dovecot.

Pour permettre à dovecot d'entraîner Dspam lorsqu'un email passe du dossier Spam a un dossier de réception ou inversement, nous avons besoin d'installer un plugin. Télécharger ce plugin ici et envoyer le tar.gz sur votre serveur dans le dossier personnel de root. Puis exécutez les commandes :

cd
mv /home/user/data/dovecot-antispam-HEAD-98b5e06.tar.gz ./
gzip -d dovecot-antispam-HEAD-98b5e06.tar.gz
tar xvf dovecot-antispam-HEAD-98b5e06.tar
cd dovecot-antispam-HEAD-98b5e06
cp defconfig .config

Editez le fichier .config pour y décommenter la ligne :

DOVECOT=/usr/include/dovecot

Votre plugin est maintenant prêtre à être compilé et installé. Une seule ligne de commande :

make install

Ce plugin étant prêt à être utilisé, passons à la configuration générale de dovecot dans le fichier /etc/dovecot/dovecot.conf :

protocols = imap imaps pop3 pop3s managesieve
mail_location = maildir:/home/var/vmail/%d/%n/Maildir:INBOX=/home/var/vmail/%d/%n/Maildir/:INDEX=/home/var/vmail/%d/%n/Maildir/tmp/index

Dans la partie protocol imap :

  mail_plugins = antispam
  mail_plugin_dir = /usr/lib/dovecot/modules/imap
  imap_client_workarounds = outlook-idle delay-newmail tb-extra-mailbox-sep

Dans la partie protocol pop3 :

  pop3_client_workarounds = outlook-no-nuls oe-ns-eoh

Dans la partie protocol managesieve :

  mail_location=~/.dovecot.sieve

Dans la partie protocol lda :

   postmaster_address = postmaster@domain.tld
   auth_socket_path = /var/run/dovecot/auth-master
   mail_plugins = sieve
   log_path = /home/var/log/dovecot-deliver.log
   global_script_path = /home/var/vmail/globalsieverc

Dans la partie auth default :

 mechanisms = plain login
 # Commenter la partie passdb pam
 # Décommenter la partie passdb sql avec comme paramètres :
    args = /etc/dovecot/dovecot-sql.conf

Dans la partie userdb static :

    args = uid=5000 gid=5000 home=/home/var/vmail/%d/%n allow_all_users=yes

 Dans la partie socket listen :

    master {
        path = /var/run/dovecot/auth-master
        mode = 0600
        user = vmail
    }
    client {
        path = /var/spool/postfix/private/auth
        mode = 0660
        user = postfix
        group = postfix
    }


Dans la partie plugin, nous allons paramètrer le plugin précédemment compilé :

sieve_storage=~/sieve

##################################
# Antispam plugin

# Debugging options
# Uncomment to get the desired debugging behaviour.
# Note that in some cases stderr debugging will not be as
# verbose as syslog debugging due to internal limitations.
#
# antispam_debug_target = syslog
# antispam_debug_target = stderr
# antispam_verbose_debug = 1

# backend selection, MUST be configured first,
# there's no default so you need to set one of
# these options:
# antispam_backend = crm114
antispam_backend = dspam
# antispam_backend = pipe
# antispam_backend = spool2dir

# mail signature (used with any backend requiring a signature)
antispam_signature = X-DSPAM-Signature

# action to take on mails without signature
# (used with any backend requiring a signature)
# (we recommend only setting this to 'move' after verifying that the
# whole setup is working)
# antispam_signature_missing = move # move silently without training
antispam_signature_missing = move

# The list of folders for trash, spam and unsure can be given
# with three options, e.g. "trash" matches the given folders
# exactly as written, "trash_pattern" accept the * wildcard at
# the end of the foldername, "trash_pattern_ignorecase"
# accepts the * wildcard at the end of the foldername _and_
# matches the name case insensitivly.

# the *-wildcard with the following meaning:
# * at the end: any folder that _start_ with the string
# e.g.:
# antispam_trash_pattern = deleted *;Gel&APY-schte *
# match any folders that start with "deleted " or "Gelöschte "
# match is _case_senstive_!
#
# antispam_trash_pattern_ignorecase = deleted *;Gel&APY-schte *
# match any folders that start with "deleted " or "gelöschte "
# match is _case_insenstive_, except the non-USASCII letters,
# "ö" in this example.
# To match the upper-case Ö, too, you need to add yet another
# pattern "gel&ANY-schte *", note the different UTF7 encoding:
# &ANY- instead of &APY-.


# semicolon-separated list of Trash folders (default unset i.e. none)
antispam_trash = trash;Trash;Deleted Items; Deleted Messages;&AMk-l&AOk-ments supprim&AOk-s
# antispam_trash = trash;Trash;Deleted Items; Deleted Messages
# antispam_trash_pattern = trash;Trash;Deleted *
# antispam_trash_pattern_ignorecase = trash;Deleted *

# semicolon-separated list of spam folders
antispam_spam = Spam;spam;Junk;junk;Courrier ind&AOk-sirable
# antispam_spam_pattern = SPAM
# antispam_spam_pattern_ignorecase = SPAM

# semicolon-separated list of unsure folders (default unset i.e. none)
# antispam_unsure =
# antispam_unsure_pattern =
# antispam_unsure_pattern_ignorecase =

# Whether to allow APPENDing to SPAM folders or not. Must be set to
# "yes" (case insensitive) to be activated. Before activating, please
# read the discussion below.
# antispam_allow_append_to_spam = no

###########################
# BACKEND SPECIFIC OPTIONS
#

#===================
# dspam plugin

# dspam binary
antispam_dspam_binary = /usr/bin/dspam

# semicolon-separated list of extra arguments to dspam
# (default unset i.e. none)
antispam_dspam_args = --user;root;--source=error
# antispam_dspam_args = --deliver=;--user;%u # % expansion done by dovecot
# antispam_dspam_args = --mode=teft

# Ignore mails where the DSPAM result header contains any of the
# strings listed in the blacklist
# (default unset i.e. none)
# antispam_dspam_result_header = X-DSPAM-Result
# semicolon-separated list of blacklisted results, case insensitive
# antispam_dspam_result_blacklist = Virus

 

Il faut configurer dovector pour utiliser correctement notre base de données dans le fichier /etc/dovecot/dovecot-sql.conf :

driver = mysql
connect = host=127.0.0.1 dbname=serverconf user=serverconfuser password=serverconfpass
default_pass_scheme = PLAIN-MD5
password_query = SELECT CONCAT(user.user, '@', domain.name) AS user,user.password FROM user,domain WHERE user.domain_id=domain.id AND CONCAT(user.user, '@', domain.name)='%u'

 

Comme le serveur utilise des protocoles sécurisés, nous devons créés des certificats :

openssl req -new -x509 -days 3650 -nodes -out /etc/ssl/certs/dovecot.pem -keyout /etc/ssl/private/dovecot.pem
# Country Name (2 letter code) [AU]:                        FR
# State or Province Name (full name) [Some-State]:            France
# Locality Name (eg, city) []:                            Besancon
# Organization Name (eg, company) [Internet Widgits Pty Ltd]:        Company Name
# Organizational Unit Name (eg, section) []:                    .
# Common Name (eg, YOUR name) []:                            domain.tld
# Email Address []:                                    certs@domain.tld
openssl req -new -x509 -days 3650 -nodes -out /etc/ssl/certs/postfix.pem -keyout /etc/ssl/private/postfix.pem

Et de donner les droits nécessaires :

chmod o= /etc/ssl/private/dovecot.pem
chmod o= /etc/ssl/private/postfix.pem

 

Il faut aussi créer l'utilisateur vmail, créer quelques fichiers vides avec les bons droits, etc :

groupadd -g 5000 vmail
useradd -g vmail -u 5000 vmail -d /home/var/vmail -m
rm -rf /home/var/vmail/* /home/var/vmail/.*
touch /home/var/log/dovecot-deliver.log
chmod 777 /home/var/log/dovecot-deliver.log

chgrp postfix /etc/postfix/mysql/*.cf
chmod u=rw,g=r,o= /etc/postfix/mysql/*.cf
chown vmail:vmail /etc/dovecot/dovecot.conf
chmod g+r /etc/dovecot/dovecot.conf

 

Postfix et Dovecot sont maintenant configuré, plus qu'à configuré l'antispam Dspam et l'antivirus ClamAV.

Pour DSpam, commençons par créer son dossier d'exécution avec les bons droits :

mkdir -p /var/spool/postfix/var/run/
chown dspam:dspam /var/spool/postfix/var/run/
chown -R dspam:dspam /etc/dspam/*

 

Puis le paramétrer dans /etc/dspam/dspam.conf (seules les lignes qui changents sont indiqués ici) :

StorageDriver /usr/lib/dspam/libmysql_drv.so
#TrustedDeliveryAgent "/usr/lib/dovecot/deliver -d %u"
DeliveryHost 127.0.0.1
DeliveryPort 10026
DeliveryIdent localhost
DeliveryProto SMTP
QuarantineAgent "/usr/bin/sudo /usr/lib/dovecot/deliver -d %u -m Spam"
Trust vmail
Feature noise
WebStats no
Preference "spamAction=quarantine"
Preference "signatureLocation=headers" # 'message' or 'headers'
#Preference "showFactors=on"
TrackSources spam virus
ClamAVPort 3310
ClamAVHost 127.0.0.1
ClamAVResponse reject
ServerPID /var/run/dspam/dspam.pid
ServerMode auto
ServerPass.Relay1 "secret"
ServerParameters "--deliver=innocent -d %u"
ServerIdent "localhost.localdomain"
ServerDomainSocketPath "/var/spool/postfix/var/run/dspam.sock"
ClientHost /var/spool/postfix/var/run/dspam.sock
ClientIdent "secret@Relay1"

On modifie la configuration du stockage dans MySQL dans /etc/dspam/dspam.d/mysql.conf :

MySQLUIDInSignature    on

On paramètre DSpam pour regrouper tous les apprentissages de tous les emails dans un seul et même utilisateur dpsam dans vi /var/spool/dspam/group :

dspam:shared:*

Et on indique au démon qu'il peut maintenant démarrer dans /etc/default/dspam :

Start=yes

 

Nous y sommes presque, plus que l'antivirus !

Dans /etc/clamav/clamd.conf

# Commenter la ligne avec  localSocket et ajouter :
TCPAddr 127.0.0.1
TCPSocket 3310

 

Et voilà !

 

Il ne reste plus qu'à tout redémarrer :

/etc/init.d/clamav-daemon restart
/etc/init.d/dspam restart
/etc/init.d/postfix restart
/etc/init.d/dovecot restart

Attention, dans certains cas il faudra peut-être redémarrer plusieurs fois dovecot.

 

Si vous avez suivi tout le tutorial jusqu'à maintenant, votre serveur est maintenant tout à fait opérationnel avec toutes les options de base dont il a besoin. Mais nous pouvons encore y ajouter un peu de sécurité et de monitoring...


Retour au sommaire du tutorial complet.