NetAdmin — Console DNS & Mail

Interface d'administration DNS et Mail pour serveurs BIND9 + Postfix/Dovecot.


Architecture

┌─────────────────────────────────────────────────────────┐
│  Frontend React (Vite)          :3000                   │
│  Backend FastAPI                :8000                   │
│  MariaDB (base: netadmin)       — données NetAdmin      │
│  MariaDB (base: netadmin_mail)  — données Postfix       │
│  Redis mTLS broker              :6380                   │
│         │                                               │
│  dns_agent.py  ←→ dns.commands/acks   → BIND9          │
│  mail_agent.py ←→ mail.commands/acks  → Postfix        │
└─────────────────────────────────────────────────────────┘

Installation

Prérequis

  • Python 3.11+
  • Node.js 18+
  • MariaDB 10.6+
  • Redis 7+
  • BIND9 9.20+
  • Postfix + Dovecot
  • Rspamd (optionnel, pour DKIM)

Bases de données MariaDB

Base NetAdmin (gestion interne)

-- Se connecter en root MariaDB
mysql -u root -p

-- Créer l'utilisateur et la base
CREATE DATABASE netadmin CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'netadmin'@'localhost' IDENTIFIED BY 'mot_de_passe_fort';
GRANT ALL PRIVILEGES ON netadmin.* TO 'netadmin'@'localhost';
FLUSH PRIVILEGES;

Les tables sont créées automatiquement au premier démarrage du backend.

Base netadmin_mail (lue par Postfix/Dovecot)

À créer sur le serveur mail :

CREATE DATABASE netadmin_mail CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'postfix'@'localhost' IDENTIFIED BY 'mot_de_passe_fort';
GRANT ALL PRIVILEGES ON netadmin_mail.* TO 'postfix'@'localhost';
FLUSH PRIVILEGES;

Créer les tables manuellement :

USE netadmin_mail;

