Une clé API n'a pas besoin d'exister ailleurs que dans la mémoire du processus qui l'utilise : HashiCorp Vault la chiffre au repos, la délivre au runtime contre une identité authentifiée et journalise chaque lecture. Votre clé API CaptchaAI sort du dépôt Git et des variables d'environnement partagées ; elle devient un secret remplaçable sans toucher au code.
Le chemin tient en quatre étapes : écrire le secret, écrire la stratégie de lecture, lire la clé depuis Python et Node.js, puis la faire tourner sans redéploiement.
Avant de commencer
- Un serveur HashiCorp Vault (auto-hébergé ou HCP Vault)
- L'accès à la CLI ou à l'API de Vault
- Une clé API CaptchaAI
- Python 3.8+ ou Node.js 18+
Ce que Vault change pour vos workers CAPTCHA
| Sans Vault | Avec Vault |
|---|---|
Clé API dans le fichier .env ou dans le code |
Clé stockée chiffrée dans Vault |
| Clé partagée via Slack ou par e-mail | Accès via API authentifiée |
| Aucune piste d'audit des accès | Chaque lecture journalisée avec l'identité |
| Rotation manuelle des clés | Rotation automatisable |
| Même clé dans tous les environnements | Une clé par environnement, avec stratégies |
La dernière ligne est la plus sous-estimée : tant qu'une seule clé circule entre dev, staging et production, la moindre fuite oblige à tout arrêter. Avec des chemins distincts, vous révoquez un périmètre à la fois.
Étape 1 : écrire la clé API dans Vault
Activez le moteur KV v2 s'il ne l'est pas déjà, puis stockez la clé sous un chemin dédié :
# Enable the KV secrets engine (if not already enabled)
vault secrets enable -path=secret kv-v2
# Store the CaptchaAI API key
vault kv put secret/captchaai api_key="YOUR_API_KEY"
# Verify
vault kv get secret/captchaai
KV v2 conserve un historique des versions : après une rotation, l'ancienne valeur reste consultable le temps de vérifier que tous les workers ont basculé.
Étape 2 : restreindre les workers à la lecture seule
Un worker n'a besoin que de lire. Écrivez une stratégie qui n'accorde rien d'autre :
# captcha-worker-policy.hcl
path "secret/data/captchaai" {
capabilities = ["read"]
}
path "secret/metadata/captchaai" {
capabilities = ["read"]
}
Appliquez-la :
vault policy write captcha-worker captcha-worker-policy.hcl
Un token porteur de cette seule stratégie ne peut ni écrire, ni supprimer, ni lister d'autres secrets. L'audit devient exploitable : une lecture inattendue désigne un worker précis.
Étape 3 : lire la clé au runtime en Python
Le client hvac lit le secret au démarrage, puis le relit périodiquement pour capter une rotation.
# vault_solver.py
import os
import time
import hvac
import requests
# Connect to Vault
vault_client = hvac.Client(
url=os.environ.get("VAULT_ADDR", "http://127.0.0.1:8200"),
token=os.environ.get("VAULT_TOKEN"),
)
def get_api_key():
"""Retrieve CaptchaAI API key from Vault."""
secret = vault_client.secrets.kv.v2.read_secret_version(
path="captchaai",
mount_point="secret",
)
return secret["data"]["data"]["api_key"]
class CaptchaSolver:
"""CAPTCHA solver with Vault-managed credentials."""
def __init__(self):
self.api_key = get_api_key()
self.session = requests.Session()
self._key_fetched_at = time.time()
self._key_refresh_interval = 3600 # Re-fetch key hourly
def _refresh_key_if_needed(self):
"""Periodically refresh the key from Vault."""
if time.time() - self._key_fetched_at > self._key_refresh_interval:
self.api_key = get_api_key()
self._key_fetched_at = time.time()
def solve(self, sitekey, pageurl):
"""Solve reCAPTCHA v2 using Vault-managed key."""
self._refresh_key_if_needed()
# Submit
resp = self.session.get("https://ocr.captchaai.com/in.php", params={
"key": self.api_key,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": pageurl,
"json": "1",
})
result = resp.json()
if result.get("status") != 1:
raise Exception(f"Submit failed: {result.get('request')}")
task_id = result["request"]
time.sleep(15)
for _ in range(25):
poll = self.session.get("https://ocr.captchaai.com/res.php", params={
"key": self.api_key,
"action": "get",
"id": task_id,
"json": "1",
})
poll_result = poll.json()
if poll_result.get("status") == 1:
return poll_result["request"]
if poll_result.get("request") != "CAPCHA_NOT_READY":
raise Exception(f"Error: {poll_result.get('request')}")
time.sleep(5)
raise Exception("Timeout")
# Usage
solver = CaptchaSolver()
token = solver.solve(
"6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
"https://www.google.com/recaptcha/api2/demo"
)
print(f"Token: {token[:30]}...")
La clé n'est jamais écrite sur disque. L'intervalle d'une heure fixe le délai maximal entre une rotation et sa prise en compte par un worker déjà lancé.
Étape 4 : la même logique en Node.js
Sans bibliothèque dédiée, un appel HTTP sur l'endpoint v1/secret/data/... avec l'en-tête X-Vault-Token suffit. Le token Vault reste injecté par l'environnement d'exécution, jamais par le dépôt.
// vault_solver.js
const axios = require('axios');
const VAULT_ADDR = process.env.VAULT_ADDR || 'http://127.0.0.1:8200';
const VAULT_TOKEN = process.env.VAULT_TOKEN;
async function getApiKey() {
const resp = await axios.get(
`${VAULT_ADDR}/v1/secret/data/captchaai`,
{ headers: { 'X-Vault-Token': VAULT_TOKEN } }
);
return resp.data.data.data.api_key;
}
class CaptchaSolver {
constructor() {
this.apiKey = null;
this.keyFetchedAt = 0;
this.refreshInterval = 3600000; // 1 hour
}
async init() {
this.apiKey = await getApiKey();
this.keyFetchedAt = Date.now();
}
async refreshKeyIfNeeded() {
if (Date.now() - this.keyFetchedAt > this.refreshInterval) {
this.apiKey = await getApiKey();
this.keyFetchedAt = Date.now();
}
}
async solve(sitekey, pageurl) {
await this.refreshKeyIfNeeded();
const submit = await axios.get('https://ocr.captchaai.com/in.php', {
params: {
key: this.apiKey, method: 'userrecaptcha',
googlekey: sitekey, pageurl, json: '1',
},
});
if (submit.data.status !== 1) throw new Error(submit.data.request);
const taskId = submit.data.request;
await new Promise(r => setTimeout(r, 15000));
for (let i = 0; i < 25; i++) {
const poll = await axios.get('https://ocr.captchaai.com/res.php', {
params: { key: this.apiKey, action: 'get', id: taskId, json: '1' },
});
if (poll.data.status === 1) return poll.data.request;
if (poll.data.request !== 'CAPCHA_NOT_READY') throw new Error(poll.data.request);
await new Promise(r => setTimeout(r, 5000));
}
throw new Error('Timeout');
}
}
(async () => {
const solver = new CaptchaSolver();
await solver.init();
const token = await solver.solve(
'6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-',
'https://www.google.com/recaptcha/api2/demo'
);
console.log(`Token: ${token.slice(0, 30)}...`);
})();
Choisir la méthode d'authentification Vault
Le VAULT_TOKEN des deux exemples convient au développement. En production, la méthode dépend de l'endroit où tourne le worker, pas du langage.
| Méthode | Adaptée à | Configuration |
|---|---|---|
| Token | Développement, CI/CD | Variable d'environnement VAULT_TOKEN |
| AppRole | Services en production | Role ID + Secret ID |
| Kubernetes | Charges de travail K8s | JWT du service account |
| AWS IAM | Workers EC2 ou Lambda | Rôle d'instance |
AppRole : le choix par défaut en production
Remplacez l'initialisation du client par une ouverture de session AppRole :
# AppRole authentication — no static token needed
vault_client = hvac.Client(url=os.environ["VAULT_ADDR"])
vault_client.auth.approle.login(
role_id=os.environ["VAULT_ROLE_ID"],
secret_id=os.environ["VAULT_SECRET_ID"],
)
# Now read the secret
secret = vault_client.secrets.kv.v2.read_secret_version(path="captchaai")
api_key = secret["data"]["data"]["api_key"]
AppRole supprime le token statique de longue durée : le worker échange un Role ID et un Secret ID contre un token court, renouvelable. Si un conteneur est compromis, la fenêtre d'exposition se compte en minutes.
Dépannage
| Problème | Cause | Correctif |
|---|---|---|
403 Forbidden renvoyé par Vault |
La stratégie n'autorise pas la lecture du chemin | Vérifiez les chemins dans captcha-worker-policy.hcl |
VAULT_TOKEN expiré |
Durée de vie du token dépassée | Passez à AppRole et à des tokens renouvelables |
| La clé ne change pas après rotation | Intervalle de rafraîchissement trop long | Réduisez _key_refresh_interval |
| Vault injoignable au démarrage | Incident réseau, ou serveur encore scellé | Gardez la clé en cache mémoire et prévoyez un fallback |
| Erreur de clé invalide côté CaptchaAI | Ancienne clé révoquée avant la bascule des workers | Révoquez seulement après le dernier cycle de rafraîchissement |
Scénario : trois environnements dans une agence
Une agence d'automatisation lyonnaise exécute ses workers sur des instances OVHcloud, avec dev, staging et production séparés. Le montage tient en trois chemins et trois stratégies : secret/captchaai/dev, secret/captchaai/staging, secret/captchaai/prod, chacun lisible par un AppRole distinct.
Deux effets pratiques. Une erreur de configuration en développement ne met plus en jeu la clé de production. Et quand un client demande qui a eu accès à quoi, les logs d'audit Vault répondent, horodatés par identité — utile dans une revue RGPD, où la traçabilité des accès aux secrets compte parmi les mesures techniques à décrire.
Côté capacité, rien ne change : la facturation CaptchaAI repose sur les threads du plan. Passer de BASIC ($15/mois, 5 threads) à ADVANCE ($90/mois, 50 threads) ne modifie rien dans Vault.
Faire tourner la clé sans redéployer
C'est le bénéfice qui justifie tout le montage. Quatre gestes suffisent, workers en marche.
- Générez une nouvelle clé API dans le tableau de bord CaptchaAI
- Écrivez-la dans Vault :
vault kv put secret/captchaai api_key="NEW_KEY" - Les workers récupèrent la nouvelle valeur au prochain cycle de rafraîchissement
- Révoquez l'ancienne clé dans le tableau de bord une fois tous les workers passés à la nouvelle
Aucune modification de code, aucun redéploiement : la rotation devient une opération sur un secret, pas une mise en production.
FAQ
Vault ajoute-t-il de la latence à chaque résolution de CAPTCHA ?
Non. La clé est lue au démarrage, puis relue une fois par heure dans les exemples ci-dessus. Le chemin critique d'une résolution ne contient aucun appel à Vault.
Faut-il un secret KV ou un secret dynamique pour une clé API CaptchaAI ?
Un secret KV. Les secrets dynamiques génèrent des identifiants à la demande auprès d'un système compatible (base de données, cloud) ; une clé API CaptchaAI est une valeur externe que vous écrivez vous-même. KV v2 et son versionnage suffisent.
Comment savoir qui a lu la clé lors d'un audit ?
Activez un audit device Vault (file ou syslog) : chaque lecture y apparaît avec l'identité, le chemin et l'horodatage. Croisez ces entrées avec les Role ID de vos AppRoles pour remonter au worker.
Que faire si Vault est scellé au moment où un worker redémarre ?
Il doit échouer explicitement plutôt que démarrer sans clé : journalisez l'erreur, sortez avec un code non nul, laissez l'orchestrateur relancer. Un processus déjà vivant garde sa clé en mémoire, jamais sur disque.
Articles connexes
- Restreindre l'usage de la clé par liste blanche IP
- Faire tourner votre clé API CaptchaAI
- Intégrer CaptchaAI dans Google Cloud Functions
Prochaines étapes
Récupérez votre clé API, écrivez-la dans Vault et faites-la lire par un AppRole dédié.
Guides associés :