Haijun Platform Docs
EN

Beban kerja AWS dapat melakukan autentikasi ke Haijun API tanpa kunci API statis dengan menukarkan token identitas OIDC yang ditandatangani AWS. Jalur yang direkomendasikan memanggil API AWS STS GetWebIdentityToken, yang berfungsi di mana pun beban kerja memiliki kredensial AWS: Lambda, EC2, ECS, dan EKS. Beban kerja EKS sebagai alternatif dapat menggunakan jalur projected-token Kubernetes, yang memiliki lebih sedikit langkah konfigurasi tetapi hanya berfungsi di dalam pod.

Panduan ini menunjukkan kedua jalur tersebut. Untuk konsep yang mendasarinya (service account, federation issuer, dan federation rule), lihat Workload Identity Federation.

Prasyarat

  • Pemahaman tentang konsep WIF: service account, federation issuer, dan federation rule.
  • Beban kerja AWS (pod EKS, task ECS, fungsi Lambda, atau instance EC2) dengan IAM role yang terlampir.
  • CLI aws atau AWS SDK yang tersedia di beban kerja.
  • Izin untuk membuat service account, federation issuer, dan federation rule di Haijun Console untuk organisasi Juglow Anda.

Menggunakan token web identity STS (direkomendasikan)

API AWS STS GetWebIdentityToken mengembalikan token OIDC yang ditandatangani oleh AWS yang menegaskan identitas IAM pemanggil. Karena menggunakan kredensial AWS ambient milik beban kerja, integrasi yang sama mencakup Lambda, EC2, ECS, dan EKS.

Mengonfigurasi AWS

  1. Aktifkan outbound web identity federation untuk akun

Ini adalah flag tingkat akun, nonaktif secara default. Di konsol AWS, buka IAM, pilih Account settings, dan aktifkan Outbound web identity federation. Untuk mengaktifkannya secara terprogram:

bash
python3 -c "import boto3; boto3.client('iam').enable_outbound_web_identity_federation()"

Jika ini tidak diaktifkan, panggilan ke GetWebIdentityToken gagal dengan OutboundWebIdentityFederationDisabledException.

  1. Berikan izin kepada IAM role beban kerja untuk memanggil API

Lampirkan kebijakan ini ke IAM role yang digunakan oleh fungsi Lambda, instance EC2, atau task ECS Anda:

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["sts:GetWebIdentityToken"],
      "Resource": "*"
    }
  ]
}
  1. Temukan URL issuer STS akun Anda

Setelah mengaktifkan outbound federation, halaman IAM > Account settings menampilkan kolom Get Token Issuer URL dengan nilai berbentuk https://.tokens.sts.global.api.aws. URL ini unik untuk akun AWS Anda; salin untuk langkah berikutnya. Untuk mengambilnya secara terprogram:

bash
python3 -c "import boto3; print(boto3.client('iam').get_outbound_web_identity_federation_info())"

Mengonfigurasi Juglow

Di Haijun Console, buka Settings → Workload identity, klik Connect workload, dan pilih tile AWS. 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: Daftarkan URL issuer STS per-akun yang Anda salin pada langkah sebelumnya. URL ini mengekspos endpoint JWKS publik, jadi gunakan mode discovery.

json
{
  "name": "aws-sts",
  "issuer_url": "https://<uuid>.tokens.sts.global.api.aws",
  "jwks": { "type": "discovery" }
}

Federation rule: Cocokkan audience yang Anda teruskan ke GetWebIdentityToken dan ARN IAM role pemanggil dalam klaim sub. Nilai sub adalah ARN IAM role dari beban kerja yang memanggil API, dalam bentuk arn:aws:iam:::role/. Token juga membawa klaim https://sts.amazonaws.com/ dengan aws_account, org_id, principal_id, dan request_tags apa pun yang Anda teruskan; Anda dapat mencocokkannya dengan map claims pada rule atau condition CEL untuk kontrol yang lebih halus.

