Section 05PKIFactorSecOpsArchitecte

Résolution mail

Adressez les bonnes personnes au bon moment, sans copier vos mailing-lists.

Dernière mise à jour 2026-04-18

PKIFactor associe chaque certificat à une équipe propriétaire (champ team_email). Lorsqu'un événement de cycle de vie survient (émission, expiration imminente, renouvellement, révocation), la plateforme résout dynamiquement l'adresse mail d'équipe en liste de destinataires via votre annuaire d'entreprise. Deux backends sont supportés : Microsoft Graph (Microsoft 365, Azure AD / Entra ID) et LDAP / Active Directory (on-premise).

Cela évite d'avoir à maintenir des mailing-lists statiques dans PKIFactor : la source de vérité reste votre annuaire.

Diagramme 5.A — Flux de résolution
  1. Cert event
    certificate.expires_soon
  2. PKIFactor
    lookup team_email
  3. Resolver
    Graph / LDAP query
  4. AD / AAD
    group members
  5. SMTP
    render + send mail

5.2. Configuration Microsoft Graph (Microsoft 365 / Entra ID)

Pré-requis Azure

  1. Inscrire une application dans Azure AD → App registrations
  2. Accorder les permissions Application suivantes (consentement admin) :
PermissionTypeJustification
Group.Read.AllApplicationLister les groupes
GroupMember.Read.AllApplicationLire les membres d'un groupe
User.Read.AllApplicationRésoudre les emails des membres
Mail.SendApplication(optionnel) envoi via Graph plutôt que SMTP
  1. Générer un client secret ou un certificat client (recommandé en production).

Configuration dans PKIFactor

yaml
# /etc/pkifactor/config.yaml
team_resolver:
  backend: graph
  graph:
    tenant_id:     "00000000-0000-0000-0000-000000000000"
    client_id:     "11111111-1111-1111-1111-111111111111"
    # Choisir l'une des méthodes :
    client_secret: "${ENTRA_CLIENT_SECRET}"        # secret manager
    # ou bien :
    # client_certificate_path: /etc/pkifactor/secrets/entra-app.pem
    # client_certificate_thumbprint: "AB:CD:EF:..."

    cache_ttl_seconds: 600  # cache des résolutions de groupe (10 min)
    timeout_seconds:   10

    # Stratégie de résolution
    resolution:
      mode: distribution_list   # ou: mail_enabled_security_group, m365_group, dynamic
      fallback_to_owner: true   # si la liste est vide, notifier le propriétaire du groupe

Test de configuration

bash
sudo pkifactor-ctl team-resolver test \
  --backend graph \
  --address "pki-team@acme.onmicrosoft.com"

# Sortie attendue :
#   ✓ Auth OK (token expires in 3599s)
#   ✓ Group found: 'PKI Team' (id=aaaa-bbbb-cccc)
#   ✓ Members resolved: 7 user(s)
#       - alice@acme.com
#       - bob@acme.com
#       - charlie@acme.com
#       ...
#   Total resolution time: 412 ms

Comportement

  • Si team_email correspond à une liste de distribution ou un groupe M365, PKIFactor récupère la liste transitive des membres (via transitiveMembers).
  • Si l'adresse pointe vers un groupe dynamique, la résolution honore les filtres dynamiques.
  • Si le groupe est mail-enabled security, l'adresse principale (mail) du groupe est utilisée directement comme destinataire (pas de fan-out).

5.3. Configuration LDAP / Active Directory (on-premise)

Configuration

yaml
# /etc/pkifactor/config.yaml
team_resolver:
  backend: ldap
  ldap:
    uri:        "ldaps://dc01.acme.local:636"
    bind_dn:    "CN=svc-pkifactor,OU=Service Accounts,DC=acme,DC=local"
    bind_password: "${LDAP_BIND_PASSWORD}"

    # Vérification TLS
    tls:
      verify: true
      ca_bundle: /etc/pkifactor/secrets/ad-root-ca.pem

    # Recherche du groupe par adresse mail
    group_search:
      base_dn: "OU=Groups,DC=acme,DC=local"
      filter:  "(&(objectClass=group)(mail={team_email}))"
      attributes:
        members_attr:    "member"           # AD : liste de DN
        members_format:  "dn"               # ou "rfc2307" pour OpenLDAP

    # Résolution des membres → email
    member_search:
      base_dn: "DC=acme,DC=local"
      filter:  "(&(objectClass=user)(distinguishedName={dn}))"
      mail_attr: "mail"

      # Filtre d'exclusion (utilisateurs désactivés)
      exclude_filter: "(userAccountControl:1.2.840.113556.1.4.803:=2)"

    # Récursion pour les groupes imbriqués
    nested_groups: true
    max_depth: 5

    cache_ttl_seconds: 600
    timeout_seconds:   10

