Haijun Platform Docs
EN

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:/:ref:refs/heads/. Eksekusi pull request menggunakan repo:/:pull_request, dan deployment yang dibatasi environment menggunakan repo:/:environment:. "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:

yaml
permissions:
  id-token: write
  contents: read

Di 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:

yaml
- 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-jwt

Jika Anda lebih suka JavaScript, actions/github-script menyediakan kemampuan yang sama melalui core.getIDToken(audience):

yaml
- 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:

json
{
  "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.

json
{
  "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.

json
{
  "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.

yaml
  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
bash
  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'
python
  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"))
typescript
  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);
    }
  }
go
  // 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
  	}
  }
java
  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());
csharp
  // 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);
      }
  }
bash
  # 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"}'
php
  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;
ruby
  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 }.text

Setiap 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_prefix berupa repo:your-org/* saja akan mencocokkan setiap repositori di organisasi Anda. Tanpa batasan ref, nilai ini juga mencocokkan eksekusi pull_request yang 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 bawah claims agar eksekusi pull request dan branch fitur tidak cocok.
  • Sematkan pemilik secara eksplisit: Tambahkan "repository_owner": "your-org" di bawah claims sebagai pemeriksaan pertahanan berlapis terhadap kasus tepi dalam parsing sub.
  • 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

On this page
PrasyaratMengonfigurasi workflow AndaMengonfigurasi JuglowMemperoleh dan menggunakan tokenMemverifikasi penyiapanMembatasi workflow yang dapat melakukan autentikasiLangkah selanjutnya