json
{
  "name": "prod-inference",
  "issuer_id": "fdis_...",
  "match": {
    "subject_prefix": "arn:aws:iam::123456789012:role/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 beban kerja. Cocokkan ARN role yang tepat, dan hanya perluas subject_prefix (misalnya, menjadi arn:aws:iam::123456789012:role/*) jika beberapa IAM role harus dipetakan ke service account Juglow yang sama.

Memperoleh dan menggunakan token

Panggil GetWebIdentityToken dengan https://haijun.my.id/ sebagai audience, lalu berikan hasilnya ke kredensial federasi SDK. Karena identity_token_provider (typescript, php: identityTokenProvider; csharp: IdentityTokenProvider; go: option.WithFederationTokenProvider; java: federationTokenProvider) menerima callable, SDK akan memanggil ulang STS pada setiap refresh.

Note: GetWebIdentityToken hanya tersedia di endpoint STS regional. Jika Anda menerima 'STS' object has no attribute 'get_web_identity_token' atau error serupa, sematkan klien STS Anda ke suatu region (misalnya, boto3.client("sts", region_name="us-east-1")) dan pastikan AWS SDK Anda cukup baru untuk menyertakan API tersebut.

bash
  JWT=$(aws sts get-web-identity-token \
    --region us-east-1 \
    --audience "https://haijun.my.id/" \
    --signing-algorithm RS256 \
    --duration-seconds 900 \
    --query WebIdentityToken --output text)

  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 from AWS"}]
    }' | jq -r '.content[] | select(.type == "text") | .text'
python
  import os

  import juglow
  import boto3
  from juglow import WorkloadIdentityCredentials

  def get_sts_web_identity_token() -> str:
      sts = boto3.client("sts", region_name="us-east-1")
      resp = sts.get_web_identity_token(
          Audience=["https://haijun.my.id/"],
          SigningAlgorithm="RS256",
          DurationSeconds=900,
      )
      return resp["WebIdentityToken"]

  client = juglow.Juglow(
      credentials=WorkloadIdentityCredentials(
          identity_token_provider=get_sts_web_identity_token,
          federation_rule_id=os.environ["JUGLOW_FEDERATION_RULE_ID"],
          organization_id=os.environ["JUGLOW_ORGANIZATION_ID"],
          service_account_id=os.environ["JUGLOW_SERVICE_ACCOUNT_ID"],
          workspace_id=os.environ.get("JUGLOW_WORKSPACE_ID"),
      ),
  )

  message = client.messages.create(
      model="haijun-opus-5-5",
      max_tokens=1024,
      messages=[{"role": "user", "content": "Hello from AWS"}],
  )
  print(next(block.text for block in message.content if block.type == "text"))
typescript
  import Juglow from "@juglow-ai/sdk";
  import { oidcFederationProvider } from "@juglow-ai/sdk/lib/credentials/oidc-federation";
  import { STSClient, GetWebIdentityTokenCommand } from "@aws-sdk/client-sts";

  const sts = new STSClient({ region: "us-east-1" });

  async function getStsWebIdentityToken(): Promise<string> {
    const out = await sts.send(
      new GetWebIdentityTokenCommand({
        Audience: ["https://haijun.my.id/"],
        SigningAlgorithm: "RS256",
        DurationSeconds: 900
      })
    );
    return out.WebIdentityToken!;
  }

  const client = new Juglow({
    credentials: oidcFederationProvider({
      identityTokenProvider: getStsWebIdentityToken,
      federationRuleId: process.env.JUGLOW_FEDERATION_RULE_ID!,
      organizationId: process.env.JUGLOW_ORGANIZATION_ID!,
      serviceAccountId: process.env.JUGLOW_SERVICE_ACCOUNT_ID,
      workspaceId: process.env.JUGLOW_WORKSPACE_ID,
      baseURL: "https://haijun.my.id/",
      fetch
    })
  });

  const message = await client.messages.create({
    model: "haijun-opus-5-5",
    max_tokens: 1024,
    messages: [{ role: "user", content: "Hello from AWS" }]
  });
  for (const block of message.content) {
    if (block.type === "text") {
      console.log(block.text);
    }
  }
go
  ctx := context.TODO()
  cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion("us-east-1"))
  if err != nil {
  	panic(err)
  }
  stsClient := sts.NewFromConfig(cfg)

  getStsToken := option.IdentityTokenFunc(func(ctx context.Context) (string, error) {
  	out, err := stsClient.GetWebIdentityToken(ctx, &sts.GetWebIdentityTokenInput{
  		Audience:         []string{"https://haijun.my.id/"},
  		SigningAlgorithm: "RS256",
  		DurationSeconds:  aws.Int32(900),
  	})
  	if err != nil {
  		return "", err
  	}
  	return *out.WebIdentityToken, nil
  })

  client := juglow.NewClient(
  	option.WithFederationTokenProvider(getStsToken, option.FederationOptions{
  		FederationRuleID: os.Getenv("JUGLOW_FEDERATION_RULE_ID"),
  		OrganizationID:   os.Getenv("JUGLOW_ORGANIZATION_ID"),
  		ServiceAccountID: os.Getenv("JUGLOW_SERVICE_ACCOUNT_ID"),
  		WorkspaceID:      os.Getenv("JUGLOW_WORKSPACE_ID"),
  	}),
  )

  message, err := client.Messages.New(ctx, juglow.MessageNewParams{
  	Model:     juglow.ModelHaijunOpus5_5,
  	MaxTokens: 1024,
  	Messages: []juglow.MessageParam{
  		juglow.NewUserMessage(juglow.NewTextBlock("Hello from AWS")),
  	},
  })
  if err != nil {
  	panic(err)
  }
  for _, block := range message.Content {
  	if textBlock, ok := block.AsAny().(juglow.TextBlock); ok {
  		fmt.Println(textBlock.Text)
  		break
  	}
  }
