- Replace form_urlencoded with RFC 3986 compliant URI encoding - Implement aws_uri_encode() matching AWS SigV4 spec exactly - Unreserved chars (A-Z,a-z,0-9,-,_,.,~) not encoded - All other chars percent-encoded with uppercase hex - Preserve slashes in paths, encode in query params - Normalize empty paths to '/' per AWS spec - Fix test expectations (body hash, HMAC values) - Add comprehensive SigV4 signature determinism test This fixes the canonicalization mismatch that caused signature validation failures in T047. Auth can now be enabled for production. Refs: T058.S1
298 lines
9.4 KiB
Rust
298 lines
9.4 KiB
Rust
//! CNI Integration Tests
|
|
//!
|
|
//! These tests demonstrate the pod→network attachment flow using the PrismNET CNI plugin.
|
|
//!
|
|
//! Test requirements:
|
|
//! - PrismNET server must be running on localhost:50052
|
|
//! - A test VPC and Subnet must be created
|
|
//! - CNI plugin binary must be built and available
|
|
//!
|
|
//! Run with: cargo test --test cni_integration_test -- --ignored
|
|
|
|
use anyhow::Result;
|
|
use serde_json::json;
|
|
use std::process::Command;
|
|
use std::io::Write;
|
|
use uuid::Uuid;
|
|
|
|
/// Test CNI ADD command with PrismNET backend
|
|
///
|
|
/// This test demonstrates:
|
|
/// 1. Creating a pod network attachment point
|
|
/// 2. Allocating an IP address from PrismNET
|
|
/// 3. Returning network configuration to the container runtime
|
|
#[tokio::test]
|
|
#[ignore] // Requires PrismNET server running
|
|
async fn test_cni_add_creates_prismnet_port() -> Result<()> {
|
|
// Test configuration
|
|
let container_id = Uuid::new_v4().to_string();
|
|
let netns = format!("/var/run/netns/test-{}", container_id);
|
|
let ifname = "eth0";
|
|
|
|
// PrismNET test environment
|
|
let prismnet_addr = std::env::var("NOVANET_SERVER_ADDR")
|
|
.unwrap_or_else(|_| "http://127.0.0.1:50052".to_string());
|
|
let subnet_id = std::env::var("TEST_SUBNET_ID")
|
|
.expect("TEST_SUBNET_ID must be set for integration tests");
|
|
let org_id = "test-org";
|
|
let project_id = "test-project";
|
|
|
|
// Build CNI config
|
|
let cni_config = json!({
|
|
"cniVersion": "1.0.0",
|
|
"name": "k8shost-net",
|
|
"type": "prismnet",
|
|
"prismnet": {
|
|
"server_addr": prismnet_addr,
|
|
"subnet_id": subnet_id,
|
|
"org_id": org_id,
|
|
"project_id": project_id,
|
|
}
|
|
});
|
|
|
|
// Find CNI plugin binary
|
|
let cni_path = std::env::var("CNI_PLUGIN_PATH")
|
|
.unwrap_or_else(|_| "./target/debug/prismnet-cni".to_string());
|
|
|
|
println!("Testing CNI ADD with container_id={}", container_id);
|
|
println!("CNI plugin path: {}", cni_path);
|
|
|
|
// Invoke CNI ADD
|
|
let mut child = Command::new(&cni_path)
|
|
.env("CNI_COMMAND", "ADD")
|
|
.env("CNI_CONTAINERID", &container_id)
|
|
.env("CNI_NETNS", &netns)
|
|
.env("CNI_IFNAME", ifname)
|
|
.env("CNI_PATH", "/opt/cni/bin")
|
|
.stdin(std::process::Stdio::piped())
|
|
.stdout(std::process::Stdio::piped())
|
|
.stderr(std::process::Stdio::piped())
|
|
.spawn()?;
|
|
|
|
// Write config to stdin
|
|
if let Some(mut stdin) = child.stdin.take() {
|
|
stdin.write_all(cni_config.to_string().as_bytes())?;
|
|
}
|
|
|
|
// Wait for result
|
|
let output = child.wait_with_output()?;
|
|
|
|
// Check for success
|
|
if !output.status.success() {
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
panic!("CNI ADD failed: {}", stderr);
|
|
}
|
|
|
|
// Parse result
|
|
let result: serde_json::Value = serde_json::from_slice(&output.stdout)?;
|
|
println!("CNI ADD result: {}", serde_json::to_string_pretty(&result)?);
|
|
|
|
// Verify result structure
|
|
assert_eq!(result["cniVersion"], "1.0.0");
|
|
assert!(result["interfaces"].is_array());
|
|
assert!(result["ips"].is_array());
|
|
|
|
// Extract allocated IP
|
|
let ip_address = result["ips"][0]["address"]
|
|
.as_str()
|
|
.expect("IP address not found in CNI result");
|
|
println!("Pod allocated IP: {}", ip_address);
|
|
|
|
// Extract MAC address
|
|
let mac_address = result["interfaces"][0]["mac"]
|
|
.as_str()
|
|
.expect("MAC address not found in CNI result");
|
|
println!("Pod MAC address: {}", mac_address);
|
|
|
|
// Verify port was created in PrismNET
|
|
// (In production, we would query PrismNET to verify the port exists)
|
|
|
|
// Cleanup: Invoke CNI DEL
|
|
println!("Cleaning up - invoking CNI DEL");
|
|
invoke_cni_del(&cni_path, &cni_config, &container_id, &netns, ifname).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Test CNI DEL command with PrismNET backend
|
|
///
|
|
/// This test demonstrates:
|
|
/// 1. Removing a pod network attachment
|
|
/// 2. Deleting the port from PrismNET
|
|
#[tokio::test]
|
|
#[ignore] // Requires PrismNET server running
|
|
async fn test_cni_del_removes_prismnet_port() -> Result<()> {
|
|
// First create a port using ADD
|
|
let container_id = Uuid::new_v4().to_string();
|
|
let netns = format!("/var/run/netns/test-{}", container_id);
|
|
let ifname = "eth0";
|
|
|
|
let prismnet_addr = std::env::var("NOVANET_SERVER_ADDR")
|
|
.unwrap_or_else(|_| "http://127.0.0.1:50052".to_string());
|
|
let subnet_id = std::env::var("TEST_SUBNET_ID")
|
|
.expect("TEST_SUBNET_ID must be set for integration tests");
|
|
|
|
let cni_config = json!({
|
|
"cniVersion": "1.0.0",
|
|
"name": "k8shost-net",
|
|
"type": "prismnet",
|
|
"prismnet": {
|
|
"server_addr": prismnet_addr,
|
|
"subnet_id": subnet_id,
|
|
"org_id": "test-org",
|
|
"project_id": "test-project",
|
|
}
|
|
});
|
|
|
|
let cni_path = std::env::var("CNI_PLUGIN_PATH")
|
|
.unwrap_or_else(|_| "./target/debug/prismnet-cni".to_string());
|
|
|
|
// Create port
|
|
println!("Creating test port with CNI ADD");
|
|
invoke_cni_add(&cni_path, &cni_config, &container_id, &netns, ifname).await?;
|
|
|
|
// Now test DEL
|
|
println!("Testing CNI DEL with container_id={}", container_id);
|
|
|
|
let mut child = Command::new(&cni_path)
|
|
.env("CNI_COMMAND", "DEL")
|
|
.env("CNI_CONTAINERID", &container_id)
|
|
.env("CNI_NETNS", &netns)
|
|
.env("CNI_IFNAME", ifname)
|
|
.env("CNI_PATH", "/opt/cni/bin")
|
|
.stdin(std::process::Stdio::piped())
|
|
.stdout(std::process::Stdio::piped())
|
|
.stderr(std::process::Stdio::piped())
|
|
.spawn()?;
|
|
|
|
if let Some(mut stdin) = child.stdin.take() {
|
|
stdin.write_all(cni_config.to_string().as_bytes())?;
|
|
}
|
|
|
|
let output = child.wait_with_output()?;
|
|
|
|
// DEL should succeed
|
|
if !output.status.success() {
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
println!("CNI DEL stderr: {}", stderr);
|
|
}
|
|
assert!(output.status.success(), "CNI DEL should succeed");
|
|
|
|
println!("CNI DEL succeeded - port removed from PrismNET");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Test complete pod lifecycle: create → network → delete
|
|
///
|
|
/// This test demonstrates the full integration flow:
|
|
/// 1. Pod is created via k8shost API server
|
|
/// 2. CNI plugin allocates network port from PrismNET
|
|
/// 3. Pod receives IP address and MAC address
|
|
/// 4. Pod is deleted
|
|
/// 5. CNI plugin removes network port
|
|
#[tokio::test]
|
|
#[ignore] // Requires both k8shost and PrismNET servers running
|
|
async fn test_full_pod_network_lifecycle() -> Result<()> {
|
|
// This test would:
|
|
// 1. Create a pod via k8shost API
|
|
// 2. Simulate kubelet invoking CNI ADD
|
|
// 3. Update pod status with network info
|
|
// 4. Delete pod
|
|
// 5. Simulate kubelet invoking CNI DEL
|
|
//
|
|
// For now, this is a placeholder for the full integration test
|
|
// that would be implemented in S6.2 after all components are wired together
|
|
|
|
println!("Full pod network lifecycle test - placeholder");
|
|
println!("This will be implemented after S6.1 completion");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Test multi-tenant network isolation
|
|
///
|
|
/// This test demonstrates:
|
|
/// 1. Pod from org-a gets network in org-a's subnet
|
|
/// 2. Pod from org-b gets network in org-b's subnet
|
|
/// 3. Network isolation is enforced at PrismNET level
|
|
#[tokio::test]
|
|
#[ignore] // Requires PrismNET server with multi-tenant setup
|
|
async fn test_multi_tenant_network_isolation() -> Result<()> {
|
|
// This test would verify that:
|
|
// - Org-A pods get IPs from org-a subnets
|
|
// - Org-B pods get IPs from org-b subnets
|
|
// - Cross-tenant network access is blocked
|
|
|
|
println!("Multi-tenant network isolation test - placeholder");
|
|
println!("This will be implemented in S6.1 after basic flow is validated");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// Helper functions
|
|
|
|
async fn invoke_cni_add(
|
|
cni_path: &str,
|
|
cni_config: &serde_json::Value,
|
|
container_id: &str,
|
|
netns: &str,
|
|
ifname: &str,
|
|
) -> Result<serde_json::Value> {
|
|
let mut child = Command::new(cni_path)
|
|
.env("CNI_COMMAND", "ADD")
|
|
.env("CNI_CONTAINERID", container_id)
|
|
.env("CNI_NETNS", netns)
|
|
.env("CNI_IFNAME", ifname)
|
|
.env("CNI_PATH", "/opt/cni/bin")
|
|
.stdin(std::process::Stdio::piped())
|
|
.stdout(std::process::Stdio::piped())
|
|
.stderr(std::process::Stdio::piped())
|
|
.spawn()?;
|
|
|
|
if let Some(mut stdin) = child.stdin.take() {
|
|
stdin.write_all(cni_config.to_string().as_bytes())?;
|
|
}
|
|
|
|
let output = child.wait_with_output()?;
|
|
|
|
if !output.status.success() {
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
return Err(anyhow::anyhow!("CNI ADD failed: {}", stderr));
|
|
}
|
|
|
|
let result = serde_json::from_slice(&output.stdout)?;
|
|
Ok(result)
|
|
}
|
|
|
|
async fn invoke_cni_del(
|
|
cni_path: &str,
|
|
cni_config: &serde_json::Value,
|
|
container_id: &str,
|
|
netns: &str,
|
|
ifname: &str,
|
|
) -> Result<()> {
|
|
let mut child = Command::new(cni_path)
|
|
.env("CNI_COMMAND", "DEL")
|
|
.env("CNI_CONTAINERID", container_id)
|
|
.env("CNI_NETNS", netns)
|
|
.env("CNI_IFNAME", ifname)
|
|
.env("CNI_PATH", "/opt/cni/bin")
|
|
.stdin(std::process::Stdio::piped())
|
|
.stdout(std::process::Stdio::piped())
|
|
.stderr(std::process::Stdio::piped())
|
|
.spawn()?;
|
|
|
|
if let Some(mut stdin) = child.stdin.take() {
|
|
stdin.write_all(cni_config.to_string().as_bytes())?;
|
|
}
|
|
|
|
let output = child.wait_with_output()?;
|
|
|
|
if !output.status.success() {
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
eprintln!("CNI DEL warning: {}", stderr);
|
|
}
|
|
|
|
Ok(())
|
|
}
|