Setiap eksekusi workflow GitHub Actions dapat meminta token identitas bertanda tangan dari penerbit yang di-host GitHub di https://token.actions.githubusercontent.com. Dengan "Workload Identity Federation" (federasi identitas beban kerja), workflow Anda menukar token tersebut dengan token akses Juglow berumur pendek, sehingga job CI Anda dapat memanggil Haijun API tanpa secret JUGLOW_API_KEY yang disimpan di repositori Anda.
Klaim sub pada token mengodekan konteks repositori dan pemicu. Untuk push ke sebuah branch, formatnya adalah repo:. Eksekusi pull request menggunakan repo:, dan deployment yang dibatasi environment menggunakan repo:. "Federation rule" (aturan federasi) Anda dicocokkan dengan klaim ini (dan klaim lainnya, seperti repository_owner dan ref) untuk menentukan eksekusi workflow mana yang diizinkan untuk melakukan autentikasi.
Prasyarat
- Pemahaman tentang konsep WIF: "service account" (akun layanan), "federation issuer" (penerbit federasi), dan "federation rule" (aturan federasi).
- Repositori GitHub tempat Anda dapat mengedit file workflow dan memberikan izin
id-token: write.
- Izin untuk membuat akun layanan, penerbit federasi, dan aturan federasi di Haijun Console untuk organisasi Juglow Anda.
- ID organisasi Juglow Anda. Anda dapat menemukannya di Haijun Console pada Settings → Organization.
Mengonfigurasi workflow Anda
GitHub hanya menerbitkan token identitas untuk job yang secara eksplisit memintanya. Tambahkan izin id-token: write di tingkat workflow atau job:
permissions:
id-token: write
contents: readDi dalam job, runner menyediakan dua variabel lingkungan: ACTIONS_ID_TOKEN_REQUEST_URL dan ACTIONS_ID_TOKEN_REQUEST_TOKEN. Panggil URL permintaan dengan token permintaan sebagai kredensial bearer dan "audience" (audiens) pilihan Anda sebagai parameter kueri, lalu tulis "JSON Web Token" (token web JSON), atau JWT, yang dikembalikan ke sebuah file:
- name: Fetch GitHub OIDC token
run: |
curl -sS -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=https://haijun.my.id/" \
| jq -r .value > /tmp/gha-jwtJika Anda lebih suka JavaScript, actions/github-script menyediakan kemampuan yang sama melalui core.getIDToken(audience):
- name: Fetch GitHub OIDC token
uses: actions/github-script@v8
with:
script: |
const fs = require('fs');
const token = await core.getIDToken('https://haijun.my.id/');
fs.writeFileSync('/tmp/gha-jwt', token);Token yang telah didekode membawa klaim yang mendeskripsikan eksekusi workflow. Aturan federasi Anda dicocokkan dengan klaim-klaim ini:
{
"iss": "https://token.actions.githubusercontent.com",
"sub": "repo:your-org/your-repo:ref:refs/heads/main",
"aud": "https://haijun.my.id/",
"repository": "your-org/your-repo",
"repository_owner": "your-org",
"ref": "refs/heads/main",
"sha": "abc123...",
"workflow": "CI",
"actor": "octocat",
"event_name": "push"
}Lihat referensi klaim subjek OIDC GitHub untuk daftar lengkap format sub.
Mengonfigurasi Juglow
Di Haijun Console, buka Settings → Workload identity, klik Connect workload, lalu pilih tile GitHub Actions. Wizard akan memandu Anda mendaftarkan issuer, membuat akun layanan, dan membuat aturan federasi.
Wizard membuat sumber daya ini untuk Anda. Gunakan nilai-nilai berikut, baik Anda memasukkannya di wizard maupun mengirimkannya ke Admin API:
Penerbit federasi: GitHub memublikasikan dokumen discovery OIDC dan JWKS-nya secara publik, jadi gunakan mode discovery. Juglow memperbarui kunci secara otomatis ketika GitHub merotasinya.
{
"name": "github-actions",
"issuer_url": "https://token.actions.githubusercontent.com",
"jwks": { "type": "discovery" }
}Aturan federasi: Cocokkan hanya eksekusi workflow yang memang ingin Anda percayai. Lihat Membatasi workflow yang dapat melakukan autentikasi untuk cara membatasi cakupan klaim-klaim ini dengan aman.
{
"name": "gha-main",
"issuer_id": "fdis_...",
"match": {
"subject_prefix": "repo:your-org/your-repo:ref:refs/heads/main",
"audience": "https://haijun.my.id/",
"claims": {
"repository_owner": "your-org"
}
},
"target": {
"type": "service_account",
"service_account_id": "svac_..."
},
"workspace_id": "wrkspc_...",
"oauth_scope": "workspace:developer",
"token_lifetime_seconds": 600
}Buat aturan sespesifik yang dimungkinkan oleh workload. Longgarkan subject_prefix menjadi repo:your-org/your-repo:* (dipasangkan dengan batasan claims.ref) hanya jika aturan harus mencocokkan beberapa jenis event dari repositori yang sama. Hal ini karena segmen akhir sub berbeda-beda antara event ref:..., environment:..., dan pull_request.
Memperoleh dan menggunakan token
Atur variabel lingkungan federasi pada job dan panggil SDK seperti biasa. Juglow() (typescript: new Juglow(); csharp: new JuglowClient(); go: juglow.NewClient(); java: JuglowOkHttpClient.fromEnv(); php: new Client(); ruby: Juglow::Client.new) membaca JUGLOW_IDENTITY_TOKEN_FILE, menukar JWT pada permintaan pertama, dan memperbarui token akses secara otomatis sebelum kedaluwarsa.
name: Call Haijun
on: push
permissions:
id-token: write
contents: read
jobs:
call-haijun:
runs-on: ubuntu-latest
env:
JUGLOW_FEDERATION_RULE_ID: fdrl_...
JUGLOW_ORGANIZATION_ID: 00000000-0000-0000-0000-000000000000
JUGLOW_SERVICE_ACCOUNT_ID: svac_...
JUGLOW_WORKSPACE_ID: wrkspc_... # required when the rule covers multiple workspaces
JUGLOW_IDENTITY_TOKEN_FILE: /tmp/gha-jwt
steps:
- uses: actions/checkout@v5
- name: Fetch GitHub OIDC token
run: |
curl -sS -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=https://haijun.my.id/" \
| jq -r .value > "$JUGLOW_IDENTITY_TOKEN_FILE"
- name: Run your script
run: |
pip install juglow
python your_script.py JWT=$(cat /tmp/gha-jwt)
RESPONSE=$(curl -sS https://haijun.my.id/v1/oauth/token \
-H "content-type: application/json" \
--data @- <<JSON
{
"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
)
ACCESS_TOKEN=$(echo "$RESPONSE" | jq -r .access_token)
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_FEDERATION_RULE_ID, JUGLOW_ORGANIZATION_ID,
# JUGLOW_SERVICE_ACCOUNT_ID, JUGLOW_WORKSPACE_ID, dan JUGLOW_IDENTITY_TOKEN_FILE
# dari environment job.
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_FEDERATION_RULE_ID, JUGLOW_ORGANIZATION_ID,
// JUGLOW_SERVICE_ACCOUNT_ID, JUGLOW_WORKSPACE_ID, dan JUGLOW_IDENTITY_TOKEN_FILE
// dari environment job.
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_FEDERATION_RULE_ID, JUGLOW_ORGANIZATION_ID,
// JUGLOW_SERVICE_ACCOUNT_ID, JUGLOW_WORKSPACE_ID, dan JUGLOW_IDENTITY_TOKEN_FILE
// dari environment job.
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_FEDERATION_RULE_ID, JUGLOW_ORGANIZATION_ID,
// JUGLOW_SERVICE_ACCOUNT_ID, JUGLOW_WORKSPACE_ID, dan JUGLOW_IDENTITY_TOKEN_FILE
// dari lingkungan job.
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
# dari environment job.
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
// dari environment job.
$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
# dari environment job.
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 }.textSetiap token identitas yang diterbitkan GitHub kedaluwarsa sekitar lima menit setelah diterbitkan. Endpoint permintaan token (ACTIONS_ID_TOKEN_REQUEST_URL) tetap valid selama seluruh job, sehingga Anda dapat mengambil token baru kapan saja. SDK menukar token pada penggunaan pertama dan menyimpan cache token akses Juglow yang dihasilkan. Untuk job yang berjalan lebih lama dari masa berlaku token Juglow, SDK membaca ulang JUGLOW_IDENTITY_TOKEN_FILE pada setiap pembaruan, jadi jalankan ulang langkah pengambilan secara berkala (atau bungkus dalam loop latar belakang) agar file tetap mutakhir. Sebagai alternatif, berikan callback penyedia token ke SDK yang memanggil ACTIONS_ID_TOKEN_REQUEST_URL secara langsung alih-alih menggunakan path file.
Memverifikasi penyiapan
Pertukaran yang berhasil mengembalikan access_token yang diawali dengan sk-ant-oat01- dan nilai expires_in dalam detik. Pertukaran yang ditolak mengembalikan 401 authentication_error yang buram dengan pesan tetap Authentication failed, apa pun pemeriksaan yang gagal.
Dalam sebagian besar kasus, alasan penolakan dicatat pada entri percobaan tersebut di halaman riwayat autentikasi. Panduan Memecahkan masalah pertukaran yang gagal menjelaskan pemeriksaan tersebut secara berurutan.
Penyebab paling umum dari sisi GitHub Actions adalah format klaim sub yang tidak cocok, karena segmen akhirnya berbeda-beda antara event ref:..., environment:..., dan pull_request. Dalam kasus ini, entri riwayat menampilkan alasan match_subject_prefix.
Membatasi workflow yang dapat melakukan autentikasi
Warning:
subject_prefixberuparepo:your-org/*saja akan mencocokkan setiap repositori di organisasi Anda. Tanpa batasanref, nilai ini juga mencocokkan eksekusipull_requestyang dipicu dari fork. Akibatnya, siapa pun yang dapat membuka pull request ke repositori yang cocok dapat memperoleh token Juglow terfederasi.
Kunci blok match pada aturan ke cakupan tersempit yang sesuai dengan kasus penggunaan Anda:
- Sematkan ke satu repositori: Gunakan
subject_prefix: "repo:your-org/your-repo:*"agar repositori lain di organisasi tidak cocok.
- Sematkan ke branch yang dilindungi: Tambahkan
"ref": "refs/heads/main"(atau branch rilis Anda) di bawahclaimsagar eksekusi pull request dan branch fitur tidak cocok.
- Sematkan pemilik secara eksplisit: Tambahkan
"repository_owner": "your-org"di bawahclaimssebagai pemeriksaan pertahanan berlapis terhadap kasus tepi dalam parsingsub.
- Sematkan ke environment deployment: Untuk job deploy, cocokkan
subject_prefix: "repo:your-org/your-repo:environment:production"dan lindungi environment tersebut dengan reviewer wajib di GitHub.
Langkah selanjutnya
- Workload Identity Federation: panduan penyiapan lengkap, variabel lingkungan, dan urutan prioritas kredensial.
- Autentikasi: perbandingan federasi dengan kunci API.