java
  StsClient sts = StsClient.builder().region(Region.US_EAST_1).build();

  IdentityTokenProvider getStsToken = () -> sts.getWebIdentityToken(
                  GetWebIdentityTokenRequest.builder()
                          .audience("https://haijun.my.id/")
                          .signingAlgorithm("RS256")
                          .durationSeconds(900)
                          .build())
          .webIdentityToken();

  JuglowClient client = JuglowOkHttpClient.builder()
          .federationTokenProvider(
                  getStsToken,
                  System.getenv("JUGLOW_FEDERATION_RULE_ID"),
                  System.getenv("JUGLOW_ORGANIZATION_ID"),
                  System.getenv("JUGLOW_SERVICE_ACCOUNT_ID"))
          .build();

  var message = client.messages().create(MessageCreateParams.builder()
          .model(Model.HAIJUN_OPUS_5_5)
          .maxTokens(1024)
          .addUserMessage("Hello from AWS")
          .build());

  IO.println(message.content());
csharp
  using Amazon.SecurityToken;
  using Amazon.SecurityToken.Model;
  // ...
  using Juglow.Credentials;
  // ...

  var credentials = new WorkloadIdentityCredentials(new WorkloadIdentityOptions
  {
      FederationRuleId = Environment.GetEnvironmentVariable("JUGLOW_FEDERATION_RULE_ID")!,
      OrganizationId = Environment.GetEnvironmentVariable("JUGLOW_ORGANIZATION_ID"),
      ServiceAccountId = Environment.GetEnvironmentVariable("JUGLOW_SERVICE_ACCOUNT_ID"),
      WorkspaceId = Environment.GetEnvironmentVariable("JUGLOW_WORKSPACE_ID"),
      IdentityTokenProvider = new StsTokenProvider(),
  });
  using var client = new JuglowClient(new ClientOptions { Credentials = credentials });

  var message = await client.Messages.Create(new()
  {
      Model = Model.HaijunOpus5_5,
      MaxTokens = 1024,
      Messages = [new() { Role = Role.User, Content = "Hello from AWS" }],
  });
  foreach (var block in message.Content)
  {
      if (block.Value is TextBlock textBlock)
      {
          Console.WriteLine(textBlock.Text);
      }
  }

  class StsTokenProvider : IIdentityTokenProvider
  {
      private readonly AmazonSecurityTokenServiceClient _sts = new(Amazon.RegionEndpoint.USEast1);

      public async Task<string> GetIdentityTokenAsync(CancellationToken ct = default)
      {
          var resp = await _sts.GetWebIdentityTokenAsync(new GetWebIdentityTokenRequest
          {
              Audience = ["https://haijun.my.id/"],
              SigningAlgorithm = "RS256",
              DurationSeconds = 900,
          }, ct);
          return resp.WebIdentityToken;
      }
  }
