Validate L7 policy configuration in the loaded OPA data. Returns a list of errors and warnings. Errors should prevent sandbox startup; warnings are logged but don't block.
(data_json: &serde_json::Value)
| 937 | /// Returns a list of errors and warnings. Errors should prevent sandbox startup; |
| 938 | /// warnings are logged but don't block. |
| 939 | pub fn validate_l7_policies(data_json: &serde_json::Value) -> (Vec<String>, Vec<String>) { |
| 940 | let mut errors = Vec::new(); |
| 941 | let mut warnings = Vec::new(); |
| 942 | |
| 943 | let Some(policies) = data_json |
| 944 | .get("network_policies") |
| 945 | .and_then(|v| v.as_object()) |
| 946 | else { |
| 947 | return (errors, warnings); |
| 948 | }; |
| 949 | |
| 950 | for (name, policy) in policies { |
| 951 | let Some(endpoints) = policy.get("endpoints").and_then(|v| v.as_array()) else { |
| 952 | continue; |
| 953 | }; |
| 954 | |
| 955 | for (i, ep) in endpoints.iter().enumerate() { |
| 956 | let protocol = ep.get("protocol").and_then(|v| v.as_str()).unwrap_or(""); |
| 957 | let l7_protocol = L7Protocol::parse(protocol); |
| 958 | let jsonrpc_family = l7_protocol.is_some_and(L7Protocol::is_jsonrpc_family); |
| 959 | let tls = ep.get("tls").and_then(|v| v.as_str()).unwrap_or(""); |
| 960 | let enforcement = ep.get("enforcement").and_then(|v| v.as_str()).unwrap_or(""); |
| 961 | let access = ep.get("access").and_then(|v| v.as_str()).unwrap_or(""); |
| 962 | let has_rules = ep |
| 963 | .get("rules") |
| 964 | .and_then(|v| v.as_array()) |
| 965 | .is_some_and(|a| !a.is_empty()); |
| 966 | let websocket_has_graphql_policy = |
| 967 | protocol == "websocket" && json_endpoint_has_graphql_policy(ep); |
| 968 | let host = ep.get("host").and_then(|v| v.as_str()).unwrap_or(""); |
| 969 | let endpoint_path = ep.get("path").and_then(|v| v.as_str()).unwrap_or(""); |
| 970 | |
| 971 | // Read ports from either "ports" array or scalar "port". |
| 972 | let ports: Vec<u64> = ep.get("ports").and_then(|v| v.as_array()).map_or_else( |
| 973 | || { |
| 974 | ep.get("port") |
| 975 | .and_then(serde_json::Value::as_u64) |
| 976 | .filter(|p| *p > 0) |
| 977 | .into_iter() |
| 978 | .collect() |
| 979 | }, |
| 980 | |arr| arr.iter().filter_map(serde_json::Value::as_u64).collect(), |
| 981 | ); |
| 982 | let loc = format!("{name}.endpoints[{i}]"); |
| 983 | |
| 984 | if protocol == "mcp" { |
| 985 | if host.trim().is_empty() { |
| 986 | errors.push(format!( |
| 987 | "{loc}: protocol mcp requires host; protocol alone is not a wildcard endpoint" |
| 988 | )); |
| 989 | } |
| 990 | if !ports.iter().any(|port| *port > 0) { |
| 991 | errors.push(format!( |
| 992 | "{loc}: protocol mcp requires port or ports; protocol alone is not a wildcard endpoint" |
| 993 | )); |
| 994 | } |
| 995 | } |
| 996 |