Agent DNS - load_config() lit maintenant la section `redis` du config.yaml (host, port, password, ssl, ssl_ca_cert, ssl_certfile, ssl_keyfile) → l'agent ne tombait plus sur localhost:6379 par défaut - Suppression de retry_on_timeout (déprécié redis-py 6.0) - Suppression de socket_keepalive_options (clés string invalides) - int() forcé sur port et db dans run() pour éviter TypeError - Traceback complet sur fatal error - get_state: relativize=False → dnspython conserve les FQDN complets - get_state: parse les enregistrements "; [DISABLED]" pour les inclure avec active=False dans le state retourné - _fmt_value: ne ajoute plus le point final sur les noms relatifs (srvweb → reste srvweb, srvweb.infolix.fr → srvweb.infolix.fr.) - save_zone_raw: named-checkzone + snapshot (même logique qu'apply_zone) - gen_certs.sh: REDIS_HOST obligatoire, détection IP vs FQDN pour le SAN Backend DNS - _sync_zone_from_state: normalise les noms FQDN → court avant _stable_id (mail.infolix.fr. → mail pour correspondre à ce qui est en BDD) - _sync_zone_from_state: active=%s au lieu de COALESCE(active,1) → les enregistrements désactivés importés depuis le fichier de zone sont correctement marqués active=0 en BDD Frontend - App.jsx: VITE_API_BASE prioritaire sur localStorage - validateRecord: accepte les noms relatifs sans point pour CNAME/MX/NS
12 KiB
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/
- 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}viapasslib
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_preferenceszones,records(DNS)domains,accounts,aliases(Mail NetAdmin)pending_actions,audit_log,sync_history
netadmin_mail (lue par Postfix/Dovecot)
domain— domaines hébergésmailbox— boîtes aux lettres (username = email complet)alias— alias et redirections (inclut self-aliases pour virtual_alias_maps)sender_login_maps— autorisations d'envoi