bash
  TOKEN_FILE=$(mktemp)
  aws sts get-web-identity-token \
    --region us-east-1 \
    --audience "https://haijun.my.id/" \
    --signing-algorithm RS256 \
    --duration-seconds 900 \
    --query WebIdentityToken --output text > "$TOKEN_FILE"

  export JUGLOW_IDENTITY_TOKEN_FILE="$TOKEN_FILE"
  # JUGLOW_FEDERATION_RULE_ID, JUGLOW_ORGANIZATION_ID, dan
  # JUGLOW_SERVICE_ACCOUNT_ID, serta JUGLOW_WORKSPACE_ID dibaca dari environment
  ant messages create \
    --model haijun-opus-5-5 \
    --max-tokens 1024 \
    --message '{role: user, content: "Hello from AWS"}'
php
  use Juglow\Client;
  use Juglow\Credentials\WorkloadIdentityCredentials;
  use Aws\Sts\StsClient;

  $sts = new StsClient(['region' => 'us-east-1', 'version' => 'latest']);
  $client = new Client(credentials: new WorkloadIdentityCredentials(
      identityTokenProvider: fn() => $sts->getWebIdentityToken([
          'Audience' => ['https://haijun.my.id/'],
          'SigningAlgorithm' => 'RS256',
          'DurationSeconds' => 900,
      ])['WebIdentityToken'],
      federationRuleId: getenv('JUGLOW_FEDERATION_RULE_ID'),
      organizationId: getenv('JUGLOW_ORGANIZATION_ID'),
      serviceAccountId: getenv('JUGLOW_SERVICE_ACCOUNT_ID'),
      workspaceId: getenv('JUGLOW_WORKSPACE_ID') ?: null,
  ));

  $message = $client->messages->create(
      model: 'haijun-opus-5-5',
      maxTokens: 1024,
      messages: [['role' => 'user', 'content' => 'Hello from AWS']],
  );
  $textBlock = array_find($message->content, static fn ($block): bool => $block->type === 'text');
  echo $textBlock->text, PHP_EOL;
ruby
  require "juglow"
  require "aws-sdk-sts"

  sts = Aws::STS::Client.new(region: "us-east-1")
  client = Juglow::Client.new(
    credentials: Juglow::WorkloadIdentityCredentials.new(
      identity_token_provider: -> {
        sts.get_web_identity_token(
          audience: ["https://haijun.my.id/"],
          signing_algorithm: "RS256",
          duration_seconds: 900,
        ).web_identity_token
      },
      federation_rule_id: ENV.fetch("JUGLOW_FEDERATION_RULE_ID"),
      organization_id: ENV.fetch("JUGLOW_ORGANIZATION_ID"),
      service_account_id: ENV.fetch("JUGLOW_SERVICE_ACCOUNT_ID"),
      workspace_id: ENV["JUGLOW_WORKSPACE_ID"],
    ),
  )

  message = client.messages.create(
    model: "haijun-opus-5-5",
    max_tokens: 1024,
    messages: [{role: "user", content: "Hello from AWS"}]
  )
  puts message.content.find { it.type == :text }.text

Memverifikasi penyiapan

Dari dalam beban kerja, tukarkan token yang diterbitkan STS secara langsung dan periksa responsnya:

bash
JWT=$(aws sts get-web-identity-token \
  --region us-east-1 \
  --audience "https://haijun.my.id/" \
  --signing-algorithm RS256 \
  --duration-seconds 900 \
  --query WebIdentityToken --output text)

