Integraciones

Integración de Vault para la gestión de claves API CaptchaAI

Tus workers pueden leer la API key de CaptchaAI desde HashiCorp Vault en tiempo de ejecución, sin que la clave aparezca en el repositorio ni en un .env. Aquí tienes el circuito completo: dónde guardarla, qué política aplicarle, cómo autenticar al worker y cómo rotarla sin tocar código.

Por qué mover la API key de CaptchaAI a Vault

Imagina una agencia con equipo repartido entre Madrid y Bogotá que monitoriza precios para varios clientes. La misma clave acaba copiada en tres portátiles, en dos runners de CI y en un contenedor de producción; cuando alguien cambia de proyecto, nadie sabe dónde quedó.

Si el proyecto maneja datos personales bajo el RGPD o la LOPDGDD, una credencial compartida y sin traza de acceso es justo lo que una auditoría marca en rojo.

Sin Vault Con Vault
Clave API en el .env o en el código Clave cifrada dentro de Vault
Clave compartida por Slack o correo Acceso mediante una API autenticada
Sin registro de quién la leyó Cada lectura queda trazada con identidad
Rotación manual Soporte de rotación automatizada
La misma clave en todos los entornos Una clave por entorno, con políticas propias

Lo que necesitas antes de empezar

Requisito Para qué
Servidor HashiCorp Vault (autohospedado o HCP) Guardar el secreto cifrado
Acceso al CLI o a la API de Vault Escribir la clave y aplicar políticas
Tu API key de CaptchaAI El secreto que vas a proteger
Python 3.8+ o Node.js 18+ Ejecutar los ejemplos

Paso 1: guarda la clave en el motor KV

Activa el motor de secretos KV v2 y escribe la clave en secret/captchaai. Sustituye YOUR_API_KEY por tu clave real.

# 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
  • Lánzalo desde una máquina de confianza, nunca desde un runner compartido.
  • Con varios entornos, separa las rutas: secret/captchaai/dev, staging y prod.
  • Confirma con vault kv get que el valor quedó escrito.

Paso 2: limita el acceso con una política de solo lectura

Un worker que resuelve CAPTCHA solo necesita leer: sin escritura ni borrado, un proceso expuesto no puede hacer más daño que una lectura.

# captcha-worker-policy.hcl
path "secret/data/captchaai" {
  capabilities = ["read"]
}

path "secret/metadata/captchaai" {
  capabilities = ["read"]
}

Aplica la política:

vault policy write captcha-worker captcha-worker-policy.hcl

Paso 3: elige el método de autenticación antes de escribir código

Decide esto antes de tocar el cliente: cambia pocas líneas, pero condiciona cómo despliegas.

Método Cuándo usarlo Configuración
Token Desarrollo y CI/CD Variable de entorno VAULT_TOKEN
AppRole Servicios en producción Role ID + Secret ID
Kubernetes Cargas de trabajo en K8s JWT de la service account
AWS IAM Workers en EC2 o Lambda Rol de instancia

Paso 4: lee la clave desde Python

El cliente hvac recupera el secreto al instanciar el solver. El resto es el flujo habitual: envías la tarea a in.php y consultas el resultado en res.php.

# 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 clave vive solo en memoria: no se escribe en disco.
  • _key_refresh_interval marca cada cuánto se relee Vault: los procesos en marcha adoptan solos la clave rotada.
  • Con otro tipo de desafío solo cambian los parámetros de envío.

Paso 5: el mismo patrón en Node.js

Aquí no hace falta cliente oficial: la API HTTP de Vault se consulta con axios y la cabecera X-Vault-Token. La API key nunca llega a disco.

// 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)}...`);
})();

AppRole: la opción recomendada en producción

Con AppRole desaparece el token estático de larga vida: el worker se autentica con Role ID y Secret ID, y recibe un token de sesión con TTL corto.

# 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"]
  • Ningún token permanente en el entorno del contenedor.
  • Un Role ID por servicio, revocable sin tocar el resto.
  • Renovación automática mientras el proceso siga vivo.

Rotar la clave sin desplegar nada

  1. Genera una nueva API key de CaptchaAI desde el panel de control
  2. Actualiza Vault: vault kv put secret/captchaai api_key="NEW_KEY"
  3. Los workers recogen la clave nueva en su siguiente ciclo de refresco
  4. Revoca la clave anterior en el panel cuando todos los procesos se hayan refrescado

El paso 3 no requiere despliegue: ocurre solo porque el solver vuelve a consultar Vault cada hora.

Coste y encaje con tu plan de CaptchaAI

Vault no cambia cómo factura CaptchaAI: los planes se cobran por thread concurrente, con resoluciones ilimitadas por thread dentro del mes.

  • BASIC ($15/mes, 5 threads) cubre un entorno de pruebas más un worker pequeño.
  • ADVANCE ($90/mes, 50 threads) es el escalón habitual cuando varios procesos comparten cuenta.
  • Separar rutas y claves por entorno no encarece el plan: se mide en threads, no en credenciales.
  • Un coste fijo en USD se presupuesta mejor que el pago por resolución si facturas en pesos o euros.

Errores frecuentes y cómo resolverlos

Problema Causa Solución
403 Forbidden desde Vault La política no permite lectura Revisa la ruta en captcha-worker-policy.hcl
VAULT_TOKEN caducado Se superó el TTL del token Cambia a AppRole para tokens renovables
La clave no se refresca Intervalo de refresco demasiado largo Reduce _key_refresh_interval
Vault no responde Problema de red o del servidor Cachea la clave en memoria como respaldo

Preguntas frecuentes

¿Vault o variables de entorno para la API key?

Depende del equipo. Para un script personal basta una variable de entorno bien protegida, como explica la guía sobre credenciales en variables de entorno. Con varios entornos o requisitos de auditoría, Vault gana: centraliza la rotación y deja traza de cada lectura.

¿Cada cuánto conviene rotar la API key?

Con este patrón de refresco, rotar cuesta un comando:

  • Cada trimestre, como línea base.
  • En cuanto alguien sale del equipo.
  • De inmediato si sospechas de una filtración.

¿Cómo evito que la clave termine en los logs?

No registres la respuesta de Vault ni los parámetros enviados a in.php. Guarda solo el task_id y el estado; para depurar, enmascara la clave dejando los últimos cuatro caracteres.

¿Puedo autenticarme desde Kubernetes sin token estático?

Sí. El método de Kubernetes intercambia el JWT de la service account del pod por un token de Vault de vida corta: el equivalente a AppRole cuando los workers son pods.

¿Cómo pruebo la integración sin arriesgar la clave de producción?

Genera una segunda API key en el panel y escríbela en una ruta de pruebas. Si se filtra durante los ensayos, la revocas y producción sigue igual.

Artículos relacionados

Próximos pasos

Protege tus credenciales con Vault desde el primer día: obtén tu API key. Para seguir:

Los comentarios están deshabilitados para este artículo.