Skip to content

Commit 88c5101

Browse files
authored
feat: add support for policies and certificates (aws#371)
1 parent 9c52bf7 commit 88c5101

5 files changed

Lines changed: 496 additions & 1 deletion

File tree

src/bedrock_agentcore/tools/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,20 @@
77
BrowserConfiguration,
88
BrowserExtension,
99
BrowserSigningConfiguration,
10+
Certificate,
11+
CertificateLocation,
1012
CodeInterpreterConfiguration,
13+
EnterprisePolicy,
14+
EnterprisePolicyS3Location,
1115
ExtensionS3Location,
1216
ExternalProxy,
1317
NetworkConfiguration,
1418
ProfileConfiguration,
1519
ProxyConfiguration,
1620
ProxyCredentials,
1721
RecordingConfiguration,
22+
ResourceLocation,
23+
SecretsManagerLocation,
1824
SessionConfiguration,
1925
ViewportConfiguration,
2026
VpcConfig,
@@ -25,19 +31,25 @@
2531
"BasicAuth",
2632
"BrowserClient",
2733
"browser_session",
34+
"Certificate",
35+
"CertificateLocation",
2836
"CodeInterpreter",
2937
"code_session",
3038
"BrowserConfiguration",
3139
"BrowserExtension",
3240
"BrowserSigningConfiguration",
3341
"CodeInterpreterConfiguration",
42+
"EnterprisePolicy",
43+
"EnterprisePolicyS3Location",
3444
"ExtensionS3Location",
3545
"ExternalProxy",
3646
"NetworkConfiguration",
3747
"ProfileConfiguration",
3848
"ProxyConfiguration",
3949
"ProxyCredentials",
4050
"RecordingConfiguration",
51+
"ResourceLocation",
52+
"SecretsManagerLocation",
4153
"SessionConfiguration",
4254
"ViewportConfiguration",
4355
"VpcConfig",

src/bedrock_agentcore/tools/browser_client.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,14 @@
2222
from bedrock_agentcore._utils.user_agent import build_user_agent_suffix
2323

2424
from .._utils.endpoints import get_control_plane_endpoint, get_data_plane_endpoint
25-
from .config import BrowserExtension, ProfileConfiguration, ProxyConfiguration, ViewportConfiguration
25+
from .config import (
26+
BrowserExtension,
27+
Certificate,
28+
EnterprisePolicy,
29+
ProfileConfiguration,
30+
ProxyConfiguration,
31+
ViewportConfiguration,
32+
)
2633

2734

2835
def _to_dict(value):
@@ -116,6 +123,8 @@ def create_browser(
116123
description: Optional[str] = None,
117124
recording: Optional[Dict] = None,
118125
browser_signing: Optional[Dict] = None,
126+
enterprise_policies: Optional[List[Union[EnterprisePolicy, Dict[str, Any]]]] = None,
127+
certificates: Optional[List[Union[Certificate, Dict[str, Any]]]] = None,
119128
tags: Optional[Dict[str, str]] = None,
120129
client_token: Optional[str] = None,
121130
) -> Dict:
@@ -148,6 +157,11 @@ def create_browser(
148157
{
149158
"enabled": True
150159
}
160+
enterprise_policies (Optional[List[Union[EnterprisePolicy, Dict]]]): Chromium
161+
enterprise policies at managed enforcement level. Up to 10 policy files,
162+
each .json and max 5MB, from a same-region S3 bucket.
163+
certificates (Optional[List[Union[Certificate, Dict]]]): Root CA certificates
164+
from Secrets Manager for the browser to trust.
151165
tags (Optional[Dict[str, str]]): Tags for the browser
152166
client_token (Optional[str]): Idempotency token
153167
@@ -194,6 +208,12 @@ def create_browser(
194208
request_params["browserSigning"] = browser_signing
195209
self.logger.info("🔐 Web Bot Auth (browserSigning) enabled")
196210

211+
if enterprise_policies:
212+
request_params["enterprisePolicies"] = [_to_dict(p) for p in enterprise_policies]
213+
214+
if certificates:
215+
request_params["certificates"] = [_to_dict(c) for c in certificates]
216+
197217
if tags:
198218
request_params["tags"] = tags
199219

@@ -299,6 +319,8 @@ def start(
299319
proxy_configuration: Optional[Union[ProxyConfiguration, Dict[str, Any]]] = None,
300320
extensions: Optional[List[Union[BrowserExtension, Dict[str, Any]]]] = None,
301321
profile_configuration: Optional[Union[ProfileConfiguration, Dict[str, Any]]] = None,
322+
enterprise_policies: Optional[List[Union[EnterprisePolicy, Dict[str, Any]]]] = None,
323+
certificates: Optional[List[Union[Certificate, Dict[str, Any]]]] = None,
302324
) -> str:
303325
"""Start a browser sandbox session.
304326
@@ -324,6 +346,11 @@ def start(
324346
configuration for persisting browser state across sessions. Can be a
325347
ProfileConfiguration dataclass or a plain dict:
326348
{"profileIdentifier": "my-profile-id"}
349+
enterprise_policies (Optional[List[Union[EnterprisePolicy, Dict]]]): Chromium
350+
enterprise policies at recommended enforcement level. Up to 10 policy files,
351+
each .json and max 5MB, from a same-region S3 bucket.
352+
certificates (Optional[List[Union[Certificate, Dict]]]): Root CA certificates
353+
from Secrets Manager for the browser session to trust.
327354
328355
Returns:
329356
str: The session ID of the newly created session.
@@ -373,6 +400,12 @@ def start(
373400
if profile_configuration is not None:
374401
request_params["profileConfiguration"] = _to_dict(profile_configuration)
375402

403+
if enterprise_policies is not None:
404+
request_params["enterprisePolicies"] = [_to_dict(p) for p in enterprise_policies]
405+
406+
if certificates is not None:
407+
request_params["certificates"] = [_to_dict(c) for c in certificates]
408+
376409
response = self.data_plane_client.start_browser_session(**request_params)
377410

378411
self.identifier = response["browserIdentifier"]
@@ -633,6 +666,8 @@ def browser_session(
633666
proxy_configuration: Optional[Union[ProxyConfiguration, Dict[str, Any]]] = None,
634667
extensions: Optional[List[Union[BrowserExtension, Dict[str, Any]]]] = None,
635668
profile_configuration: Optional[Union[ProfileConfiguration, Dict[str, Any]]] = None,
669+
enterprise_policies: Optional[List[Union[EnterprisePolicy, Dict[str, Any]]]] = None,
670+
certificates: Optional[List[Union[Certificate, Dict[str, Any]]]] = None,
636671
) -> Generator[BrowserClient, None, None]:
637672
"""Context manager for creating and managing a browser sandbox session.
638673
@@ -648,6 +683,10 @@ def browser_session(
648683
extensions. Each element can be a BrowserExtension dataclass or a plain dict.
649684
profile_configuration (Optional[Union[ProfileConfiguration, Dict[str, Any]]]): Profile
650685
configuration. Can be a ProfileConfiguration dataclass or a plain dict.
686+
enterprise_policies (Optional[List[Union[EnterprisePolicy, Dict[str, Any]]]]): Chromium
687+
enterprise policies. Each element can be an EnterprisePolicy dataclass or a plain dict.
688+
certificates (Optional[List[Union[Certificate, Dict[str, Any]]]]): Root CA certificates.
689+
Each element can be a Certificate dataclass or a plain dict.
651690
652691
Yields:
653692
BrowserClient: An initialized and started browser client.
@@ -687,6 +726,10 @@ def browser_session(
687726
start_kwargs["extensions"] = extensions
688727
if profile_configuration is not None:
689728
start_kwargs["profile_configuration"] = profile_configuration
729+
if enterprise_policies is not None:
730+
start_kwargs["enterprise_policies"] = enterprise_policies
731+
if certificates is not None:
732+
start_kwargs["certificates"] = certificates
690733

691734
client.start(**start_kwargs)
692735

src/bedrock_agentcore/tools/config.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,124 @@ def to_dict(self) -> Dict:
452452
return config
453453

454454

455+
@dataclass
456+
class EnterprisePolicyS3Location:
457+
"""S3 location of a browser enterprise policy JSON file.
458+
459+
Attributes:
460+
bucket: S3 bucket name (must be in the same region as the API call)
461+
prefix: S3 object key for the policy JSON file
462+
version_id: Optional S3 object version ID
463+
"""
464+
465+
bucket: str
466+
prefix: str
467+
version_id: Optional[str] = None
468+
469+
def to_dict(self) -> Dict:
470+
"""Convert to API-compatible dictionary."""
471+
location = {"bucket": self.bucket, "prefix": self.prefix}
472+
if self.version_id:
473+
location["versionId"] = self.version_id
474+
return location
475+
476+
477+
@dataclass
478+
class ResourceLocation:
479+
"""Location of a resource. Currently supports S3.
480+
481+
Attributes:
482+
s3: S3 location of the resource
483+
"""
484+
485+
s3: Optional[EnterprisePolicyS3Location] = None
486+
487+
def to_dict(self) -> Dict:
488+
"""Convert to API-compatible dictionary."""
489+
if self.s3:
490+
return {"s3": self.s3.to_dict()}
491+
raise ValueError("ResourceLocation must have one location type set")
492+
493+
494+
@dataclass
495+
class EnterprisePolicy:
496+
"""Browser enterprise policy.
497+
498+
Attributes:
499+
location: Location of the enterprise policy file
500+
type: "MANAGED" for CreateBrowser or "RECOMMENDED" for StartBrowserSession
501+
"""
502+
503+
location: ResourceLocation
504+
type: str
505+
506+
def __post_init__(self):
507+
"""Validate enterprise policy type."""
508+
if self.type not in ["MANAGED", "RECOMMENDED"]:
509+
raise ValueError(f"type must be 'MANAGED' or 'RECOMMENDED', got '{self.type}'")
510+
511+
def to_dict(self) -> Dict:
512+
"""Convert to API-compatible dictionary."""
513+
return {
514+
"location": self.location.to_dict(),
515+
"type": self.type,
516+
}
517+
518+
519+
@dataclass
520+
class SecretsManagerLocation:
521+
"""Secrets Manager location for a certificate.
522+
523+
Attributes:
524+
secret_arn: ARN of the Secrets Manager secret containing the certificate
525+
"""
526+
527+
secret_arn: str
528+
529+
def to_dict(self) -> Dict:
530+
"""Convert to API-compatible dictionary."""
531+
return {"secretArn": self.secret_arn}
532+
533+
534+
@dataclass
535+
class CertificateLocation:
536+
"""Location from which to retrieve a certificate.
537+
538+
Attributes:
539+
secrets_manager: Secrets Manager location containing the certificate
540+
"""
541+
542+
secrets_manager: SecretsManagerLocation
543+
544+
def to_dict(self) -> Dict:
545+
"""Convert to API-compatible dictionary."""
546+
return {"secretsManager": self.secrets_manager.to_dict()}
547+
548+
549+
@dataclass
550+
class Certificate:
551+
"""Root CA certificate for browser or code interpreter.
552+
553+
Attributes:
554+
location: Location of the certificate
555+
"""
556+
557+
location: CertificateLocation
558+
559+
def to_dict(self) -> Dict:
560+
"""Convert to API-compatible dictionary."""
561+
return {"location": self.location.to_dict()}
562+
563+
@classmethod
564+
def from_secret_arn(cls, secret_arn: str) -> "Certificate":
565+
"""Create a Certificate from a Secrets Manager ARN.
566+
567+
Args:
568+
secret_arn: ARN of the secret containing the certificate
569+
"""
570+
return cls(location=CertificateLocation(secrets_manager=SecretsManagerLocation(secret_arn=secret_arn)))
571+
572+
455573
def create_browser_config(
456574
name: str,
457575
execution_role_arn: str,

0 commit comments

Comments
 (0)