Patterns
Stratégie de rotation des clés API
Deux clés actives, rotation 90 jours, dual-validation
Une clé API qui ne tourne pas est une clé compromise en sursis. Le pattern Scell.io : chaque tenant peut maintenir 2 clés `sk_live_*` simultanément actives (clé courante + clé de rotation). Workflow de rotation tous les 90 jours : (1) générer une nouvelle clé via `POST /api/v1/api-keys`, (2) déployer la nouvelle clé dans tous les environnements (CI, prod, secrets manager), (3) attendre 7 jours que la nouvelle clé soit utilisée (vérifier via `GET /api/v1/api-keys/{id}/usage`), (4) révoquer l'ancienne via `DELETE /api/v1/api-keys/{old_id}`. La fenêtre de chevauchement évite l'outage si un service oublie le rolling. Pour les secrets compromis (incident), bypass la fenêtre : révoquer immédiatement et déclencher un re-deploy. Ne JAMAIS partager une clé entre tenants ou environnements ; ne JAMAIS hardcoder dans le code source ; toujours via env var ou secret manager (cf. pattern secret-management).
À retenir
- 2 clés actives simultanément (courante + rotation)
- Rotation programmée tous les 90 jours
- Fenêtre de chevauchement de 7 jours minimum
- Vérifier l'usage via GET /api/v1/api-keys/{id}/usage avant révocation
- Incident : révocation immédiate, pas de fenêtre
Exemple de code
// Cron job mensuel — génération clé de rotation
import { ScellClient } from '@scell/sdk';
import { vault } from './vault';
async function rotateScellKey() {
const scell = new ScellClient(await vault.read('scell/current'));
// 1. Generate new key
const newKey = await scell.apiKeys.create({ label: `rotation-${Date.now()}` });
// 2. Push to vault as 'next' (services should fall back to 'next' first)
await vault.write('scell/next', newKey.secret);
// 3. Trigger rolling re-deploy across services
await ci.triggerWorkflow('rolling-deploy', { reason: 'scell-key-rotation' });
// 4. After 7 days (separate cron), promote 'next' to 'current' and revoke old
// scheduleAfter('7 days', async () => {
// await vault.copy('scell/next', 'scell/current');
// await scell.apiKeys.delete(oldKeyId);
// });
}