CREATE TABLE domain (
    domain       VARCHAR(253) NOT NULL,
    description  VARCHAR(255) NOT NULL DEFAULT '',
    max_accounts INT          NOT NULL DEFAULT 0,
    max_aliases  INT          NOT NULL DEFAULT 0,
    max_quota_mb INT          NOT NULL DEFAULT 0,
    mb_quota_mb  INT          NOT NULL DEFAULT 0,
    active       TINYINT(1)   NOT NULL DEFAULT 1,
    created      DATETIME     NOT NULL DEFAULT NOW(),
    modified     DATETIME     NOT NULL DEFAULT NOW(),
    PRIMARY KEY (domain)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE mailbox (
    username  VARCHAR(128) NOT NULL,
    password  VARCHAR(255) NOT NULL,
    name      VARCHAR(255) NOT NULL DEFAULT '',
    domain    VARCHAR(253) NOT NULL,
    quota_mb  INT          NOT NULL DEFAULT 1024,
    active    TINYINT(1)   NOT NULL DEFAULT 1,
    created   DATETIME     NOT NULL DEFAULT NOW(),
    modified  DATETIME     NOT NULL DEFAULT NOW(),
    PRIMARY KEY (username),
    FOREIGN KEY (domain) REFERENCES domain(domain) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE alias (
    address  VARCHAR(253) NOT NULL,
    goto     TEXT         NOT NULL,
    domain   VARCHAR(253) NOT NULL,
    active   TINYINT(1)   NOT NULL DEFAULT 1,
    created  DATETIME     NOT NULL DEFAULT NOW(),
    modified DATETIME     NOT NULL DEFAULT NOW(),
    PRIMARY KEY (address),
    FOREIGN KEY (domain) REFERENCES domain(domain) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE sender_login_maps (
    sender     VARCHAR(255) NOT NULL,
    authorized VARCHAR(255) NOT NULL,
    domain     VARCHAR(253) NOT NULL,
    PRIMARY KEY (sender, authorized)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Si le backend NetAdmin tourne sur un serveur différent du serveur mail, autoriser la connexion distante :

CREATE USER 'netadmin_agent'@'IP_SERVEUR_NETADMIN' IDENTIFIED BY 'mot_de_passe_fort';
GRANT ALL PRIVILEGES ON netadmin_mail.* TO 'netadmin_agent'@'IP_SERVEUR_NETADMIN';
FLUSH PRIVILEGES;

Permissions du fichier de configuration

# Le fichier config.yaml doit être lisible par l'utilisateur netadmin
chown root:netadmin /etc/netadmin/config.yaml
chmod 640 /etc/netadmin/config.yaml
chown root:netadmin /etc/netadmin
chmod 750 /etc/netadmin

Backend

cd backend/
python3 -m venv ../venv
source ../venv/bin/activate
pip install -r requirements.txt
cp config.yaml /etc/netadmin/config.yaml   # adapter les valeurs
uvicorn main:app --host 0.0.0.0 --port 8000

Frontend

cd frontend/
npm install
npm run build          # production
# ou
npm run dev            # développement :3000

Agents

# Agent DNS (sur le serveur BIND9)
cd agents/
pip install -r requirements.txt
cp ../backend/config.yaml /etc/netadmin/config.yaml   # section dns_agent
python3 dns_agent.py

# Agent Mail (sur le serveur Postfix)
python3 mail_agent.py

Services systemd

cp agents/netadmin-dns-agent.service  /etc/systemd/system/
cp agents/netadmin-mail-agent.service /etc/systemd/system/
systemctl enable --now netadmin-dns-agent netadmin-mail-agent

Configuration

backend/config.yaml

dns:
  default_ns:
    - "ns1.example.fr."
    - "ns2.example.fr."
  default_soa_admin: "hostmaster.example.fr"
  dnssec_enabled: true
  key_dir:        "/etc/bind/keys"

mail_default_aliases:
  abuse:      "postmaster@example.fr"
  hostmaster: "postmaster@example.fr"
  postmaster: "admin@example.fr"
  webmaster:  "admin@example.fr"

dns_agent:
  zones_dir:        "/etc/bind/zones"
  named_conf_local: "/etc/bind/named.conf.local"
  rndc_cmd:         "rndc"
  key_dir:          "/etc/bind/keys"
  dnssec_policy:    "default"

mail_agent:
  dkim_dir:      "/var/lib/rspamd/dkim"
  dkim_selector: "mail"

Postfix main.cf

virtual_mailbox_domains = mysql:/etc/postfix/mysql_virtual_mailbox_domains.cf
virtual_mailbox_maps    = mysql:/etc/postfix/mysql_virtual_mailbox_maps.cf
virtual_alias_maps      = mysql:/etc/postfix/mysql_virtual_alias_maps.cf
smtpd_sender_login_maps = mysql:/etc/postfix/mysql_sender_login_maps.cf
smtpd_sender_restrictions = reject_sender_login_mismatch, permit

Fichiers .cf Postfix (pointent sur netadmin_mail)

# mysql_virtual_mailbox_domains.cf
query = SELECT domain FROM domain WHERE domain='%s' AND active=1

# mysql_virtual_mailbox_maps.cf
query = SELECT CONCAT(domain,'/',SUBSTRING_INDEX(username,'@',1),'/') 
        FROM mailbox WHERE username='%s' AND active=1

# mysql_virtual_alias_maps.cf
query = SELECT goto FROM alias WHERE address='%s' AND active=1

# mysql_sender_login_maps.cf
query = SELECT authorized FROM sender_login_maps WHERE sender='%s'

Migration depuis PostfixAdmin

# Aperçu sans modification
python3 migrate_postfixadmin.py --dry-run \
  --user root --password xxx \
  --source-db postfix --target-db netadmin_mail

# Migration réelle
python3 migrate_postfixadmin.py \
  --user root --password xxx \
  --source-db postfix --target-db netadmin_mail

Le script migre : domaines, boîtes aux lettres (SHA512-CRYPT conservé), alias. Les mots de passe $1$ (MD5) ou $2$ (bcrypt) sont marqués {MIGRATION_REQUIRED} et le compte est désactivé — reset admin requis.

Scripts SQL de correction

# Corriger les alias avec source en partie locale seulement
mysql netadmin -e "UPDATE aliases SET source = CONCAT(source, '@', domain) WHERE source NOT LIKE '%@%';"

# Corriger les double domaines dans mailbox (si migration partielle)
mysql netadmin_mail -e "
UPDATE mailbox
SET username = CONCAT(SUBSTRING_INDEX(username,'@',1),'@',SUBSTRING_INDEX(username,'@',-1))
WHERE username LIKE '%@%@%';"

Fonctionnalités

DNS

  • Gestion des zones BIND9 (création via Domaines uniquement)
  • Enregistrements : A, AAAA, CNAME, MX, TXT, NS, PTR, SRV, CAA, TLSA, DS
  • DNSSEC par zone (activation/désactivation, récupération des clés DS)
  • NS par défaut configurables, email SOA admin par défaut
  • Archivage automatique des clés DNSSEC dans key_dir/old/

Mail

  • Gestion des domaines mail avec quotas (global + par BAL)
  • Boîtes aux lettres avec quota individuel et nom affiché
  • Alias avec activation/désactivation
  • DKIM via Rspamd (rspamadm dkim_keygen)
  • Alias RFC obligatoires créés automatiquement (abuse, postmaster, hostmaster, webmaster)
  • Sender login maps (prochainement)
  • Mots de passe hashés en {SHA512-CRYPT} via passlib

Administration

  • Authentification locale + OpenID Connect (Keycloak)
  • TOTP (2FA) par compte, obligatoire ou optionnel
  • RBAC : global_admin, dns_admin, mail_admin, domain_admin, dns_zone_admin
  • Préférences utilisateur (thème clair/sombre) persistées en BDD
  • Audit log complet
  • Notifications email à la connexion

Interface

  • Thème sombre / clair
  • Création de domaine unifiée (DNS + Mail)
  • Validation des enregistrements DNS (FQDN, IPv4, IPv6)
  • Affichage des enregistrements DS détaillés (Key Tag, Algorithme, Digest)
  • Interface entièrement en français

Sécurité

Génération du session_secret

Le session_secret dans config.yaml est utilisé pour signer les tokens de session. Il doit être une clé aléatoire forte, unique par installation :

# Avec Python (recommandé)
python3 -c "import secrets; print(secrets.token_hex(32))"

# Avec OpenSSL
openssl rand -hex 32

Les deux génèrent une clé de 64 caractères hexadécimaux (256 bits). À renseigner dans config.yaml :

auth:
  session_secret: "coller_la_clé_générée_ici"

⚠ Ne jamais committer cette valeur dans un dépôt Git. Utiliser une variable d'environnement ou un gestionnaire de secrets en production.

Certificats mTLS Redis

cd agents/
bash gen_certs.sh   # génère CA + certificats agents

Copier les certificats dans /etc/netadmin/certs/.

Premier compte admin

Au premier démarrage, NetAdmin crée automatiquement :

  • Utilisateur : admin
  • Mot de passe : admin
  • Changer immédiatement après connexion.

Structure des fichiers

netadmin/
├── backend/
│   ├── main.py              # FastAPI app
│   ├── config.py            # Dataclasses de configuration
│   ├── config.yaml          # Configuration principale
│   ├── db.py                # Connexion MariaDB (netadmin)
│   ├── broker.py            # File Redis + pending_actions
│   ├── audit.py             # Audit log
│   ├── notify.py            # Notifications email
│   ├── requirements.txt
│   └── routers/
│       ├── auth.py          # Auth locale + OIDC + TOTP + RBAC
│       ├── dns.py           # Zones + enregistrements + DNSSEC
│       └── mail.py          # Domaines + comptes + alias + DKIM
├── agents/
│   ├── dns_agent.py         # Agent BIND9
│   ├── mail_agent.py        # Agent Postfix/Dovecot/Rspamd
│   ├── requirements.txt
│   ├── gen_certs.sh         # Génération certificats mTLS
│   ├── netadmin-dns-agent.service
│   └── netadmin-mail-agent.service
├── frontend/
│   ├── src/
│   │   ├── App.jsx          # Routing + sidebar + auth
│   │   ├── App.css          # Thème sombre/clair
│   │   ├── main.jsx         # Point d'entrée + polices
│   │   └── components/
│   │       ├── LoginPage.jsx
│   │       ├── DomainsManager.jsx   # DNS + Mail par domaine
│   │       ├── DNSManager.jsx       # Enregistrements + DNSSEC
│   │       ├── MailManager.jsx      # Comptes + alias
│   │       ├── UsersManager.jsx     # Utilisateurs + rôles
│   │       ├── UserProfile.jsx      # Mon compte + 2FA + thème
│   │       ├── AuditLog.jsx
│   │       ├── SyncDashboard.jsx
│   │       └── SyncToast.jsx
│   ├── package.json
│   └── vite.config.js
├── migrate_postfixadmin.py  # Migration depuis PostfixAdmin
├── fix_alias_source.sql     # Correction alias partiels
├── fix_double_domain.sql    # Correction double domaine
└── README.md

Bases de données

netadmin (gestion NetAdmin)

  • users, user_roles, sessions, user_preferences
  • zones, records (DNS)
  • domains, accounts, aliases (Mail NetAdmin)
  • pending_actions, audit_log, sync_history

netadmin_mail (lue par Postfix/Dovecot)

  • domain — domaines hébergés
  • mailbox — boîtes aux lettres (username = email complet)
  • alias — alias et redirections (inclut self-aliases pour virtual_alias_maps)
  • sender_login_maps — autorisations d'envoi
S
Description
No description provided
Readme
571 KiB
Languages
Python 48%
JavaScript 44.5%
Shell 4.2%
CSS 3.2%