mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Improvements for DevUI (#5840)
This commit is contained in:
committed by
GitHub
Unverified
parent
ae666a4887
commit
0e12640c70
@@ -126,29 +126,6 @@ def serve(
|
||||
if not isinstance(port, int) or not (1 <= port <= 65535):
|
||||
raise ValueError(f"Invalid port: {port}. Must be integer between 1 and 65535")
|
||||
|
||||
# Security check: warn loudly when network-exposed without authentication.
|
||||
if host not in ("127.0.0.1", "localhost") and not auth_enabled:
|
||||
logger.warning("WARNING: Exposing DevUI to the network with --no-auth.")
|
||||
logger.warning("Anyone on your network can read agent metadata and trigger requests.")
|
||||
logger.warning("Drop --no-auth and DevUI will require Bearer tokens.")
|
||||
|
||||
# Refuse to auto-generate a token for network-exposed binds. Auto-generated tokens
|
||||
# are fine for localhost convenience; for anything else, require an explicit token.
|
||||
if auth_enabled and not auth_token:
|
||||
import os
|
||||
|
||||
env_token = os.environ.get("DEVUI_AUTH_TOKEN")
|
||||
if not env_token:
|
||||
is_production = (
|
||||
host not in ("127.0.0.1", "localhost")
|
||||
or os.environ.get("CI") == "true"
|
||||
or os.environ.get("KUBERNETES_SERVICE_HOST")
|
||||
)
|
||||
if is_production:
|
||||
logger.error("Authentication required but no token provided.")
|
||||
logger.error("Set DEVUI_AUTH_TOKEN env var or pass auth_token='...' to serve().")
|
||||
raise ValueError("DEVUI_AUTH_TOKEN required when host is not localhost")
|
||||
|
||||
# Enable instrumentation if requested
|
||||
if instrumentation_enabled:
|
||||
from agent_framework.observability import enable_instrumentation
|
||||
|
||||
@@ -81,13 +81,15 @@ Examples:
|
||||
parser.add_argument(
|
||||
"--no-auth",
|
||||
action="store_true",
|
||||
help="Disable Bearer token authentication. DevUI is auth-enabled by default; use this to opt out.",
|
||||
help=(
|
||||
"Disable Bearer token authentication for loopback-only local development. Non-loopback hosts require auth."
|
||||
),
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--auth-token",
|
||||
type=str,
|
||||
help="Custom Bearer token. Auto-generated and logged at startup when omitted.",
|
||||
help="Custom Bearer token. Required for non-loopback hosts when DEVUI_AUTH_TOKEN is not set.",
|
||||
)
|
||||
|
||||
parser.add_argument("--version", action="version", version=f"Agent Framework DevUI {get_version()}")
|
||||
|
||||
@@ -89,7 +89,7 @@ class DevServer:
|
||||
mode: Server mode - 'developer' (full access, verbose errors) or 'user' (restricted APIs, generic errors)
|
||||
auth_enabled: Whether to require Bearer token auth on /v1/* endpoints. Defaults to True.
|
||||
auth_token: Bearer token. If None and auth_enabled, falls back to the DEVUI_AUTH_TOKEN
|
||||
environment variable, then to an auto-generated token (logged at startup).
|
||||
environment variable. Loopback binds may use an auto-generated token logged at startup.
|
||||
"""
|
||||
self.entities_dir = entities_dir
|
||||
self.port = port
|
||||
@@ -106,7 +106,7 @@ class DevServer:
|
||||
self.ui_enabled = ui_enabled
|
||||
self.mode = mode
|
||||
self.auth_enabled = auth_enabled
|
||||
self.auth_token = self._resolve_auth_token(auth_enabled, auth_token)
|
||||
self.auth_token = self._resolve_auth_token(host, auth_enabled, auth_token)
|
||||
self.executor: AgentFrameworkExecutor | None = None
|
||||
self.openai_executor: OpenAIExecutor | None = None
|
||||
self.deployment_manager = DeploymentManager()
|
||||
@@ -118,8 +118,14 @@ class DevServer:
|
||||
"""Set in-memory entities to register on startup."""
|
||||
self._pending_entities = entities
|
||||
|
||||
_AUTH_LOOPBACK_HOSTS = frozenset({"127.0.0.1", "localhost"})
|
||||
_LOOPBACK_HOSTS = frozenset({"127.0.0.1", "localhost", "[::1]", "::1"})
|
||||
|
||||
@classmethod
|
||||
def _is_auth_loopback_host(cls, host: str) -> bool:
|
||||
"""Return True when unauthenticated DevUI may be limited to local loopback."""
|
||||
return host.lower() in cls._AUTH_LOOPBACK_HOSTS
|
||||
|
||||
def _loopback_allowed_hosts(self) -> frozenset[str] | None:
|
||||
"""Return the Host-header allowlist when bound to a loopback interface, else None.
|
||||
|
||||
@@ -131,16 +137,25 @@ class DevServer:
|
||||
return None
|
||||
return self._LOOPBACK_HOSTS
|
||||
|
||||
@staticmethod
|
||||
def _resolve_auth_token(auth_enabled: bool, auth_token: str | None) -> str | None:
|
||||
@classmethod
|
||||
def _resolve_auth_token(cls, host: str, auth_enabled: bool, auth_token: str | None) -> str | None:
|
||||
"""Resolve the active Bearer token. Returns None when auth is disabled."""
|
||||
is_loopback = cls._is_auth_loopback_host(host)
|
||||
if not auth_enabled:
|
||||
if not is_loopback:
|
||||
raise ValueError(
|
||||
"DevUI authentication cannot be disabled for non-loopback hosts. "
|
||||
"Bind to 127.0.0.1/localhost for no-auth local development, or enable auth and provide "
|
||||
"DEVUI_AUTH_TOKEN or auth_token for network-reachable binds."
|
||||
)
|
||||
return None
|
||||
if auth_token:
|
||||
return auth_token
|
||||
env_token = os.getenv("DEVUI_AUTH_TOKEN")
|
||||
if env_token:
|
||||
return env_token
|
||||
if not is_loopback:
|
||||
raise ValueError("DEVUI_AUTH_TOKEN or auth_token is required when DevUI is bound to a non-loopback host.")
|
||||
generated = secrets.token_urlsafe(32)
|
||||
logger.info("=" * 70)
|
||||
logger.info("DevUI authentication enabled with auto-generated token:")
|
||||
|
||||
Reference in New Issue
Block a user