اسأل فريقك سؤالاً واحداً: كم دقيقة يحتاجها تبديل مفتاح CaptchaAI API لو تسرّب الآن؟ إذا كانت الإجابة «نعدّل ملف .env ثم نعيد نشر العمال»، فالخلل ليس في المفتاح بل في مكان تخزينه. المسار الأنظف: ضع المفتاح في HashiCorp Vault، واجعل كل عامل يقرأه وقت التشغيل بسياسة قراءة فقط، ثم حدّثه في مكان واحد فيلتقطه الجميع تلقائياً.
يبني هذا الدليل المسار كاملاً حول CaptchaAI: تخزين المفتاح، كتابة السياسة، قراءته من Python وNode.js، ثم تدويره بلا تعديل في الكود.
متطلبات قبل البدء
- خادم HashiCorp Vault — مستضاف ذاتياً أو عبر HCP Vault
- صلاحية استخدام Vault CLI أو الـ API الخاص به
- مفتاح CaptchaAI API فعّال من لوحة التحكم
- Python 3.8+ أو Node.js 18+ في بيئة العامل
ما الذي يتغيّر فعلياً حين ينتقل المفتاح إلى Vault
الفرق يظهر في ثلاثة أسئلة تشغيلية: من يقرأ المفتاح، وهل هناك سجل لكل قراءة، وكم يكلّف التبديل حين يصبح ضرورياً.
| قبل Vault | بعد Vault |
|---|---|
المفتاح مكتوب داخل الكود أو في ملف .env |
المفتاح مخزَّن مشفَّراً داخل Vault |
| يُتداول عبر Slack أو البريد | يُقرأ عبر واجهة مصادَق عليها فقط |
| لا تعرف من قرأ المفتاح ولا متى | كل قراءة مسجَّلة بهوية صاحبها |
| التدوير تعديل يدوي ثم إعادة نشر | التدوير تحديث واحد يلتقطه العمال |
| مفتاح واحد يسري على كل البيئات | مسار وسياسة مستقلان لكل بيئة |
القاعدة العملية: أي قيمة تصلح للنسخ في محادثة فريق لم تعد سرّاً — عاملها كمفتاح مكشوف وبدّلها.
الخطوة 1: خزّن مفتاح CaptchaAI API داخل Vault
فعّل محرّك الأسرار من نوع KV v2 إن لم يكن مفعّلاً، ثم اكتب المفتاح في المسار secret/captchaai. استبدل YOUR_API_KEY بالقيمة الحقيقية من لوحة التحكم.
# 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
إن كنت تشغّل أكثر من بيئة، افصل المسارات من اليوم الأول: secret/captchaai/dev وsecret/captchaai/staging وsecret/captchaai/prod. الفصل المتأخر يفرض تعديل كل عامل يعمل بالفعل.
الخطوة 2: امنح العمال حق القراءة فقط
العامل الذي يحلّ الكابتشا لا يحتاج إلا إلى قراءة قيمة واحدة. اكتب سياسة تحصر صلاحيته في مسار البيانات ومسار البيانات الوصفية، بلا حق كتابة أو حذف:
# captcha-worker-policy.hcl
path "secret/data/captchaai" {
capabilities = ["read"]
}
path "secret/metadata/captchaai" {
capabilities = ["read"]
}
ثم طبّق السياسة على الخادم:
vault policy write captcha-worker captcha-worker-policy.hcl
اربطها بعد ذلك بطريقة المصادقة التي ستعتمدها: رمز مباشر أثناء التطوير، أو AppRole في الإنتاج.
الخطوة 3: اقرأ المفتاح من داخل عامل Python
مكتبة hvac تختصر الاتصال. الفكرة أن يُقرأ المفتاح مرة عند إنشاء الكائن ثم يُعاد جلبه دورياً — كل ساعة هنا — كي يلتقط العامل أي تدوير بلا إعادة تشغيل. بقية المسار هو نمط CaptchaAI المعتاد: أرسل المهمة إلى in.php، احفظ المعرّف، ثم استفسر عن النتيجة من 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]}...")
المفتاح هنا لا يُطبع في سجل ولا يُمرَّر كوسيط في سطر الأوامر، وهما المكانان اللذان تتسرّب منهما المفاتيح عملياً.
الخطوة 4: النمط نفسه في Node.js
لا تحتاج إلى مكتبة مخصّصة لـ Vault؛ طلب HTTP واحد بترويسة X-Vault-Token يكفي. البنية مطابقة: جلب أولي، تحديث دوري، ثم إرسال واستطلاع.
// 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)}...`);
})();
اختر طريقة المصادقة بحسب البيئة
الرمز الثابت مقبول على جهاز المطوّر وغير مقبول في الإنتاج: مدّته تنتهي وتجديده يدوي. اختر ما يناسب مكان تشغيل العامل:
| الطريقة | الأنسب لـ | ما تحتاجه |
|---|---|---|
| Token | التطوير المحلي وخطوات CI/CD | متغيّر البيئة VAULT_TOKEN |
| AppRole | خدمات الإنتاج | معرّف الدور + المعرّف السري |
| Kubernetes | أحمال العمل داخل العناقيد | JWT الخاص بحساب الخدمة |
| AWS IAM | عمال EC2 أو Lambda | دور المثيل |
مثال AppRole للإنتاج
مع 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"]
سيناريو عملي: وكالة أتمتة في دبي بثلاث بيئات
تخيّل وكالة تدير اختبارات جودة لثلاثة عملاء. قبل Vault كان المفتاح نفسه على أجهزة أربعة مطوّرين وفي خادم الاختبار وداخل أداة البناء. وحين انتهى عقد أحد المتعاقدين لم يكن أمام الفريق سوى تبديل المفتاح وإعادة نشر كل شيء.
بعد نقل المفاتيح إلى Vault:
- مسار مستقل لكل بيئة وسياسة قراءة منفصلة لكل فريق.
- إنهاء وصول المتعاقد يتم بحذف ارتباطه بالسياسة، دون لمس المفتاح.
- سجل التدقيق يجيب عن «من قرأ مفتاح الإنتاج الأسبوع الماضي؟» في ثوانٍ، وهو ما تطلبه المراجعات الأمنية الداخلية.
أما الاشتراك فلا يتأثر: تظل خطط CaptchaAI قائمة على عدد الـ threads المتزامنة مع حلول غير محدودة لكل thread، فبيئة الاختبار تكفيها خطة BASIC بسعر $15 شهرياً مع 5 threads، بينما أسطول الإنتاج قد يحتاج ADVANCE بسعر $90 شهرياً مع 50 thread. مهمة Vault تنظيم من يقرأ المفتاح، لا زيادة ما يحلّه.
تدوير مفتاح CaptchaAI API دون إعادة نشر
- أنشئ مفتاح CaptchaAI جديداً من لوحة التحكم.
- اكتبه فوق القيمة القديمة عبر
vault kv put secret/captchaai api_key="NEW_KEY"— ومحرّك KV v2 يحتفظ بالنسخة السابقة. - انتظر دورة التحديث التالية؛ في المثال أعلاه ساعة واحدة كحدّ أقصى.
- ألغِ المفتاح القديم من لوحة التحكم بعد التأكد من انتقال كل العمال.
لا سطر كود يتغيّر ولا عملية نشر تُشغَّل. وإن أردت تدويراً أسرع، اخفض _key_refresh_interval إلى 300 ثانية بدل 3600؛ الكلفة نداء إضافي إلى Vault كل خمس دقائق لكل عامل.
أعطال شائعة أثناء التشغيل
| العَرَض | السبب الغالب | الإجراء |
|---|---|---|
رد 403 Forbidden من Vault |
السياسة لا تشمل المسار المطلوب | راجع مسارَي secret/data/captchaai وsecret/metadata/captchaai في السياسة |
انتهاء صلاحية VAULT_TOKEN أثناء تشغيل طويل |
مدّة الرمز أقصر من عمر العامل | انتقل إلى AppRole برموز قابلة للتجديد |
| العامل ما زال يستعمل المفتاح القديم بعد التدوير | فاصل إعادة الجلب طويل | قلّل الفاصل أو أضف إشارة تحديث فورية |
| تعذّر الوصول إلى Vault لحظة التحديث | انقطاع شبكة أو صيانة للخادم | أبقِ آخر مفتاح ناجح في الذاكرة وسجّل الفشل |
الأسئلة الشائعة
هل تضيف قراءة المفتاح من Vault تأخيراً على وقت حل الكابتشا؟
لا تُقرأ القيمة مع كل مهمة، بل مرة عند الإقلاع ثم عند كل دورة تحديث. نداء واحد إلى Vault كل ساعة لا يظهر أثره في وقت الحل، لأن هذا الوقت محكوم بنوع التحدي لا بمصدر المفتاح.
كيف أمنع ظهور المفتاح في سجلات التطبيق؟
مرّره كوسيط داخلي فقط، ولا تطبع كائن الطلب كاملاً أثناء التصحيح. وإذا اضطررت إلى تسجيل شيء، اكتفِ بآخر أربعة محارف، وتأكد من ألا تلتقط سجلات الخادم الوسيط عنواناً يحمل المفتاح.
متى أختار AppRole بدل الرمز الثابت؟
- الرمز المباشر: جهازك أو خطوة CI قصيرة تنتهي خلال دقائق.
- AppRole: أي عامل يعمل بلا انقطاع داخل حاوية طويلة الأجل، لأن المعرّف السري قصير الأجل وإلغاؤه يصيب عاملاً واحداً فقط.
هل يفرض تفعيل Vault ترقية خطة CaptchaAI؟
لا. الخطط قائمة على عدد الـ threads المتزامنة مع حلول غير محدودة لكل thread، وVault يعمل في طبقة الأسرار قبل أن ينطلق الطلب. اختر الخطة بحسب التزامن المطلوب، من BASIC بسعر $15 شهرياً وحتى VIP-3 بسعر $7,500 شهرياً.
ماذا يحدث إذا كان Vault غير متاح عند تشغيل عامل جديد؟
العامل القائم يواصل عمله بالمفتاح المحفوظ في ذاكرته، أما الجديد فلن يجد ما يقرأه. عالج الحالة بإعادة محاولة ذات تراجع أسّي قبل الإيقاف، وراقب معدل فشل قراءة الأسرار منفصلاً حتى تميّز عطل Vault عن خطأ واجهة الحل.