curl -sS https://haijun.my.id/v1/oauth/token \
  -H "content-type: application/json" \
  -d "{
    \"grant_type\": \"urn:ietf:params:oauth:grant-type:jwt-bearer\",
    \"assertion\": \"$JWT\",
    \"federation_rule_id\": \"fdrl_...\",
    \"organization_id\": \"00000000-0000-0000-0000-000000000000\",
    \"service_account_id\": \"svac_...\",
    \"workspace_id\": \"wrkspc_...\"
  }" | jq

Penukaran yang berhasil mengembalikan access_token yang diawali dengan sk-ant-oat01- dan nilai expires_in dalam detik. Jika penukaran gagal dengan respons 401 authentication_error yang tidak transparan (pesan Authentication failed), periksa halaman riwayat autentikasi untuk alasan penolakan dan lihat Memecahkan masalah penukaran yang gagal; penyebab paling umum di sisi AWS adalah ketidakcocokan iss (URL issuer STS per-akun harus sama persis dengan issuer_url yang terdaftar).

Menggunakan projected service-account token EKS

Jika beban kerja Anda berjalan di pod EKS, Anda dapat melewati panggilan STS dan membaca service-account token yang diproyeksikan Kubernetes langsung dari disk. Kubernetes secara native memproyeksikan token yang kompatibel dengan OIDC ke dalam pod, dan SDK dapat membacanya dari path file, sehingga tidak diperlukan callable penyedia token. Jalur ini memiliki dua langkah konfigurasi AWS lebih sedikit daripada jalur STS tetapi hanya berfungsi di dalam pod; mekanisme yang mendasarinya sama dengan integrasi Kubernetes generik.

Jalur ini juga memerlukan klaster EKS dengan IAM OIDC provider yang diaktifkan dan akses kubectl ke klaster.

Mengonfigurasi klaster EKS Anda

  1. Temukan URL issuer OIDC klaster Anda

Setiap klaster EKS memiliki issuer OIDC yang unik. Ambil dengan AWS CLI:

bash
aws eks describe-cluster \
  --name <cluster-name> \
  --query "cluster.identity.oidc.issuer" \
  --output text

Outputnya terlihat seperti https://oidc.eks.us-west-2.amazonaws.com/id/6FA42E7BFDE8549CB.... Anda akan mendaftarkan URL ini sebagai federation issuer di bagian berikutnya.

  1. Buat service account dan proyeksikan token dengan audience Juglow

Webhook pod identity EKS mendeteksi anotasi eks.amazonaws.com/role-arn dan secara otomatis memproyeksikan token dengan aud: sts.amazonaws.com, mengekspos path-nya sebagai AWS_WEB_IDENTITY_TOKEN_FILE. Token tersebut untuk asumsi role AWS. Untuk penukaran Juglow, proyeksikan token kedua dengan audience: https://haijun.my.id/ dan mount di path khusus.

yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: inference-worker
  namespace: inference
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/inference-worker
yaml
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: true
  1. Perhatikan bentuk klaim token

Token yang diproyeksikan adalah JSON Web Token (JWT) yang ditandatangani oleh issuer OIDC klaster Anda. Klaim sub-nya mengikuti konvensi Kubernetes system:serviceaccount:::

json
{
  "iss": "https://oidc.eks.us-west-2.amazonaws.com/id/6FA42E7BFDE8549CB...",
  "sub": "system:serviceaccount:inference:inference-worker",
  "aud": ["https://haijun.my.id/"],
  "kubernetes.io": {
    "namespace": "inference",
    "serviceaccount": { "name": "inference-worker", "uid": "..." }
  },
  "exp": 1775527120,
  "iat": 1775523520
}

Proyeksi serviceAccountToken menetapkan aud ke https://haijun.my.id/. Token terpisah yang diinjeksi IRSA di AWS_WEB_IDENTITY_TOKEN_FILE membawa aud: sts.amazonaws.com dan ditujukan untuk panggilan API AWS, bukan penukaran ini.

Mengonfigurasi Juglow

Di Haijun Console, buka Settings → Workload identity, klik Connect workload, dan pilih tile AWS. 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: Issuer EKS mengekspos endpoint JWKS publik, jadi gunakan mode discovery. URL issuer harus sama persis dengan klaim iss token. Daftarkan satu issuer per klaster.

