@@ -138,6 +138,12 @@ struct NetworkEndpointDef {
138138 graphql_persisted_queries : BTreeMap < String , GraphqlOperationDef > ,
139139 #[ serde( default , skip_serializing_if = "is_zero_u32" ) ]
140140 graphql_max_body_bytes : u32 ,
141+ #[ serde( default , skip_serializing_if = "String::is_empty" ) ]
142+ credential_signing : String ,
143+ #[ serde( default , skip_serializing_if = "String::is_empty" ) ]
144+ signing_service : String ,
145+ #[ serde( default , skip_serializing_if = "String::is_empty" ) ]
146+ signing_region : String ,
141147}
142148
143149// Signature dictated by serde's `skip_serializing_if`, which requires `&T`.
@@ -350,6 +356,9 @@ fn to_proto(raw: PolicyFile) -> SandboxPolicy {
350356 } )
351357 . collect ( ) ,
352358 graphql_max_body_bytes : e. graphql_max_body_bytes ,
359+ credential_signing : e. credential_signing ,
360+ signing_service : e. signing_service ,
361+ signing_region : e. signing_region ,
353362 }
354363 } )
355364 . collect ( ) ,
@@ -515,6 +524,9 @@ fn from_proto(policy: &SandboxPolicy) -> PolicyFile {
515524 } )
516525 . collect ( ) ,
517526 graphql_max_body_bytes : e. graphql_max_body_bytes ,
527+ credential_signing : e. credential_signing . clone ( ) ,
528+ signing_service : e. signing_service . clone ( ) ,
529+ signing_region : e. signing_region . clone ( ) ,
518530 }
519531 } )
520532 . collect ( ) ,
@@ -697,6 +709,16 @@ pub enum PolicyViolation {
697709 TooManyPaths { count : usize } ,
698710 /// A network endpoint uses a TLD wildcard (e.g. `*.com`).
699711 TldWildcard { policy_name : String , host : String } ,
712+ /// `credential_signing` is set but `signing_service` is missing.
713+ MissingSigningService { policy_name : String , host : String } ,
714+ /// `credential_signing` has an unrecognized value.
715+ UnknownCredentialSigning {
716+ policy_name : String ,
717+ host : String ,
718+ value : String ,
719+ } ,
720+ /// `credential_signing` and `request_body_credential_rewrite` are both set.
721+ CredentialSigningWithBodyRewrite { policy_name : String , host : String } ,
700722}
701723
702724impl fmt:: Display for PolicyViolation {
@@ -733,6 +755,31 @@ impl fmt::Display for PolicyViolation {
733755 use subdomain wildcards like '*.example.com' instead"
734756 )
735757 }
758+ Self :: MissingSigningService { policy_name, host } => {
759+ write ! (
760+ f,
761+ "network policy '{policy_name}': endpoint '{host}' has credential_signing \
762+ set but signing_service is empty"
763+ )
764+ }
765+ Self :: UnknownCredentialSigning {
766+ policy_name,
767+ host,
768+ value,
769+ } => {
770+ write ! (
771+ f,
772+ "network policy '{policy_name}': endpoint '{host}' has unrecognized \
773+ credential_signing value '{value}' (expected sigv4, sigv4:body, or sigv4:no_body)"
774+ )
775+ }
776+ Self :: CredentialSigningWithBodyRewrite { policy_name, host } => {
777+ write ! (
778+ f,
779+ "network policy '{policy_name}': endpoint '{host}' has both credential_signing \
780+ and request_body_credential_rewrite set; these options are mutually exclusive"
781+ )
782+ }
736783 }
737784 }
738785}
@@ -837,6 +884,30 @@ pub fn validate_sandbox_policy(
837884 } ) ;
838885 }
839886 }
887+ if !ep. credential_signing . is_empty ( )
888+ && !matches ! (
889+ ep. credential_signing. as_str( ) ,
890+ "sigv4" | "sigv4:body" | "sigv4:no_body"
891+ )
892+ {
893+ violations. push ( PolicyViolation :: UnknownCredentialSigning {
894+ policy_name : name. clone ( ) ,
895+ host : ep. host . clone ( ) ,
896+ value : ep. credential_signing . clone ( ) ,
897+ } ) ;
898+ }
899+ if !ep. credential_signing . is_empty ( ) && ep. signing_service . is_empty ( ) {
900+ violations. push ( PolicyViolation :: MissingSigningService {
901+ policy_name : name. clone ( ) ,
902+ host : ep. host . clone ( ) ,
903+ } ) ;
904+ }
905+ if !ep. credential_signing . is_empty ( ) && ep. request_body_credential_rewrite {
906+ violations. push ( PolicyViolation :: CredentialSigningWithBodyRewrite {
907+ policy_name : name. clone ( ) ,
908+ host : ep. host . clone ( ) ,
909+ } ) ;
910+ }
840911 }
841912 }
842913
@@ -1380,6 +1451,167 @@ network_policies:
13801451 assert ! ( validate_sandbox_policy( & policy) . is_ok( ) ) ;
13811452 }
13821453
1454+ #[ test]
1455+ fn validate_rejects_credential_signing_without_signing_service ( ) {
1456+ let mut policy = restrictive_default_policy ( ) ;
1457+ policy. network_policies . insert (
1458+ "aws" . into ( ) ,
1459+ NetworkPolicyRule {
1460+ name : "bedrock" . into ( ) ,
1461+ endpoints : vec ! [ NetworkEndpoint {
1462+ host: "bedrock-runtime.us-east-1.amazonaws.com" . into( ) ,
1463+ port: 443 ,
1464+ credential_signing: "sigv4" . into( ) ,
1465+ signing_service: String :: new( ) ,
1466+ ..Default :: default ( )
1467+ } ] ,
1468+ ..Default :: default ( )
1469+ } ,
1470+ ) ;
1471+ let violations = validate_sandbox_policy ( & policy) . unwrap_err ( ) ;
1472+ assert ! (
1473+ violations
1474+ . iter( )
1475+ . any( |v| matches!( v, PolicyViolation :: MissingSigningService { .. } ) )
1476+ ) ;
1477+ }
1478+
1479+ #[ test]
1480+ fn validate_accepts_credential_signing_with_signing_service ( ) {
1481+ let mut policy = restrictive_default_policy ( ) ;
1482+ policy. network_policies . insert (
1483+ "aws" . into ( ) ,
1484+ NetworkPolicyRule {
1485+ name : "bedrock" . into ( ) ,
1486+ endpoints : vec ! [ NetworkEndpoint {
1487+ host: "bedrock-runtime.us-east-1.amazonaws.com" . into( ) ,
1488+ port: 443 ,
1489+ credential_signing: "sigv4" . into( ) ,
1490+ signing_service: "bedrock" . into( ) ,
1491+ ..Default :: default ( )
1492+ } ] ,
1493+ ..Default :: default ( )
1494+ } ,
1495+ ) ;
1496+ assert ! ( validate_sandbox_policy( & policy) . is_ok( ) ) ;
1497+ }
1498+
1499+ #[ test]
1500+ fn validate_accepts_sigv4_body_with_signing_service ( ) {
1501+ let mut policy = restrictive_default_policy ( ) ;
1502+ policy. network_policies . insert (
1503+ "aws" . into ( ) ,
1504+ NetworkPolicyRule {
1505+ name : "bedrock" . into ( ) ,
1506+ endpoints : vec ! [ NetworkEndpoint {
1507+ host: "bedrock-runtime.us-east-1.amazonaws.com" . into( ) ,
1508+ port: 443 ,
1509+ credential_signing: "sigv4:body" . into( ) ,
1510+ signing_service: "bedrock" . into( ) ,
1511+ ..Default :: default ( )
1512+ } ] ,
1513+ ..Default :: default ( )
1514+ } ,
1515+ ) ;
1516+ assert ! ( validate_sandbox_policy( & policy) . is_ok( ) ) ;
1517+ }
1518+
1519+ #[ test]
1520+ fn validate_accepts_sigv4_no_body_with_signing_service ( ) {
1521+ let mut policy = restrictive_default_policy ( ) ;
1522+ policy. network_policies . insert (
1523+ "aws" . into ( ) ,
1524+ NetworkPolicyRule {
1525+ name : "s3" . into ( ) ,
1526+ endpoints : vec ! [ NetworkEndpoint {
1527+ host: "s3.us-east-1.amazonaws.com" . into( ) ,
1528+ port: 443 ,
1529+ credential_signing: "sigv4:no_body" . into( ) ,
1530+ signing_service: "s3" . into( ) ,
1531+ ..Default :: default ( )
1532+ } ] ,
1533+ ..Default :: default ( )
1534+ } ,
1535+ ) ;
1536+ assert ! ( validate_sandbox_policy( & policy) . is_ok( ) ) ;
1537+ }
1538+
1539+ #[ test]
1540+ fn validate_rejects_sigv4_no_body_without_signing_service ( ) {
1541+ let mut policy = restrictive_default_policy ( ) ;
1542+ policy. network_policies . insert (
1543+ "aws" . into ( ) ,
1544+ NetworkPolicyRule {
1545+ name : "s3" . into ( ) ,
1546+ endpoints : vec ! [ NetworkEndpoint {
1547+ host: "s3.us-east-1.amazonaws.com" . into( ) ,
1548+ port: 443 ,
1549+ credential_signing: "sigv4:no_body" . into( ) ,
1550+ signing_service: String :: new( ) ,
1551+ ..Default :: default ( )
1552+ } ] ,
1553+ ..Default :: default ( )
1554+ } ,
1555+ ) ;
1556+ let violations = validate_sandbox_policy ( & policy) . unwrap_err ( ) ;
1557+ assert ! (
1558+ violations
1559+ . iter( )
1560+ . any( |v| matches!( v, PolicyViolation :: MissingSigningService { .. } ) )
1561+ ) ;
1562+ }
1563+
1564+ #[ test]
1565+ fn validate_rejects_unknown_credential_signing ( ) {
1566+ let mut policy = restrictive_default_policy ( ) ;
1567+ policy. network_policies . insert (
1568+ "aws" . into ( ) ,
1569+ NetworkPolicyRule {
1570+ name : "test" . into ( ) ,
1571+ endpoints : vec ! [ NetworkEndpoint {
1572+ host: "example.amazonaws.com" . into( ) ,
1573+ port: 443 ,
1574+ credential_signing: "sigv4_typo" . into( ) ,
1575+ signing_service: "bedrock" . into( ) ,
1576+ ..Default :: default ( )
1577+ } ] ,
1578+ ..Default :: default ( )
1579+ } ,
1580+ ) ;
1581+ let violations = validate_sandbox_policy ( & policy) . unwrap_err ( ) ;
1582+ assert ! (
1583+ violations
1584+ . iter( )
1585+ . any( |v| matches!( v, PolicyViolation :: UnknownCredentialSigning { .. } ) )
1586+ ) ;
1587+ }
1588+
1589+ #[ test]
1590+ fn validate_rejects_credential_signing_with_body_rewrite ( ) {
1591+ let mut policy = restrictive_default_policy ( ) ;
1592+ policy. network_policies . insert (
1593+ "aws" . into ( ) ,
1594+ NetworkPolicyRule {
1595+ name : "bedrock" . into ( ) ,
1596+ endpoints : vec ! [ NetworkEndpoint {
1597+ host: "bedrock-runtime.us-east-1.amazonaws.com" . into( ) ,
1598+ port: 443 ,
1599+ credential_signing: "sigv4" . into( ) ,
1600+ signing_service: "bedrock" . into( ) ,
1601+ request_body_credential_rewrite: true ,
1602+ ..Default :: default ( )
1603+ } ] ,
1604+ ..Default :: default ( )
1605+ } ,
1606+ ) ;
1607+ let violations = validate_sandbox_policy ( & policy) . unwrap_err ( ) ;
1608+ assert ! (
1609+ violations
1610+ . iter( )
1611+ . any( |v| matches!( v, PolicyViolation :: CredentialSigningWithBodyRewrite { .. } ) )
1612+ ) ;
1613+ }
1614+
13831615 #[ test]
13841616 fn normalize_path_collapses_separators ( ) {
13851617 assert_eq ! ( normalize_path( "/usr//lib" ) , "/usr/lib" ) ;
0 commit comments