Skip to content

Commit 34dc6ff

Browse files
authored
feat(server): add security to agent cards (#1351)
Signed-off-by: Tomas Pilar <[email protected]>
1 parent e2ee7d1 commit 34dc6ff

File tree

2 files changed

+45
-8
lines changed

2 files changed

+45
-8
lines changed

apps/agentstack-server/src/agentstack_server/api/routes/a2a.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,40 @@
99
import fastapi.responses
1010
from a2a.server.apps import A2AFastAPIApplication
1111
from a2a.server.apps.rest.rest_adapter import RESTAdapter
12-
from a2a.types import AgentCard, AgentInterface, TransportProtocol
12+
from a2a.types import AgentCard, AgentInterface, HTTPAuthSecurityScheme, SecurityScheme, TransportProtocol
1313
from a2a.utils import AGENT_CARD_WELL_KNOWN_PATH
1414
from fastapi import Depends, HTTPException, Request
1515

1616
from agentstack_server.api.dependencies import (
1717
A2AProxyServiceDependency,
18+
ConfigurationDependency,
1819
ProviderServiceDependency,
1920
RequiresPermissions,
2021
)
22+
from agentstack_server.configuration import Configuration
2123
from agentstack_server.domain.models.permissions import AuthorizedUser
2224
from agentstack_server.service_layer.services.a2a import A2AServerResponse
2325

2426
router = fastapi.APIRouter()
2527

2628

27-
def create_proxy_agent_card(agent_card: AgentCard, *, provider_id: UUID, request: Request) -> AgentCard:
29+
def create_proxy_agent_card(
30+
agent_card: AgentCard, *, provider_id: UUID, request: Request, configuration: Configuration
31+
) -> AgentCard:
2832
proxy_base = str(request.url_for(a2a_proxy_jsonrpc_transport.__name__, provider_id=provider_id))
33+
34+
proxy_security = []
35+
proxy_security_schemes = {}
36+
if not configuration.auth.disable_auth:
37+
# Note that we're purposefully not using oAuth but a more generic http scheme.
38+
# This is because we don't want to declare the auth metadata but prefer discovery through related RFCs
39+
# The http scheme also covers internal jwt tokens
40+
proxy_security.append({"bearer": []})
41+
proxy_security_schemes["bearer"] = SecurityScheme(HTTPAuthSecurityScheme(scheme="bearer"))
42+
if configuration.auth.basic.enabled:
43+
proxy_security.append({"basic": []})
44+
proxy_security_schemes["basic"] = SecurityScheme(HTTPAuthSecurityScheme(scheme="basic"))
45+
2946
return agent_card.model_copy(
3047
update={
3148
"preferred_transport": TransportProtocol.jsonrpc,
@@ -34,6 +51,8 @@ def create_proxy_agent_card(agent_card: AgentCard, *, provider_id: UUID, request
3451
AgentInterface(transport=TransportProtocol.http_json, url=urljoin(proxy_base, "http")),
3552
AgentInterface(transport=TransportProtocol.jsonrpc, url=proxy_base),
3653
],
54+
"security": proxy_security,
55+
"security_schemes": proxy_security_schemes,
3756
}
3857
)
3958

@@ -51,10 +70,13 @@ async def get_agent_card(
5170
provider_id: UUID,
5271
request: Request,
5372
provider_service: ProviderServiceDependency,
73+
configuration: ConfigurationDependency,
5474
_: Annotated[AuthorizedUser, Depends(RequiresPermissions(providers={"read"}))],
5575
) -> AgentCard:
5676
provider = await provider_service.get_provider(provider_id=provider_id)
57-
return create_proxy_agent_card(provider.agent_card, provider_id=provider.id, request=request)
77+
return create_proxy_agent_card(
78+
provider.agent_card, provider_id=provider.id, request=request, configuration=configuration
79+
)
5880

5981

6082
@router.post("/{provider_id}")
@@ -64,10 +86,13 @@ async def a2a_proxy_jsonrpc_transport(
6486
request: fastapi.requests.Request,
6587
a2a_proxy: A2AProxyServiceDependency,
6688
provider_service: ProviderServiceDependency,
89+
configuration: ConfigurationDependency,
6790
user: Annotated[AuthorizedUser, Depends(RequiresPermissions(a2a_proxy={"*"}))],
6891
):
6992
provider = await provider_service.get_provider(provider_id=provider_id)
70-
agent_card = create_proxy_agent_card(provider.agent_card, provider_id=provider.id, request=request)
93+
agent_card = create_proxy_agent_card(
94+
provider.agent_card, provider_id=provider.id, request=request, configuration=configuration
95+
)
7196

7297
handler = await a2a_proxy.get_request_handler(provider=provider, user=user.user)
7398
app = A2AFastAPIApplication(agent_card=agent_card, http_handler=handler)
@@ -83,11 +108,14 @@ async def a2a_proxy_http_transport(
83108
request: fastapi.requests.Request,
84109
a2a_proxy: A2AProxyServiceDependency,
85110
provider_service: ProviderServiceDependency,
111+
configuration: ConfigurationDependency,
86112
user: Annotated[AuthorizedUser, Depends(RequiresPermissions(a2a_proxy={"*"}))],
87113
path: str = "",
88114
):
89115
provider = await provider_service.get_provider(provider_id=provider_id)
90-
agent_card = create_proxy_agent_card(provider.agent_card, provider_id=provider.id, request=request)
116+
agent_card = create_proxy_agent_card(
117+
provider.agent_card, provider_id=provider.id, request=request, configuration=configuration
118+
)
91119

92120
handler = await a2a_proxy.get_request_handler(provider=provider, user=user.user)
93121
adapter = RESTAdapter(agent_card=agent_card, http_handler=handler)

apps/agentstack-server/src/agentstack_server/api/routes/providers.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ async def preview_provider(
7474
@router.get("")
7575
async def list_providers(
7676
provider_service: ProviderServiceDependency,
77+
configuration: ConfigurationDependency,
7778
request: Request,
7879
user: Annotated[AuthorizedUser, Depends(RequiresPermissions(providers={"read"}), use_cache=False)],
7980
user_owned: Annotated[bool | None, Query()] = None,
@@ -83,7 +84,9 @@ async def list_providers(
8384
for provider in await provider_service.list_providers(user=user.user, user_owned=user_owned, origin=origin):
8485
new_provider = provider.model_copy(
8586
update={
86-
"agent_card": create_proxy_agent_card(provider.agent_card, provider_id=provider.id, request=request)
87+
"agent_card": create_proxy_agent_card(
88+
provider.agent_card, provider_id=provider.id, request=request, configuration=configuration
89+
)
8790
}
8891
)
8992
providers.append(EntityModel(new_provider))
@@ -95,14 +98,17 @@ async def list_providers(
9598
async def get_provider(
9699
id: UUID,
97100
provider_service: ProviderServiceDependency,
101+
configuration: ConfigurationDependency,
98102
request: Request,
99103
_: Annotated[AuthorizedUser, Depends(RequiresPermissions(providers={"read"}))],
100104
) -> EntityModel[ProviderWithState]:
101105
provider = await provider_service.get_provider(provider_id=id)
102106
return EntityModel(
103107
provider.model_copy(
104108
update={
105-
"agent_card": create_proxy_agent_card(provider.agent_card, provider_id=provider.id, request=request)
109+
"agent_card": create_proxy_agent_card(
110+
provider.agent_card, provider_id=provider.id, request=request, configuration=configuration
111+
)
106112
}
107113
)
108114
)
@@ -112,6 +118,7 @@ async def get_provider(
112118
async def get_provider_by_location(
113119
location: str,
114120
provider_service: ProviderServiceDependency,
121+
configuration: ConfigurationDependency,
115122
request: Request,
116123
_: Annotated[AuthorizedUser, Depends(RequiresPermissions(providers={"read"}))],
117124
) -> EntityModel[ProviderWithState]:
@@ -123,7 +130,9 @@ async def get_provider_by_location(
123130
return EntityModel(
124131
provider.model_copy(
125132
update={
126-
"agent_card": create_proxy_agent_card(provider.agent_card, provider_id=provider.id, request=request)
133+
"agent_card": create_proxy_agent_card(
134+
provider.agent_card, provider_id=provider.id, request=request, configuration=configuration
135+
)
127136
}
128137
)
129138
)

0 commit comments

Comments
 (0)