Test de configuration

bash
sudo pkifactor-ctl team-resolver test \
  --backend ldap \
  --address "pki-team@acme.local"

# Sortie attendue :
#   ✓ Bind OK as CN=svc-pkifactor,OU=Service Accounts,DC=acme,DC=local
#   ✓ Group found: CN=PKI Team,OU=Groups,DC=acme,DC=local
#   ✓ Direct members: 5
#   ✓ Nested members (depth 2): 3
#   ✓ After exclude filter (active only): 7
#       - alice@acme.com
#       - bob@acme.com
#       ...

Test depuis ldapsearch (pour debug)

bash
ldapsearch -H ldaps://dc01.acme.local:636 \
  -D "CN=svc-pkifactor,OU=Service Accounts,DC=acme,DC=local" \
  -W \
  -b "OU=Groups,DC=acme,DC=local" \
  "(&(objectClass=group)(mail=pki-team@acme.local))" \
  member

5.4. Affectation d'une équipe à un certificat

Via API

bash
curl -X POST https://pki.exemple.com/api/v1/certificates \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "template_code": "web-tls",
    "subject": { "common_name": "www.acme.com" },
    "team_email": "pki-team@acme.com"
  }'

Via UI

Mockup UI — Issue certificate (form)

Issue a new certificate

Web Server TLS
Ownership
Resolved: 7 recipient(s)
alice@acme.com, bob@acme.com, charlie@acme.com, …

5.5. Événements déclenchant une notification

ÉvénementDestinatairesDélai
certificate.issuedÉquipe + demandeurImmédiat
certificate.approval_pendingApprobateurs de l'orgImmédiat
certificate.expires_soonÉquipeT-30j, T-7j, T-1j (configurable)
certificate.expiredÉquipeÀ l'expiration
certificate.revokedÉquipe + demandeurImmédiat
certificate.renewedÉquipeImmédiat

5.6. Modèle de données

sql
-- Champs additionnels sur la table certificates
ALTER TABLE certificates ADD COLUMN team_email VARCHAR(255);
ALTER TABLE certificates ADD COLUMN team_id    VARCHAR(128); -- AD objectGUID ou Graph group id
ALTER TABLE certificates ADD COLUMN team_resolver_backend VARCHAR(16); -- 'graph' | 'ldap'
CREATE INDEX idx_certs_team_email ON certificates(team_email);

-- Cache de résolution (TTL court)
CREATE TABLE team_resolution_cache (
  team_email      VARCHAR(255) PRIMARY KEY,
  backend         VARCHAR(16) NOT NULL,
  group_id        VARCHAR(128),
  member_emails   JSONB NOT NULL,
  resolved_at     TIMESTAMPTZ NOT NULL,
  expires_at      TIMESTAMPTZ NOT NULL
);

5.7. Gestion des erreurs

PKIFactor applique une stratégie de résilience à trois niveaux :

  1. Cache valide (TTL non expiré) → utilisé directement
  2. Cache expiré + backend disponible → rafraîchissement
  3. Backend indisponible (timeout, 5xx) → fallback sur le cache expiré (jusqu'à 24h) + alerte SIEM
ini
2026-04-18T10:34:12Z WARN  team_resolver=graph status=fallback_to_stale_cache
  team_email=pki-team@acme.com cache_age=3h backend_error="timeout after 10s"
  recipients=7 source=cache_24h_grace
Cache & source de vérité

Le cache de résolution a un TTL court (10 min par défaut) pour rester cohérent avec votre annuaire. En cas d'indisponibilité Graph/LDAP, PKIFactor bascule sur le cache 24h pour garantir que les notifications critiques (expiration, révocation) partent toujours.