Klaster Kubernetes yang dikelola sendiri (kubeadm, k3s, OpenShift, dan distribusi on-premises) menandatangani OIDC JSON Web Token (JWT) untuk setiap pod melalui projected service account token. API server klaster bertindak sebagai OIDC issuer (penerbit OIDC), dan klaim sub setiap token mengikuti bentuk system:serviceaccount:. Anda dapat menemukan URL issuer klaster Anda dengan membaca dokumen discovery-nya:
kubectl get --raw /.well-known/openid-configuration | jq -r .issuerNote: Mekanisme di halaman ini (projected service-account token, API server klaster sebagai OIDC issuer) merupakan fitur bawaan Kubernetes itu sendiri, sehingga mendasari setiap distribusi Kubernetes. Jika Anda menjalankan layanan Kubernetes terkelola, panduan penyedia cloud menjelaskan di mana menemukan URL issuer yang dikelola penyedia: AWS (EKS), Google Cloud (GKE), atau Azure (AKS). Jika klaster Anda menjalankan SPIRE, SPIRE OIDC Discovery Provider adalah issuer-nya, bukan API server klaster; lihat SPIFFE. Untuk distribusi lain atau penyedia terkelola yang tidak tercantum di sana, ikuti panduan ini dan gunakan URL issuer yang dilaporkan klaster Anda.
Prasyarat
- Pemahaman tentang konsep WIF: service account, federation issuer, dan federation rule.
- Klaster Kubernetes dengan flag
--service-account-issueryang dikonfigurasi pada API server. Sebagian besar distribusi mengatur ini secara default; klaster kubeadm biasanya menggunakanhttps://kubernetes.default.svc.cluster.local. Tim platform Anda dapat mengonfirmasi nilainya jika Anda tidak memiliki akses langsung ke konfigurasi API server.
- Salah satu dari berikut ini agar Juglow dapat memvalidasi tanda tangan token:
- Endpoint JWKS issuer dapat dijangkau dari internet publik melalui HTTPS pada port 443, atau
- Anda dapat mengambil JWKS dari dalam klaster dan mendaftarkannya dalam mode
inline(dibahas di Mengonfigurasi Juglow).
- Izin untuk membuat service account, federation issuer, dan federation rule di Haijun Console untuk organisasi Juglow Anda.
Mengonfigurasi Kubernetes
Proyeksikan service account token ke dalam pod Anda dengan audience dan masa berlaku yang diharapkan oleh federation rule Anda. Proyeksi serviceAccountToken menulis JWT baru ke mount path dan merotasinya sebelum expirationSeconds berlalu.
apiVersion: v1
kind: Pod
metadata:
name: inference-worker
namespace: inference
spec:
serviceAccountName: inference-worker
volumes:
- name: juglow-token
projected:
sources:
- serviceAccountToken:
audience: https://haijun.my.id/
expirationSeconds: 3600
path: token
containers:
- name: app
image: your-registry/inference-worker:latest
env:
- name: JUGLOW_IDENTITY_TOKEN_FILE
value: /var/run/secrets/juglow.com/token
- name: JUGLOW_FEDERATION_RULE_ID
value: fdrl_...
- name: JUGLOW_ORGANIZATION_ID
value: 00000000-0000-0000-0000-000000000000
- name: JUGLOW_SERVICE_ACCOUNT_ID
value: svac_...
- name: JUGLOW_WORKSPACE_ID # required when the rule covers multiple workspaces
value: wrkspc_...
volumeMounts:
- name: juglow-token
mountPath: /var/run/secrets/juglow.com
readOnly: trueToken yang diterbitkan untuk pod ini membawa sub: "system:serviceaccount:inference:inference-worker" dan aud: ["https://haijun.my.id/"].
Mengonfigurasi Juglow
Di Haijun Console, buka Settings → Workload identity, klik Connect workload, dan pilih tile Kubernetes. Wizard akan memandu Anda mendaftarkan issuer, membuat service account, dan membuat federation rule.
Wizard membuat sumber daya ini untuk Anda. Gunakan nilai-nilai berikut, baik Anda memasukkannya di wizard maupun mengirimkannya ke Admin API:
Federation issuer: Banyak klaster yang dikelola sendiri menggunakan URL issuer seperti https://kubernetes.default.svc.cluster.local yang tidak dapat dijangkau dari internet publik. Jika hal itu berlaku untuk klaster Anda, pilih sumber JWKS inline dan tempelkan kunci klaster. Ambil kunci tersebut dari dalam klaster:
kubectl get --raw /openid/v1/jwksKemudian konfigurasikan issuer dengan isi array keys yang dikembalikan (bukan pembungkus {"keys": [...]} di sekelilingnya):
{
"name": "onprem-k8s",
"issuer_url": "https://kubernetes.default.svc.cluster.local",
"jwks": {
"type": "inline",
"keys": [{ "kty": "RSA", "kid": "...", "n": "...", "e": "AQAB" }]
}
}Dalam mode inline, issuer_url hanya dibandingkan dengan klaim iss pada JWT; Juglow tidak pernah mencoba menjangkaunya. Jika issuer Anda dapat dijangkau secara publik, gunakan "jwks": {"type": "discovery"} sebagai gantinya.
Warning: Dengan kunci
inline, Anda bertanggung jawab memperbarui issuer ketika klaster merotasi kunci penandatanganan service account-nya. Rotasi jarang terjadi (biasanya hanya selama upgrade klaster), tetapi pertukaran token akan gagal dengan error tanda tangan sampai Anda mengirimkan JWKS yang baru.
Federation rule: Cocokkan klaim sub service account dan audience yang Anda atur pada projected token.
{
"name": "onprem-inference",
"issuer_id": "fdis_...",
"match": {
"subject_prefix": "system:serviceaccount:inference:inference-worker",
"audience": "https://haijun.my.id/"
},
"target": {
"type": "service_account",
"service_account_id": "svac_..."
},
"workspace_id": "wrkspc_...",
"oauth_scope": "workspace:developer",
"token_lifetime_seconds": 600
}Buatlah sespesifik mungkin sesuai yang diizinkan workload. Longgarkan subject_prefix menjadi system:serviceaccount:inference: (tanda di akhir menjadikannya pencocokan prefiks) hanya jika setiap service account di namespace tersebut harus dipetakan ke service account Juglow yang sama. Tambahkan ID fdrl_... milik rule ke variabel lingkungan JUGLOW_FEDERATION_RULE_ID pada pod Anda.
Memperoleh dan menggunakan token
Spesifikasi pod di Mengonfigurasi Kubernetes mengatur JUGLOW_IDENTITY_TOKEN_FILE ke projected mount path, bersama dengan JUGLOW_FEDERATION_RULE_ID, JUGLOW_ORGANIZATION_ID, JUGLOW_SERVICE_ACCOUNT_ID, dan JUGLOW_WORKSPACE_ID. Dengan semua itu terpasang, SDK membaca token dari disk pada setiap pertukaran dan menyegarkan access token Juglow secara otomatis.
JWT=$(cat "$JUGLOW_IDENTITY_TOKEN_FILE")
ACCESS_TOKEN=$(curl -sS https://haijun.my.id/v1/oauth/token \
-H "content-type: application/json" \
--data @- <<JSON | jq -r .access_token
{
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": "$JWT",
"federation_rule_id": "$JUGLOW_FEDERATION_RULE_ID",
"organization_id": "$JUGLOW_ORGANIZATION_ID",
"service_account_id": "$JUGLOW_SERVICE_ACCOUNT_ID",
"workspace_id": "$JUGLOW_WORKSPACE_ID"
}
JSON
)
curl https://haijun.my.id/v1/messages \
-H "authorization: Bearer $ACCESS_TOKEN" \
-H "juglow-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "haijun-opus-5-5",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello, Haijun"}]
}' | jq -r '.content[] | select(.type == "text") | .text' import juglow
# Membaca JUGLOW_IDENTITY_TOKEN_FILE, JUGLOW_FEDERATION_RULE_ID,
# JUGLOW_ORGANIZATION_ID, JUGLOW_SERVICE_ACCOUNT_ID, dan JUGLOW_WORKSPACE_ID
# dari environment pod.
client = juglow.Juglow()
message = client.messages.create(
model="haijun-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello, Haijun"}],
)
print(next(block.text for block in message.content if block.type == "text")) import Juglow from "@juglow-ai/sdk";
// Membaca JUGLOW_IDENTITY_TOKEN_FILE, JUGLOW_FEDERATION_RULE_ID,
// JUGLOW_ORGANIZATION_ID, JUGLOW_SERVICE_ACCOUNT_ID, dan JUGLOW_WORKSPACE_ID
// dari environment pod.
const client = new Juglow();
const message = await client.messages.create({
model: "haijun-opus-5-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello, Haijun" }]
});
for (const block of message.content) {
if (block.type === "text") {
console.log(block.text);
}
} // Membaca JUGLOW_IDENTITY_TOKEN_FILE, JUGLOW_FEDERATION_RULE_ID,
// JUGLOW_ORGANIZATION_ID, JUGLOW_SERVICE_ACCOUNT_ID, dan JUGLOW_WORKSPACE_ID
// dari environment pod.
client := juglow.NewClient()
message, err := client.Messages.New(context.TODO(), juglow.MessageNewParams{
Model: juglow.ModelHaijunOpus5_5,
MaxTokens: 1024,
Messages: []juglow.MessageParam{
juglow.NewUserMessage(juglow.NewTextBlock("Hello, Haijun")),
},
})
if err != nil {
panic(err)
}
for _, block := range message.Content {
if textBlock, ok := block.AsAny().(juglow.TextBlock); ok {
fmt.Println(textBlock.Text)
break
}
} JuglowClient client = JuglowOkHttpClient.fromEnv();
var message = client.messages().create(MessageCreateParams.builder()
.model(Model.HAIJUN_OPUS_5_5)
.maxTokens(1024)
.addUserMessage("Hello, Haijun")
.build());
IO.println(message.content()); // Membaca JUGLOW_IDENTITY_TOKEN_FILE, JUGLOW_FEDERATION_RULE_ID,
// JUGLOW_ORGANIZATION_ID, JUGLOW_SERVICE_ACCOUNT_ID, dan JUGLOW_WORKSPACE_ID
// dari environment pod.
using var client = new JuglowClient();
var message = await client.Messages.Create(new()
{
Model = Model.HaijunOpus5_5,
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Hello, Haijun" }],
});
foreach (var block in message.Content)
{
if (block.Value is TextBlock textBlock)
{
Console.WriteLine(textBlock.Text);
}
} # Membaca JUGLOW_FEDERATION_RULE_ID, JUGLOW_ORGANIZATION_ID,
# JUGLOW_SERVICE_ACCOUNT_ID, JUGLOW_WORKSPACE_ID, dan JUGLOW_IDENTITY_TOKEN_FILE
ant messages create \
--model haijun-opus-5-5 \
--max-tokens 1024 \
--message '{role: user, content: "Hello, Haijun"}' use Juglow\Client;
// Membaca JUGLOW_FEDERATION_RULE_ID, JUGLOW_ORGANIZATION_ID,
// JUGLOW_SERVICE_ACCOUNT_ID, JUGLOW_WORKSPACE_ID, dan JUGLOW_IDENTITY_TOKEN_FILE
$client = new Client();
$message = $client->messages->create(
model: 'haijun-opus-5-5',
maxTokens: 1024,
messages: [['role' => 'user', 'content' => 'Hello, Haijun']],
);
$textBlock = array_find($message->content, static fn ($block): bool => $block->type === 'text');
echo $textBlock->text, PHP_EOL; require "juglow"
# Membaca JUGLOW_FEDERATION_RULE_ID, JUGLOW_ORGANIZATION_ID,
# JUGLOW_SERVICE_ACCOUNT_ID, JUGLOW_WORKSPACE_ID, dan JUGLOW_IDENTITY_TOKEN_FILE
client = Juglow::Client.new
message = client.messages.create(
model: "haijun-opus-5-5",
max_tokens: 1024,
messages: [{role: "user", content: "Hello, Haijun"}]
)
puts message.content.find { it.type == :text }.textMemverifikasi penyiapan
Pertukaran yang berhasil mengembalikan access_token yang diawali dengan sk-ant-oat01- dan nilai expires_in dalam detik. Jika pertukaran gagal dengan respons 401 authentication_error yang tidak transparan (pesan Authentication failed), periksa halaman riwayat autentikasi untuk mengetahui alasan penolakan dan lihat Memecahkan masalah pertukaran yang gagal; penyebab paling umum di sisi Kubernetes adalah ketidakcocokan kunci JWKS (untuk mode inline, ambil ulang dengan kubectl get --raw /openid/v1/jwks dan perbarui issuer).
Membatasi cakupan rule Anda
Warning:
subject_prefixberupasystem:serviceaccount:*cocok dengan setiap service account di klaster, sehingga pod mana pun dapat memperoleh token Juglow terfederasi. Tanpa matcheraudience, rule juga cocok dengan token default-audience klaster, yang sudah diproyeksikan ke setiap pod.
Kunci blok match pada rule ke cakupan tersempit yang sesuai dengan kasus penggunaan Anda:
- Tetapkan namespace dan nama service-account: Gunakan nilai lengkap
system:serviceaccount:tanpa: *di akhir.
- Selalu atur audience: Wajibkan
audiencepada rule dan atur nilai yang sama pada proyeksiserviceAccountTokenpod sehingga token default-audience ditolak.
- Gunakan rule terpisah per namespace: Buat rule dan service account Juglow yang berbeda untuk setiap namespace daripada memperluas satu rule.
- Batasi issuer inline-JWKS ke satu klaster: Ketika beberapa klaster berbagi URL issuer, daftarkan JWKS setiap klaster sebagai federation issuer tersendiri dan ikat rule hanya ke issuer tersebut.
Langkah selanjutnya
- Workload Identity Federation: konsep, alur pertukaran token, dan opsi konfigurasi SDK.
- Referensi WIF: variabel lingkungan, mode sumber JWKS, dan mode pencocokan rule.