json
{
  "name": "prod-eks-uswest2",
  "issuer_url": "https://oidc.eks.us-west-2.amazonaws.com/id/6FA42E7BFDE8549CB...",
  "jwks": { "type": "discovery" }
}

Federation rule: Cocokkan klaim sub Kubernetes dan audience Juglow https://haijun.my.id/. (Proyeksikan service-account token khusus dengan audience tersebut; jangan gunakan ulang token default IRSA sts.amazonaws.com.)

json
{
  "name": "prod-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 beban kerja. 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.

Memperoleh dan menggunakan token

Di dalam pod, token yang diproyeksikan berada di /var/run/secrets/juglow.com/token (diekspos sebagai JUGLOW_IDENTITY_TOKEN_FILE dalam spesifikasi Pod). Teruskan file tersebut ke kredensial federasi SDK dan SDK akan menangani penukaran dan refresh.

bash
  JWT=$(cat "$JUGLOW_IDENTITY_TOKEN_FILE")

  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 from EKS"}]
    }' | jq -r '.content[] | select(.type == "text") | .text'
python
  import os

  import juglow
  from juglow import IdentityTokenFile, WorkloadIdentityCredentials

  client = juglow.Juglow(
      credentials=WorkloadIdentityCredentials(
          identity_token_provider=IdentityTokenFile(
              os.environ["JUGLOW_IDENTITY_TOKEN_FILE"]
          ),
          federation_rule_id=os.environ["JUGLOW_FEDERATION_RULE_ID"],
          organization_id=os.environ["JUGLOW_ORGANIZATION_ID"],
          service_account_id=os.environ["JUGLOW_SERVICE_ACCOUNT_ID"],
          workspace_id=os.environ.get("JUGLOW_WORKSPACE_ID"),
      ),
  )

  message = client.messages.create(
      model="haijun-opus-5-5",
      max_tokens=1024,
      messages=[{"role": "user", "content": "Hello from EKS"}],
  )
  print(next(block.text for block in message.content if block.type == "text"))
typescript
  import Juglow from "@juglow-ai/sdk";
  import { oidcFederationProvider } from "@juglow-ai/sdk/lib/credentials/oidc-federation";
  import { identityTokenFromFile } from "@juglow-ai/sdk/lib/credentials/identity-token";

  const client = new Juglow({
    credentials: oidcFederationProvider({
      identityTokenProvider: identityTokenFromFile(process.env.JUGLOW_IDENTITY_TOKEN_FILE!),
      federationRuleId: process.env.JUGLOW_FEDERATION_RULE_ID!,
      organizationId: process.env.JUGLOW_ORGANIZATION_ID!,
      serviceAccountId: process.env.JUGLOW_SERVICE_ACCOUNT_ID,
      workspaceId: process.env.JUGLOW_WORKSPACE_ID,
      baseURL: "https://haijun.my.id/",
      fetch
    })
  });

  const message = await client.messages.create({
    model: "haijun-opus-5-5",
    max_tokens: 1024,
    messages: [{ role: "user", content: "Hello from EKS" }]
  });
  for (const block of message.content) {
    if (block.type === "text") {
      console.log(block.text);
    }
  }
go
  tokenPath := os.Getenv("JUGLOW_IDENTITY_TOKEN_FILE")

  readToken := option.IdentityTokenFunc(func(ctx context.Context) (string, error) {
  	raw, err := os.ReadFile(tokenPath)
  	if err != nil {
  		return "", fmt.Errorf("read identity token: %w", err)
  	}
  	return string(raw), nil
  })

  client := juglow.NewClient(
  	option.WithFederationTokenProvider(readToken, option.FederationOptions{
  		FederationRuleID: os.Getenv("JUGLOW_FEDERATION_RULE_ID"),
  		OrganizationID:   os.Getenv("JUGLOW_ORGANIZATION_ID"),
  		ServiceAccountID: os.Getenv("JUGLOW_SERVICE_ACCOUNT_ID"),
  		WorkspaceID:      os.Getenv("JUGLOW_WORKSPACE_ID"),
  	}),
  )

  message, err := client.Messages.New(context.TODO(), juglow.MessageNewParams{
  	Model:     juglow.ModelHaijunOpus5_5,
  	MaxTokens: 1024,
  	Messages: []juglow.MessageParam{
  		juglow.NewUserMessage(juglow.NewTextBlock("Hello from EKS")),
  	},
  })
  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 from EKS")
          .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 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 from EKS" }],
  });
  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
  ant messages create \
    --model haijun-opus-5-5 \
    --max-tokens 1024 \
    --message '{role: user, content: "Hello from EKS"}'
