Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ See [deployment and operation](service/studio_release_notifier/README.md).
Studio's ADK chat protocol. Sessions require persistent storage to survive
container replacement, and multiple replicas require appropriate session routing.

- **Runtime IAM role reuse**: ordinary and quick Agent creation reuse the first
role with the `AgentKitDefaultRuntimeAccess` system policy in the selected
cloud account, including roles on later IAM result pages. If none matches,
Studio creates `AgentKit_Runtime_Default_ServiceRole_<7 random characters>`
with only that policy. Existing roles keep all their current permissions;
quick creation no longer adds `AgentKitFullAccess`. Lookup errors stop the
deployment instead of triggering role creation. This applies to Volcengine
and BytePlus; existing Runtime updates and Sidecar deployments keep their
existing role behavior

- **Sandbox updates** in System Information compare each Tool's current image
with `ListToolTypes` for its cloud provider and actual region. Volcengine and
BytePlus use their own credentials and API hosts; catalogs are cached for
Expand Down
133 changes: 133 additions & 0 deletions frontend/server/runtime_iam.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Copyright (c) 2026 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Select IAM roles for new Studio Agent Runtimes"""

from __future__ import annotations

import json
import os
from typing import Any

from veadk.utils.cloud_provider import (
DEFAULT_CLOUD_PROVIDER,
CloudProvider,
iam_openapi_host,
)

DEFAULT_RUNTIME_POLICY = "AgentKitDefaultRuntimeAccess"
_ROLE_PAGE_SIZE = 100


def _result(response: dict[str, Any]) -> dict[str, Any]:
error = (response.get("ResponseMetadata") or {}).get("Error")
if error:
raise RuntimeError(error.get("Message") or str(error))
result = response.get("Result", {})
if not isinstance(result, dict):
raise RuntimeError("IAM response is missing Result")
return result


def _find_reusable_role(iam: Any) -> str | None:
offset = 0
while True:
page = _result(iam.list_roles({"Limit": _ROLE_PAGE_SIZE, "Offset": offset}))
roles = page.get("RoleMetadata")
total = page.get("Total")
if not isinstance(roles, list) or not isinstance(total, int) or total < 0:
raise RuntimeError("IAM returned an invalid role list")
for role in roles:
name = role.get("RoleName")
if not isinstance(name, str) or not name.strip():
raise RuntimeError("IAM role is missing RoleName")
policies = _result(iam.list_attached_role_policies({"RoleName": name})).get(
"AttachedPolicyMetadata"
)
if not isinstance(policies, list):
raise RuntimeError("IAM returned an invalid role policy list")
if any(
policy.get("PolicyName") == DEFAULT_RUNTIME_POLICY
and policy.get("PolicyType") == "System"
for policy in policies
):
return name
offset += len(roles)
if offset >= total:
return None
if not roles:
raise RuntimeError("IAM returned an incomplete role list")


def ensure_runtime_role(
*,
access_key: str,
secret_key: str,
session_token: str | None = None,
provider: CloudProvider = DEFAULT_CLOUD_PROVIDER,
) -> str:
"""Reuse a matching role, or create one with only the default runtime policy

Called under Studio's deployment lock so concurrent local deployments can
reuse the role created by the previous deployment
"""
from volcengine.iam.IamService import IamService

iam = IamService()
iam.set_ak(access_key)
iam.set_sk(secret_key)
iam.set_host(iam_openapi_host(provider))
iam.set_scheme("https")
if session_token:
iam.set_session_token(session_token)

existing = _find_reusable_role(iam)
if existing is not None:
return existing

from agentkit.utils.misc import generate_runtime_role_name

name = generate_runtime_role_name()
service_code = (
os.getenv("VOLCENGINE_AGENTKIT_SERVICE")
or os.getenv("VOLC_AGENTKIT_SERVICE")
or os.getenv("BYTEPLUS_AGENTKIT_SERVICE")
or ""
).lower()
trust_policy = {
"Statement": [
{
"Effect": "Allow",
"Action": ["sts:AssumeRole"],
"Principal": {
"Service": ["vefaas_dev" if "stg" in service_code else "vefaas"]
},
}
]
}
_result(
iam.create_role(
{"RoleName": name, "TrustPolicyDocument": json.dumps(trust_policy)}
)
)
_result(
iam.attach_role_policy(
{
"RoleName": name,
"PolicyName": DEFAULT_RUNTIME_POLICY,
"PolicyType": "System",
}
)
)
return name
9 changes: 9 additions & 0 deletions tests/cli/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@
from __future__ import annotations

import pytest
from unittest.mock import MagicMock


@pytest.fixture(autouse=True)
def _stub_studio_runtime_role(monkeypatch: pytest.MonkeyPatch) -> MagicMock:
"""Keep deployment tests isolated from live Runtime IAM operations"""
resolver = MagicMock(return_value="shared-runtime-role")
monkeypatch.setattr("frontend.server.runtime_iam.ensure_runtime_role", resolver)
return resolver


@pytest.fixture(autouse=True)
Expand Down
90 changes: 0 additions & 90 deletions tests/cli/test_agentkit_runtime_iam.py

This file was deleted.

Loading
Loading