Files
rustfs/crates/trusted-proxies/src/cloud/metadata/gcp.rs
T
houseme 9059a9c68d refactor(logging): standardize concurrency and trusted proxy events (#3417)
* refactor(logging): standardize concurrency and proxy events

* chore(logging): extend guardrails for concurrency and proxies

* feat(skill): add rustfs logging governance skill
2026-06-14 01:00:26 +08:00

446 lines
17 KiB
Rust

// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Google Cloud Platform (GCP) metadata fetching implementation for identifying trusted proxy ranges.
use async_trait::async_trait;
use reqwest::Client;
use serde::Deserialize;
use std::str::FromStr;
use std::time::Duration;
use tracing::{debug, info, warn};
use crate::AppError;
use crate::CloudMetadataFetcher;
/// Fetcher for GCP-specific metadata.
#[derive(Debug, Clone)]
pub struct GcpMetadataFetcher {
client: Client,
metadata_endpoint: String,
}
impl GcpMetadataFetcher {
/// Creates a new `GcpMetadataFetcher`.
pub fn new(timeout: Duration) -> Self {
let client = Client::builder().timeout(timeout).build().unwrap_or_else(|_| Client::new());
Self {
client,
metadata_endpoint: "http://metadata.google.internal".to_string(),
}
}
/// Retrieves metadata from the GCP Compute Engine metadata server.
async fn get_metadata(&self, path: &str) -> Result<String, AppError> {
let url = format!("{}/computeMetadata/v1/{}", self.metadata_endpoint, path);
debug!(
event = "trusted_proxies.cloud_metadata",
component = "trusted_proxies",
subsystem = "gcp_metadata",
provider = "gcp",
operation = "metadata_request",
path = %path,
"trusted proxy cloud metadata request started"
);
match self.client.get(&url).header("Metadata-Flavor", "Google").send().await {
Ok(response) => {
if response.status().is_success() {
let text = response
.text()
.await
.map_err(|e| AppError::cloud(format!("Failed to read GCP metadata response: {}", e)))?;
Ok(text)
} else {
debug!(
event = "trusted_proxies.cloud_metadata",
component = "trusted_proxies",
subsystem = "gcp_metadata",
provider = "gcp",
operation = "metadata_request",
path = %path,
result = "http_error",
status = %response.status(),
"trusted proxy cloud metadata request failed"
);
Err(AppError::cloud(format!("GCP metadata API returned status: {}", response.status())))
}
}
Err(e) => {
debug!(
event = "trusted_proxies.cloud_metadata",
component = "trusted_proxies",
subsystem = "gcp_metadata",
provider = "gcp",
operation = "metadata_request",
path = %path,
result = "request_failed",
error = %e,
"trusted proxy cloud metadata request failed"
);
Err(AppError::cloud(format!("GCP metadata request failed: {}", e)))
}
}
}
/// Converts a dotted-decimal subnet mask to a CIDR prefix length.
fn subnet_mask_to_prefix_length(mask: &str) -> Result<u8, AppError> {
let parts: Vec<&str> = mask.split('.').collect();
if parts.len() != 4 {
return Err(AppError::cloud(format!("Invalid subnet mask format: {}", mask)));
}
let mut prefix_length = 0;
for part in parts {
let octet: u8 = part
.parse()
.map_err(|_| AppError::cloud(format!("Invalid octet in subnet mask: {}", part)))?;
let mut remaining = octet;
while remaining > 0 {
if remaining & 0x80 == 0x80 {
prefix_length += 1;
remaining <<= 1;
} else {
break;
}
}
if remaining != 0 {
return Err(AppError::cloud("Non-contiguous subnet mask detected"));
}
}
Ok(prefix_length)
}
}
#[async_trait]
impl CloudMetadataFetcher for GcpMetadataFetcher {
fn provider_name(&self) -> &str {
"gcp"
}
async fn fetch_network_cidrs(&self) -> Result<Vec<ipnetwork::IpNetwork>, AppError> {
// Attempt to list network interfaces from GCP metadata.
match self.get_metadata("instance/network-interfaces/").await {
Ok(interfaces_metadata) => {
let interface_indices: Vec<usize> = interfaces_metadata
.lines()
.filter_map(|line| {
let line = line.trim().trim_end_matches('/');
if line.chars().all(|c| c.is_ascii_digit()) {
line.parse().ok()
} else {
None
}
})
.collect();
if interface_indices.is_empty() {
warn!(
event = "trusted_proxies.cloud_metadata",
component = "trusted_proxies",
subsystem = "gcp_metadata",
provider = "gcp",
operation = "network_cidrs",
source = "metadata",
result = "fallback",
reason = "no_interfaces",
"trusted proxy cloud metadata fallback applied"
);
return Self::default_gcp_network_ranges();
}
let mut cidrs = Vec::new();
for index in interface_indices {
// Try to get IP and subnet mask for each interface.
let ip_path = format!("instance/network-interfaces/{}/ip", index);
let mask_path = format!("instance/network-interfaces/{}/subnetmask", index);
match tokio::try_join!(self.get_metadata(&ip_path), self.get_metadata(&mask_path)) {
Ok((ip, mask)) => {
let ip = ip.trim();
let mask = mask.trim();
if let (Ok(ip_addr), Ok(prefix_len)) =
(std::net::Ipv4Addr::from_str(ip), Self::subnet_mask_to_prefix_length(mask))
{
let cidr_str = format!("{}/{}", ip_addr, prefix_len);
if let Ok(network) = ipnetwork::IpNetwork::from_str(&cidr_str) {
cidrs.push(network);
}
}
}
Err(e) => {
debug!(
event = "trusted_proxies.cloud_metadata",
component = "trusted_proxies",
subsystem = "gcp_metadata",
provider = "gcp",
operation = "network_interface",
interface_index = index,
result = "request_failed",
error = %e,
"trusted proxy cloud metadata request failed"
);
}
}
}
if cidrs.is_empty() {
warn!(
event = "trusted_proxies.cloud_metadata",
component = "trusted_proxies",
subsystem = "gcp_metadata",
provider = "gcp",
operation = "network_cidrs",
source = "metadata",
result = "fallback",
reason = "empty_metadata",
"trusted proxy cloud metadata fallback applied"
);
Self::default_gcp_network_ranges()
} else {
info!(
event = "trusted_proxies.cloud_metadata",
component = "trusted_proxies",
subsystem = "gcp_metadata",
provider = "gcp",
operation = "network_cidrs",
source = "metadata",
result = "loaded",
range_count = cidrs.len(),
"trusted proxy cloud metadata loaded"
);
Ok(cidrs)
}
}
Err(e) => {
warn!(
event = "trusted_proxies.cloud_metadata",
component = "trusted_proxies",
subsystem = "gcp_metadata",
provider = "gcp",
operation = "network_cidrs",
source = "metadata",
result = "fallback",
error = %e,
"trusted proxy cloud metadata fallback applied"
);
Self::default_gcp_network_ranges()
}
}
}
async fn fetch_public_ip_ranges(&self) -> Result<Vec<ipnetwork::IpNetwork>, AppError> {
self.fetch_gcp_ip_ranges().await
}
}
impl GcpMetadataFetcher {
/// Fetches GCP public IP ranges from the official Google source.
async fn fetch_gcp_ip_ranges(&self) -> Result<Vec<ipnetwork::IpNetwork>, AppError> {
let url = "https://www.gstatic.com/ipranges/cloud.json";
#[derive(Debug, Deserialize)]
struct GcpIpRanges {
prefixes: Vec<GcpPrefix>,
}
#[derive(Debug, Deserialize)]
struct GcpPrefix {
ipv4_prefix: Option<String>,
}
debug!(
event = "trusted_proxies.cloud_metadata",
component = "trusted_proxies",
subsystem = "gcp_metadata",
provider = "gcp",
operation = "public_ip_ranges",
source = %url,
"trusted proxy cloud metadata request started"
);
match self.client.get(url).timeout(Duration::from_secs(10)).send().await {
Ok(response) => {
if response.status().is_success() {
let ip_ranges: GcpIpRanges = response
.json()
.await
.map_err(|e| AppError::cloud(format!("Failed to parse GCP IP ranges JSON: {}", e)))?;
let mut networks = Vec::new();
for prefix in ip_ranges.prefixes {
if let Some(ipv4_prefix) = prefix.ipv4_prefix
&& let Ok(network) = ipnetwork::IpNetwork::from_str(&ipv4_prefix)
{
networks.push(network);
}
}
info!(
event = "trusted_proxies.cloud_metadata",
component = "trusted_proxies",
subsystem = "gcp_metadata",
provider = "gcp",
operation = "public_ip_ranges",
source = "api",
result = "loaded",
range_count = networks.len(),
"trusted proxy cloud metadata loaded"
);
Ok(networks)
} else {
debug!(
event = "trusted_proxies.cloud_metadata",
component = "trusted_proxies",
subsystem = "gcp_metadata",
provider = "gcp",
operation = "public_ip_ranges",
source = %url,
result = "http_error",
status = %response.status(),
"trusted proxy cloud metadata request failed"
);
Self::default_gcp_ip_ranges()
}
}
Err(e) => {
debug!(
event = "trusted_proxies.cloud_metadata",
component = "trusted_proxies",
subsystem = "gcp_metadata",
provider = "gcp",
operation = "public_ip_ranges",
source = %url,
result = "request_failed",
error = %e,
"trusted proxy cloud metadata request failed"
);
Self::default_gcp_ip_ranges()
}
}
}
/// Returns a set of default GCP public IP ranges as a fallback.
fn default_gcp_ip_ranges() -> Result<Vec<ipnetwork::IpNetwork>, AppError> {
let ranges = vec![
"8.34.208.0/20",
"8.35.192.0/20",
"8.35.208.0/20",
"23.236.48.0/20",
"23.251.128.0/19",
"34.0.0.0/15",
"34.2.0.0/16",
"34.3.0.0/23",
"34.4.0.0/14",
"34.8.0.0/13",
"34.16.0.0/12",
"34.32.0.0/11",
"34.64.0.0/10",
"34.128.0.0/10",
"35.184.0.0/13",
"35.192.0.0/14",
"35.196.0.0/15",
"35.198.0.0/16",
"35.200.0.0/13",
"35.208.0.0/12",
"35.224.0.0/12",
"35.240.0.0/13",
"104.154.0.0/15",
"104.196.0.0/14",
"107.167.160.0/19",
"107.178.192.0/18",
"108.59.80.0/20",
"108.170.192.0/18",
"108.177.0.0/17",
"130.211.0.0/16",
"136.112.0.0/12",
"142.250.0.0/15",
"146.148.0.0/17",
"172.217.0.0/16",
"172.253.0.0/16",
"173.194.0.0/16",
"192.178.0.0/15",
"209.85.128.0/17",
"216.58.192.0/19",
"216.239.32.0/19",
"2001:4860::/32",
"2404:6800::/32",
"2600:1900::/28",
"2607:f8b0::/32",
"2620:15c::/36",
"2800:3f0::/32",
"2a00:1450::/32",
"2c0f:fb50::/32",
];
let networks: Result<Vec<_>, _> = ranges.into_iter().map(ipnetwork::IpNetwork::from_str).collect();
match networks {
Ok(networks) => {
debug!(
event = "trusted_proxies.cloud_metadata",
component = "trusted_proxies",
subsystem = "gcp_metadata",
provider = "gcp",
operation = "public_ip_ranges",
result = "fallback",
source = "default_ranges",
range_count = networks.len(),
"trusted proxy cloud metadata fallback applied"
);
Ok(networks)
}
Err(e) => Err(AppError::cloud(format!("Failed to parse default GCP ranges: {}", e))),
}
}
/// Returns a set of default GCP VPC ranges as a fallback.
fn default_gcp_network_ranges() -> Result<Vec<ipnetwork::IpNetwork>, AppError> {
let ranges = vec![
"10.0.0.0/8", // Large VPCs
"172.16.0.0/12", // Medium VPCs
"192.168.0.0/16", // Small VPCs
"100.64.0.0/10", // GCP reserved range
];
let networks: Result<Vec<_>, _> = ranges.into_iter().map(ipnetwork::IpNetwork::from_str).collect();
match networks {
Ok(networks) => {
debug!(
event = "trusted_proxies.cloud_metadata",
component = "trusted_proxies",
subsystem = "gcp_metadata",
provider = "gcp",
operation = "network_cidrs",
result = "fallback",
source = "default_ranges",
range_count = networks.len(),
"trusted proxy cloud metadata fallback applied"
);
Ok(networks)
}
Err(e) => Err(AppError::cloud(format!("Failed to parse default GCP network ranges: {}", e))),
}
}
}