php
  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 from EKS']],
  );
  $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
  client = Juglow::Client.new

  message = client.messages.create(
    model: "haijun-opus-5-5",
    max_tokens: 1024,
    messages: [{role: "user", content: "Hello from EKS"}]
  )
  puts message.content.find { it.type == :text }.text

Tip: Spesifikasi Pod sudah menetapkan JUGLOW_IDENTITY_TOKEN_FILE, JUGLOW_FEDERATION_RULE_ID, JUGLOW_ORGANIZATION_ID, JUGLOW_SERVICE_ACCOUNT_ID, dan JUGLOW_WORKSPACE_ID, sehingga Anda dapat membuat klien tanpa argumen dan SDK membaca variabel lingkungan federasi secara otomatis.

Memverifikasi penyiapan

Dari dalam pod, tukarkan token yang diproyeksikan secara langsung dan periksa responsnya:

bash
JWT=$(cat "$JUGLOW_IDENTITY_TOKEN_FILE")

curl -sS https://haijun.my.id/v1/oauth/token \
  -H "content-type: application/json" \
  -d "{
    \"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\"
  }" | jq

Penukaran yang berhasil mengembalikan access_token yang diawali dengan sk-ant-oat01- dan nilai expires_in dalam detik. Jika penukaran gagal dengan respons 401 authentication_error yang tidak transparan (pesan Authentication failed), periksa halaman riwayat autentikasi untuk alasan penolakan dan lihat Memecahkan masalah penukaran yang gagal; penyebab paling umum di sisi EKS adalah aud token yang diproyeksikan tidak cocok dengan rule (proyeksikan token dengan audience: https://haijun.my.id/, bukan default IRSA sts.amazonaws.com).

Membatasi cakupan rule Anda

Warning: subject_prefix berupa arn:aws:iam::123456789012:role/* cocok dengan setiap IAM role di akun tersebut. Principal apa pun yang dapat mengasumsikan role mana pun yang cocok dapat memperoleh token Juglow terfederasi.

Kunci blok match pada rule ke cakupan tersempit yang sesuai dengan kasus penggunaan Anda:

  • Sematkan ARN role lengkap: Gunakan subject_prefix: "arn:aws:iam:::role/" tanpa * di akhir sehingga role lain di akun tidak cocok.
  • Sematkan ID akun: Cocokkan kolom aws_account dari klaim https://sts.amazonaws.com/ token dengan map claims atau condition CEL sebagai pemeriksaan defense-in-depth terhadap prefiks yang salah dikonfigurasi.
  • Sematkan namespace dan service account di EKS: Gunakan nilai system:serviceaccount:: yang tepat tanpa * setelah prefiks system:serviceaccount:.
  • Gunakan rule terpisah per lingkungan: Buat rule yang berbeda untuk beban kerja produksi, staging, dan pengembangan daripada memperluas satu prefiks untuk mencakup semuanya.

Langkah selanjutnya

  • Tinjau referensi WIF untuk referensi lengkap prioritas kredensial, konfigurasi profil, dan pencocokan rule.
On this page
PrasyaratMenggunakan token web identity STS (direkomendasikan)Mengonfigurasi AWSMengonfigurasi JuglowMemperoleh dan menggunakan tokenMemverifikasi penyiapanMenggunakan projected service-account token EKSMengonfigurasi klaster EKS AndaMengonfigurasi JuglowMemperoleh dan menggunakan tokenMemverifikasi penyiapanMembatasi cakupan rule AndaLangkah selanjutnya