Simpan API key CaptchaAI di HashiCorp Vault, lalu ambil saat runtime — bukan menaruhnya di .env yang ikut ter-commit atau di variabel environment yang terbaca di log crash. Dengan pola ini kunci hidup terenkripsi di satu tempat, setiap pembacaan tercatat lengkap dengan identitas pemanggilnya, dan Anda bisa mengganti kunci tanpa menyentuh satu baris kode pun.
Tim kecil biasanya mulai dengan satu API key bersama: masuk ke repo, dikirim lewat Slack, menempel di image Docker. Saat satu developer keluar, tidak ada yang tahu kunci mana yang bocor. Contoh di bawah memakai reCAPTCHA v2 lewat in.php dan res.php, tetapi polanya sama untuk Turnstile maupun GeeTest v3.
Apa yang berubah setelah API key pindah ke Vault
| Sebelum Vault | Sesudah Vault |
|---|---|
API key ada di file .env atau di dalam kode |
Kunci tersimpan terenkripsi di Vault |
| Kunci dibagikan lewat Slack atau email | Diambil lewat API yang terautentikasi |
| Tidak ada jejak audit siapa membaca apa | Setiap pembacaan tercatat dengan identitas |
| Rotasi kunci dikerjakan manual | Rotasi otomatis didukung |
| Satu kunci dipakai di semua environment | Satu kunci per environment dengan policy sendiri |
Vault tidak mengubah tagihan Anda
Perlu ditegaskan sejak awal: Vault tidak mengubah cara CaptchaAI menagih. Paket CaptchaAI berbasis thread — BASIC ($15/bulan) sampai VIP-3 ($7,500/bulan). Memecah kunci per environment tidak menambah biaya; yang Anda bagi hanya kapasitas thread di paket yang sama.
Yang perlu disiapkan
| Kebutuhan | Catatan |
|---|---|
| Server HashiCorp Vault | Self-hosted atau HCP Vault |
| Akses Vault CLI atau API | Untuk menulis secret dan policy |
| API key CaptchaAI | Kunci yang akan dipindahkan ke Vault |
| Python 3.8+ atau Node.js 18+ | Runtime worker pada contoh di bawah |
Langkah 1: simpan API key di KV engine
# 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
Langkah 2: buat policy read-only untuk worker
Worker penyelesai CAPTCHA hanya perlu membaca. Jangan beri kemampuan menulis pada policy yang dipakai proses produksi:
# captcha-worker-policy.hcl
path "secret/data/captchaai" {
capabilities = ["read"]
}
path "secret/metadata/captchaai" {
capabilities = ["read"]
}
Terapkan policy tersebut:
vault policy write captcha-worker captcha-worker-policy.hcl
Langkah 3: ambil kunci dari Vault di Python
Kode berikut mengambil kunci sekali saat objek dibuat, lalu menyegarkannya setiap jam. Pola cache-plus-refresh ini penting: memanggil Vault pada setiap solve menambah satu round trip jaringan ke jalur yang seharusnya cepat.
# 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]}...")
Langkah 4: pola yang sama di Node.js
Versi JavaScript memakai HTTP API Vault langsung lewat Axios, tanpa SDK tambahan. Perhatikan init() yang terpisah dari constructor — pengambilan kunci bersifat async, jadi tidak bisa dikerjakan di dalam constructor.
// 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)}...`);
})();
Memilih metode autentikasi Vault
| Metode | Cocok untuk | Yang disiapkan |
|---|---|---|
| Token | Development, CI/CD | Environment variable VAULT_TOKEN |
| AppRole | Layanan produksi | Role ID + Secret ID |
| Kubernetes | Beban kerja di K8s | JWT service account |
| AWS IAM | Worker di EC2 atau Lambda | IAM role pada instance |
AppRole: pilihan default untuk produksi
Token statis punya masalah klasik — ia harus disimpan di suatu tempat, dan tempat itu jadi rahasia baru yang perlu dijaga. AppRole memutus rantai tersebut: worker login dengan Role ID dan Secret ID, lalu menerima token berumur pendek yang bisa diperpanjang.
# 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"]
Konteks deployment di Indonesia
Tempatkan Vault di region yang sama dengan worker
Tim otomasi di Indonesia umumnya menjalankan worker di AWS ap-southeast-1 (Singapura), ap-southeast-3 (Jakarta), atau GCP asia-southeast2 (Jakarta). Taruh server Vault di region yang sama: panggilan lintas region menambah puluhan milidetik pada tiap refresh, dan bila jalur antar-region terganggu, worker ikut berhenti meski CaptchaAI sendiri sehat.
Satu developer, banyak klien
Skenario yang sering muncul di kerja kontrak (Upwork, Fastwork, dan sejenisnya): satu developer memegang beberapa klien sekaligus. Alih-alih menyimpan API key tiap klien di file terpisah pada laptop:
- buat satu path Vault per klien —
secret/captchaai/klien-a,secret/captchaai/klien-b - pasang policy read-only terpisah untuk tiap path
- cabut policy-nya saat kontrak berakhir; tidak ada file sisa yang perlu diburu di disk
Catatan kepatuhan
UU Pelindungan Data Pribadi (UU 27/2022) menuntut kontrol akses yang bisa dibuktikan atas kredensial sistem yang menyentuh data pengguna. Audit log Vault memberi bukti itu otomatis — bukan nasihat hukum, tetapi jauh lebih mudah dijelaskan saat audit dibanding sebaris kunci di .env.
Rotasi kunci tanpa deploy ulang
- Buat API key CaptchaAI baru di dashboard CaptchaAI
- Perbarui Vault:
vault kv put secret/captchaai api_key="NEW_KEY" - Worker mengambil kunci baru sendiri pada siklus refresh berikutnya
- Cabut kunci lama di dashboard setelah semua worker selesai menyegarkan
Tidak ada perubahan kode, tidak ada deployment. Jarak langkah 2 ke 4 minimal sepanjang _key_refresh_interval — dengan 3600 detik pada contoh di atas, tunggu satu jam sebelum mencabut kunci lama.
Ketika terjadi masalah
Sebagian besar kegagalan integrasi Vault berhenti pada gejala-gejala berikut.
| Gejala | Sebab | Perbaikan |
|---|---|---|
Vault membalas 403 Forbidden |
Policy tidak mengizinkan pembacaan | Cek path di captcha-worker-policy.hcl |
VAULT_TOKEN kedaluwarsa |
TTL token terlampaui | Pindah ke AppRole agar token diperpanjang otomatis |
| Kunci lama masih dipakai | Interval refresh terlalu panjang | Perkecil _key_refresh_interval |
| Vault tidak bisa dihubungi | Gangguan jaringan atau server | Pakai kunci yang sudah di-cache sebagai fallback |
CAPCHA_NOT_READY terus muncul |
Bukan kesalahan Vault | Task memang belum selesai — lanjutkan polling |
Catatan:
CAPCHA_NOT_READYdatang darires.php, bukan dari Vault, dan artinya normal. Teruskan polling sesuai jeda pada contoh kode.
Pertanyaan Umum
Apakah worker berhenti kalau Vault sedang down?
Tidak, selama kunci di-cache di memori seperti pada contoh kode. Kegagalan refresh cukup dicatat di log; worker tetap memakai kunci terakhir yang valid. Yang berisiko adalah memanggil Vault pada setiap solve tanpa cache — di situ Vault menjadi single point of failure.
Berapa sering sebaiknya kunci diambil ulang dari Vault?
Satu jam wajar untuk worker jangka panjang. Perpendek ke 5–15 menit hanya bila Anda memang merotasi kunci sesering itu; refresh terlalu agresif menambah beban Vault tanpa manfaat keamanan nyata.
Apakah pola ini berlaku untuk Turnstile dan GeeTest v3?
Ya. Yang berubah hanya parameter method saat mengirim task ke in.php; pengambilan kunci dari Vault persis sama. Perlu diingat hCaptcha, FunCaptcha, dan GeeTest v4 belum didukung CaptchaAI, sedangkan CaptchaFox (beta), Friendly Captcha (beta), dan Lemin (beta) masih berstatus beta.
Bisakah AWS Secrets Manager dipakai menggantikan Vault?
Bisa, polanya identik — ambil secret saat runtime lewat boto3, cache di memori, refresh berkala. Vault lebih unggul bila Anda berjalan di beberapa cloud; Secrets Manager lebih praktis bila seluruh worker sudah di AWS.
Perlukah kunci yang berbeda untuk tiap environment?
Ya. Pakai path terpisah: secret/captchaai/dev, secret/captchaai/staging, secret/captchaai/prod. Karena tagihan CaptchaAI berbasis thread, memisahkan kunci per environment tidak menaikkan biaya — Anda hanya membagi kapasitas thread yang sudah ada.
Artikel Terkait
- membatasi akses API key dengan whitelist IP
- prosedur rotasi API key CaptchaAI
- menjalankan CaptchaAI di Google Cloud Functions
Langkah Selanjutnya
Pindahkan API key CaptchaAI Anda dari file .env ke Vault hari ini — ambil API key Anda, simpan di KV engine, lalu jalankan worker pertama dengan policy read-only.
Panduan terkait: