Documentation Index
Fetch the complete documentation index at: https://docs.snakysec.com/llms.txt
Use this file to discover all available pages before exploring further.
Setup — Notifications app (transactional emails via Microsoft Graph)
Objectif
Provisionner une app reg Entra dédiée à l’envoi d’emails transactionnels
(alertes, onboarding, digests) sans licence M365 supplémentaire, via
Microsoft Graph SendMail + shared mailbox noreply@snakysec.com.
Architecture
┌──────────────────────────────────────────────────────────────────────┐
│ Entra ID (snakysec.onmicrosoft.com) │
│ │
│ ┌──────────────────────────────┐ ┌──────────────────────────────┐│
│ │ App: SnakySec MSSP Platform │ │ App: SnakySec MSSP ││
│ │ Scopes: User.Read, openid... │ │ — Notifications ││
│ │ Cert: entra_cert_* │ │ Scopes: Mail.Send (App) ││
│ │ Used for: SSO login │ │ Cert: notifications_cert_* ││
│ │ │ │ Used for: Graph SendMail ││
│ └──────────────────────────────┘ └──────────────────────────────┘│
│ │ │
│ ▼ │
│ ┌────────────────────────────────┐ │
│ │ Application Access Policy │ │
│ │ Scope: noreply-scope group │ │
│ │ Effect: app can ONLY send AS │ │
│ │ noreply@snakysec.com │ │
│ └────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────┐ │
│ │ Shared Mailbox │ │
│ │ noreply@snakysec.com │ │
│ │ License: 0€ (no license needed) │ │
│ │ Sign-in: blocked │ │
│ │ 50 GB free Sent Items │ │
│ └────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
Sécurité — least privilege : si le cert de l’app SSO est compromis, l’envoi
d’email reste protégé (et inversement). Application Access Policy garantit que
l’app Notifications ne peut envoyer que depuis noreply@snakysec.com,
même si elle a Mail.Send Application qui théoriquement permet d’envoyer AS
n’importe qui.
Prérequis
- Compte Global Admin sur tenant SnakySec
- M365 admin center accessible
- Exchange Online PowerShell module (pour Application Access Policy)
- OpenSSL ou PowerShell
New-SelfSignedCertificate ou Kleopatra/GnuPG (pour cert X.509)
- Accès Vault (root token ou admin policy)
Procédure (~30 min one-shot)
Étape 1 — Créer le cert X.509
Option PowerShell (recommandé Windows) :
$cert = New-SelfSignedCertificate `
-Subject "CN=SnakySec MSSP Notifications, O=SnakySec, C=FR" `
-KeySpec Signature `
-KeyExportPolicy Exportable `
-KeyAlgorithm RSA `
-KeyLength 4096 `
-HashAlgorithm SHA256 `
-NotAfter (Get-Date).AddYears(1) `
-CertStoreLocation "Cert:\CurrentUser\My"
$pwd = ConvertTo-SecureString -String "TempBackupXYZ" -Force -AsPlainText
Export-Certificate -Cert $cert `
-FilePath "$env:USERPROFILE\Desktop\snakysec-notifications.cer"
Export-PfxCertificate -Cert $cert `
-FilePath "$env:USERPROFILE\Desktop\snakysec-notifications.pfx" `
-Password $pwd
Option OpenSSL (Linux/Mac/Git Bash) :
openssl req -x509 -newkey rsa:4096 -nodes \
-keyout snakysec-notifications.key \
-out snakysec-notifications.crt \
-subj "/CN=SnakySec MSSP Notifications/O=SnakySec/C=FR" \
-days 365
Importer ensuite dans Kleopatra/KeePass pour backup local.
Étape 2 — Créer l’app reg Entra
- https://entra.microsoft.com → Identity → Applications → App registrations
- New registration :
- Name :
SnakySec MSSP — Notifications
- Supported account types : Accounts in this organizational directory only (single tenant)
- Redirect URI : laisser vide
- Cliquer Register
- Noter sur la page Overview :
- Application (client) ID (GUID)
- Directory (tenant) ID (GUID, identique au tenant SSO)
Étape 3 — Uploader le cert dans l’app
- Dans l’app que tu viens de créer → Certificates & secrets → onglet Certificates
- Upload certificate → sélectionner
snakysec-notifications.cer
- Add
- Noter le Thumbprint (utile pour debug)
Étape 4 — Permission Mail.Send + admin consent
- Dans l’app → API permissions → + Add a permission
- Microsoft Graph → Application permissions (pas Delegated)
- Chercher
Mail.Send → cocher → Add permissions
- ⚠️ Cliquer Grant admin consent for snakysec.onmicrosoft.com (en haut)
- Vérifier statut : Granted for snakysec.onmicrosoft.com (vert)
Étape 5 — Créer la shared mailbox
- https://admin.microsoft.com → Teams & groups → Shared mailboxes
- + Add a shared mailbox :
- Display name :
SnakySec No-Reply
- Email :
noreply@snakysec.com
- Add
- Vérifier que sign-in est bloqué (par défaut)
Étape 6 — Application Access Policy (PowerShell)
Restreindre l’app à pouvoir envoyer uniquement depuis noreply@snakysec.com.
# Connexion Exchange Online PowerShell
Install-Module ExchangeOnlineManagement -Scope CurrentUser # une fois
Import-Module ExchangeOnlineManagement
Connect-ExchangeOnline -UserPrincipalName <ton-admin>@snakysec.com
# Créer un groupe de sécurité mail-enabled contenant noreply uniquement
# (workaround Microsoft : Application Access Policy nécessite un groupe)
New-DistributionGroup -Name "SnakySec-Noreply-Scope" `
-Type "Security" `
-PrimarySmtpAddress "noreply-scope@snakysec.com" `
-Members @("noreply@snakysec.com")
# Application Access Policy
New-ApplicationAccessPolicy `
-AppId "<APPLICATION_CLIENT_ID_DE_L_ETAPE_2>" `
-PolicyScopeGroupId "noreply-scope@snakysec.com" `
-AccessRight RestrictAccess `
-Description "SnakySec Notifications app — restricted to noreply mailbox only"
# Vérifier que la policy est appliquée
Test-ApplicationAccessPolicy `
-Identity "noreply@snakysec.com" `
-AppId "<APPLICATION_CLIENT_ID>"
# Attendu : AccessCheckResult = Granted
Test-ApplicationAccessPolicy `
-Identity "<ton-email>@snakysec.com" `
-AppId "<APPLICATION_CLIENT_ID>"
# Attendu : AccessCheckResult = Denied (preuve que le scope est bien restreint)
Étape 7 — DKIM custom domain
Renforce la deliverability vers les ISP non-M365 (Gmail, Free, Orange).
- https://security.microsoft.com → Email & collaboration → Policies & rules → Threat policies → DKIM
- Sélectionner
snakysec.com → Enable
- M365 fournit 2 records CNAME à ajouter
- Ajouter via OVH Manager → Domains →
snakysec.com → DNS Zone
- Re-cliquer Enable dans M365 (peut prendre 1h pour propagation)
Étape 8 — Peupler Vault
# Provisionner d'abord les placeholders (idempotent)
VAULT_ADDR=http://127.0.0.1:8200 \
VAULT_TOKEN=<root-token> \
./scripts/setup/vault-init-notifications.sh
# Puis remplacer par les vraies valeurs
vault kv patch mssp/platform \
notifications_client_id="<APP_GUID_FROM_ENTRA>" \
notifications_tenant_id="<TENANT_GUID_FROM_ENTRA>" \
notifications_cert_pem=@/path/to/snakysec-notifications.crt \
notifications_cert_private_key=@/path/to/snakysec-notifications.key \
notifications_from_address="noreply@snakysec.com"
Étape 9 — Restart + verify
# Restart next-app pour qu'il pick-up les nouvelles env vars
make app-restart
# Vérifier injection en runtime
docker exec mssp-app sh -c 'echo $NOTIFICATIONS_CLIENT_ID | head -c 8'
# Attendu : 8 premiers chars du GUID (pas REPLACE_ME)
Étape 10 — Test de bout-en-bout (après B.S1 mergé)
# Une fois le code email/graph-sender.ts disponible, tester l'envoi :
docker exec mssp-app node -e "
import('@/lib/email/graph-sender').then(({ sendViaGraph }) => {
sendViaGraph({
to: 'nicolas@snakysec.com',
subject: 'Test SnakySec Notifications',
html: '<p>Si tu lis ceci, l infrastructure email Graph fonctionne.</p>',
template: 'test_smoke',
}).then(() => console.log('OK')).catch(e => console.error('FAIL', e));
});
"
Vérifier également :
- Email reçu dans
nicolas@snakysec.com
- Entrée dans
EmailLog DB avec status=SENT + graphMessageId non-null
- Sent Items folder de la shared mailbox visible dans Exchange admin center
- Audit log applicatif
email.sent enregistré
Rotation annuelle (Q3, calendrier docs/dr/test-schedule.md)
1. Générer un nouveau cert (étape 1)
2. Uploader dans la même app Entra (étape 3)
3. Ne PAS supprimer l'ancien cert tant que le nouveau n'est pas en Vault
4. Patch Vault avec le nouveau pem + key (étape 8 sans toucher tenant_id ni client_id)
5. Restart next-app
6. Test smoke (étape 10)
7. Supprimer l'ancien cert dans Entra
8. Détruire l'ancien PFX local (broyeur DIN P-7 si papier)
9. Mettre à jour journal rotation dans `docs/dr/01-keyholders.md` §7
Plan d’évolution
| Phase | Évolution |
|---|
| V1 (maintenant) | Shared mailbox 0€, posture solo, ~5-15 clients × ~5 alerts/mois max |
| Phase 2 (5 clients récurrents) | Si Microsoft applique le licensing renforcé sur shared mailboxes (annonce août 2024 reculée), bascule vers Exchange Online Plan 1 ~4€/mo sur noreply@snakysec.com |
| Phase 3 (20+ clients) | Évaluer Email Marketing License si volume newsletter > 1k/mois (digest + analytics rapports) |
Erreurs courantes
| Erreur | Solution |
|---|
| Graph SendMail retourne 403 ErrorAccessDenied | Vérifier Test-ApplicationAccessPolicy — la policy n’est pas appliquée correctement. Re-créer le DistributionGroup. |
| Email envoyé mais arrive en spam Gmail/Free | DKIM pas correctement configuré — refaire étape 7 + attendre propagation 1h |
| Graph SendMail retourne 401 Unauthorized | Cert expiré OU thumbprint pas matché OU client_id/tenant_id incorrect en Vault |
New-ApplicationAccessPolicy échoue : “ApplicationAccessPolicy already exists” | Lister via Get-ApplicationAccessPolicy puis remplacer via Set-ApplicationAccessPolicy |
| Entrypoint logue “REPLACE_ME” | Vault pas patché avec vraies valeurs — refaire étape 8 |
Références
| Version | Date | Auteur |
|---|
| 1.0 | 2026-04-26 | Nicolas Schiffgens |