RFC 0004 — Fediverse: modèle de confiance (allowlist / blocklist + scope d'interop)
- Auteur : EBO (rustwarden)
- Date : 2026-05-09
- Statut : Accepted (auto-revue mainteneur unique 2026-05-09, score 27/35)
- Apps consommatrices concernées : toute app utilisant
rustwarden-coreavec la featureexperimental-fediversequi a besoin de contrôler quels domaines Fediverse peuvent interagir avec elle (auth cross-instance, invitations, signaux de lifecycle). - Plan associé :
docs/plans/2026-05-09-fediverse-identity-federation.md, Phase 1. - Dépendances : RFC 0001 (
FediverseConfig).
Summary
Introduire le modèle de confiance configurable par l'admin : table fediverse_trusted_domains (allowlist), table fediverse_blocked_domains (blocklist + sévérité), entrées dans app_settings (fediverse_enabled, fediverse_trust_mode, fediverse_interop_scope, fediverse_authorized_fetch, fediverse_node_name, fediverse_node_description), et service FediverseTrustService exposant le check central is_domain_allowed(domain) que les RFCs suivantes appliqueront avant toute opération avec un domaine distant.
Motivation
Sans modèle de confiance, n'importe quelle instance Fediverse peut authentifier ses utilisateurs ou être ajoutée à des équipes — surface d'attaque inacceptable. Le master plan §2.1 a tranché : configuration runtime par admin entre allowlist (zero-trust par défaut) ou open_with_blocklist (modèle Mastodon-classique).
Sans cette RFC, RFC 0006 (auth AP-native) n'a pas de check à appliquer et tout domaine peut tenter une connexion.
Non-goals
- Pas de check signature HTTP — RFC 0010.
- Pas de UI admin —
rustwarden-uiajoutera plus tard les écrans/admin/fediverse/.... - Pas de monitoring / métriques sur les rejets — différé.
- Pas de propagation transitive (A trust B, B trust C ⇒ A trust C) — explicite seulement.
API proposée
Migration m20260510_000010_fediverse_trust_and_blocks
- Table
fediverse_trusted_domains:id UUID PK,domain TEXT UNIQUE NOT NULL,note TEXT NULL,added_by_user_id UUID NULL(nullable car cycle de bootstrap),created_at TIMESTAMPTZ NOT NULL. - Table
fediverse_blocked_domains:id UUID PK,domain TEXT UNIQUE NOT NULL,severity TEXT NOT NULL DEFAULT 'suspend'(suspend/silence/reject_auth),reason TEXT NULL,added_by_user_id UUID NULL,created_at TIMESTAMPTZ NOT NULL. - Insertions
app_settings(clé/valeur, tous nullable au niveau lecture) :fediverse_enabled = falsefediverse_trust_mode = allowlistfediverse_interop_scope = rustwarden_onlyfediverse_authorized_fetch = falsefediverse_node_name = Rustwardenfediverse_node_description =(vide)
Entités SeaORM
models::fediverse_trusted_domain::Modelmodels::fediverse_blocked_domain::Model
Enums et service
#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TrustMode { Allowlist, OpenWithBlocklist } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum InteropScope { RustwardenOnly, RustwardenPlusUniversalRead, Open } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BlockSeverity { Suspend, Silence, RejectAuth } pub struct FediverseTrustService { db: DatabaseConnection } impl FediverseTrustService { pub fn new(db: DatabaseConnection) -> Self; /// Read-through to `app_settings.fediverse_enabled`. pub async fn is_enabled(&self) -> Result<bool, AppError>; pub async fn trust_mode(&self) -> Result<TrustMode, AppError>; pub async fn interop_scope(&self) -> Result<InteropScope, AppError>; /// Central check applied BEFORE any AP signature verification or /// fetch towards a remote domain. Returns `false` when: /// - the feature is disabled at runtime /// - mode = allowlist and the domain is not in trusted (or is in blocked) /// - mode = open_with_blocklist and the domain is in blocked pub async fn is_domain_allowed(&self, domain: &str) -> Result<bool, AppError>; pub async fn add_trusted_domain(&self, domain: &str, note: Option<&str>, added_by: Option<Uuid>) -> Result<Model, AppError>; pub async fn remove_trusted_domain(&self, domain: &str) -> Result<bool, AppError>; pub async fn list_trusted_domains(&self) -> Result<Vec<Model>, AppError>; pub async fn add_blocked_domain(&self, domain: &str, severity: BlockSeverity, reason: Option<&str>, added_by: Option<Uuid>) -> Result<BlockedModel, AppError>; pub async fn remove_blocked_domain(&self, domain: &str) -> Result<bool, AppError>; pub async fn list_blocked_domains(&self) -> Result<Vec<BlockedModel>, AppError>; }
is_domain_allowed normalise toujours en lowercase + strip www. éventuel. Domain comparé strictement.
Analyse de compatibilité
- Non-breaking, additif derrière feature.
-
Schéma DB : 2 nouvelles tables + insertions
app_settings. Migration up/down réversible. - Pas de modification d'API publique existante.
Alternatives considérées
Alternative A (recommandée) — Tables dédiées + service avec enums
Pros : RBAC clair, requêtes simples, audit trail (added_by_user_id).
Alternative B — Tout dans app_settings (CSV serialisé)
Stocker la liste des domaines comme CSV dans une clé app_settings.
Cons : pas indexable, pas auditable, race conditions sur l'écriture concurrente.
Rejeté.
Alternative C — Configuration en ENV vars
Passer la trust list via env.
Cons : statique, nécessite redéploiement, ne respecte pas la décision plan §2.1 (admin-configurable runtime).
Rejeté.
Testing plan
12 tests :
trust_mode_defaults_to_allowlist_when_setting_missing(pure)interop_scope_defaults_to_rustwarden_only_when_setting_missing(pure)severity_parses_known_values(pure)severity_falls_back_to_suspend_for_unknown(pure)normalize_domain_lowercases(pure)normalize_domain_strips_www_prefix(pure)is_domain_allowed_returns_false_when_disabled(DB-mocked)is_domain_allowed_returns_true_when_in_allowlist_and_enabled(DB-mocked)is_domain_allowed_returns_false_when_not_in_allowlist(DB-mocked)is_domain_allowed_returns_true_in_open_mode_when_not_blocked(DB-mocked)is_domain_allowed_returns_false_in_open_mode_when_blocked(DB-mocked)add_trusted_domain_inserts_record(DB-mocked)
Score d'évaluation : 27/35 (auto-revue) → Accepted.
Open questions
Toutes résolues à l'auto-revue : enums TrustMode/InteropScope/BlockSeverity exposés pub, normalisation domain par lowercase + strip www., fallback BlockSeverity::Suspend pour valeurs inconnues.
Score
| Critère | Note | Commentaire |
|---|---|---|
| Généralité | 5 | Modèle de confiance applicable bien au-delà de l'auth. |
| Stabilité API | 4 | Additif gated, mais 2 nouvelles tables publiques. |
| Performance | 5 | Lookups indexés (UNIQUE sur domain), cache éventuel facile à ajouter plus tard. |
| Sécurité | 5 | Hardening net : zero-trust par défaut, gate avant signature AP. |
| Complexité d'implémentation | 3 | ~500 lignes (2 entités + service + 12 tests). |
| Maintenance | 3 | Schéma stable, evolution probable mineure (ajouter severity levels). |
| Documentation | 2 | Rustdoc EN, doctest, CHANGELOG ; pas encore d'exemple examples/. |
| Total | 27/35 | ≥ 25 → Accepted |
GitRust