feat: Support specifying ports in Tenant network config
This commit is contained in:
parent
acfcc77040
commit
b5ed445d5f
@ -6,9 +6,14 @@ use crate::{
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use derive_new::new;
|
||||
use k8s_openapi::api::{
|
||||
core::v1::{Namespace, ResourceQuota},
|
||||
networking::v1::{NetworkPolicy, NetworkPolicyEgressRule, NetworkPolicyIngressRule},
|
||||
use k8s_openapi::{
|
||||
api::{
|
||||
core::v1::{Namespace, ResourceQuota},
|
||||
networking::v1::{
|
||||
NetworkPolicy, NetworkPolicyEgressRule, NetworkPolicyIngressRule, NetworkPolicyPort,
|
||||
},
|
||||
},
|
||||
apimachinery::pkg::util::intstr::IntOrString,
|
||||
};
|
||||
use kube::Resource;
|
||||
use log::{debug, info, warn};
|
||||
@ -204,15 +209,18 @@ impl K8sTenantManager {
|
||||
.additional_allowed_cidr_ingress
|
||||
.iter()
|
||||
.try_for_each(|c| -> Result<(), ExecutorError> {
|
||||
let cidr_list: Vec<serde_json::Value> =
|
||||
c.0.iter()
|
||||
.map(|ci| {
|
||||
json!({
|
||||
"ipBlock": {
|
||||
"cidr": ci.to_string(),
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
let rule = serde_json::from_value::<NetworkPolicyIngressRule>(json!({
|
||||
"from": [
|
||||
{
|
||||
"ipBlock": {
|
||||
|
||||
"cidr": c.to_string(),
|
||||
}
|
||||
}
|
||||
]
|
||||
"from": cidr_list
|
||||
}))
|
||||
.map_err(|e| {
|
||||
ExecutorError::ConfigurationError(format!(
|
||||
@ -237,15 +245,39 @@ impl K8sTenantManager {
|
||||
.additional_allowed_cidr_egress
|
||||
.iter()
|
||||
.try_for_each(|c| -> Result<(), ExecutorError> {
|
||||
let rule = serde_json::from_value::<NetworkPolicyEgressRule>(json!({
|
||||
"to": [
|
||||
{
|
||||
"ipBlock": {
|
||||
let cidr_list: Vec<serde_json::Value> =
|
||||
c.0.iter()
|
||||
.map(|ci| {
|
||||
json!({
|
||||
"ipBlock": {
|
||||
"cidr": ci.to_string(),
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
let ports: Option<Vec<NetworkPolicyPort>> =
|
||||
c.1.as_ref().map(|spec| match &spec.data {
|
||||
super::PortSpecData::SinglePort(port) => vec![NetworkPolicyPort {
|
||||
port: Some(IntOrString::Int(port.clone().into())),
|
||||
..Default::default()
|
||||
}],
|
||||
super::PortSpecData::PortRange(start, end) => vec![NetworkPolicyPort {
|
||||
port: Some(IntOrString::Int(start.clone().into())),
|
||||
end_port: Some(end.clone().into()),
|
||||
protocol: None, // Not currently supported by Harmony
|
||||
}],
|
||||
|
||||
"cidr": c.to_string(),
|
||||
}
|
||||
}
|
||||
]
|
||||
super::PortSpecData::ListOfPorts(items) => items
|
||||
.iter()
|
||||
.map(|i| NetworkPolicyPort {
|
||||
port: Some(IntOrString::Int(i.clone().into())),
|
||||
..Default::default()
|
||||
})
|
||||
.collect(),
|
||||
});
|
||||
let rule = serde_json::from_value::<NetworkPolicyEgressRule>(json!({
|
||||
"to": cidr_list,
|
||||
"ports": ports,
|
||||
}))
|
||||
.map_err(|e| {
|
||||
ExecutorError::ConfigurationError(format!(
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
pub mod k8s;
|
||||
mod manager;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub use manager::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@ -74,8 +76,8 @@ pub struct TenantNetworkPolicy {
|
||||
/// Policy for egress traffic destined for the public internet.
|
||||
pub default_internet_egress: InternetEgressPolicy,
|
||||
|
||||
pub additional_allowed_cidr_ingress: Vec<cidr::Ipv4Cidr>,
|
||||
pub additional_allowed_cidr_egress: Vec<cidr::Ipv4Cidr>,
|
||||
pub additional_allowed_cidr_ingress: Vec<(Vec<cidr::Ipv4Cidr>, Option<PortSpec>)>,
|
||||
pub additional_allowed_cidr_egress: Vec<(Vec<cidr::Ipv4Cidr>, Option<PortSpec>)>,
|
||||
}
|
||||
|
||||
impl Default for TenantNetworkPolicy {
|
||||
@ -102,3 +104,121 @@ pub enum InternetEgressPolicy {
|
||||
/// Deny all outbound traffic to the internet by default.
|
||||
DenyAll,
|
||||
}
|
||||
|
||||
/// Represents a port specification that can be either a single port, a comma-separated list of ports,
|
||||
/// or a range separated by a dash.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct PortSpec {
|
||||
/// The actual representation of the ports as strings for serialization/deserialization purposes.
|
||||
pub data: PortSpecData,
|
||||
}
|
||||
|
||||
impl PortSpec {
|
||||
/// TODO write short rust doc that shows what types of input are supported
|
||||
fn parse_from_str(spec: &str) -> Result<PortSpec, String> {
|
||||
// Check for single port
|
||||
if let Ok(port) = spec.parse::<u16>() {
|
||||
let spec = PortSpecData::SinglePort(port);
|
||||
return Ok(Self { data: spec });
|
||||
}
|
||||
|
||||
if let Some(range) = spec.find('-') {
|
||||
let start_str = &spec[..range];
|
||||
let end_str = &spec[(range + 1)..];
|
||||
|
||||
if let (Ok(start), Ok(end)) = (start_str.parse::<u16>(), end_str.parse::<u16>()) {
|
||||
let spec = PortSpecData::PortRange(start, end);
|
||||
return Ok(Self { data: spec });
|
||||
}
|
||||
}
|
||||
|
||||
let ports: Vec<&str> = spec.split(',').collect();
|
||||
if !ports.is_empty() && ports.iter().all(|p| p.parse::<u16>().is_ok()) {
|
||||
let maybe_ports = ports.iter().try_fold(vec![], |mut list, &p| {
|
||||
if let Ok(p) = p.parse::<u16>() {
|
||||
list.push(p);
|
||||
return Ok(list);
|
||||
}
|
||||
Err(())
|
||||
});
|
||||
|
||||
if let Ok(ports) = maybe_ports {
|
||||
let spec = PortSpecData::ListOfPorts(ports);
|
||||
return Ok(Self { data: spec });
|
||||
}
|
||||
}
|
||||
|
||||
Err(format!("Invalid port spec format {spec}"))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PortSpec {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Self::parse_from_str(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum PortSpecData {
|
||||
SinglePort(u16),
|
||||
PortRange(u16, u16),
|
||||
ListOfPorts(Vec<u16>),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_single_port() {
|
||||
let port_spec = "2144".parse::<PortSpec>().unwrap();
|
||||
match port_spec.data {
|
||||
PortSpecData::SinglePort(port) => assert_eq!(port, 2144),
|
||||
_ => panic!("Expected SinglePort"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_port_range() {
|
||||
let port_spec = "80-90".parse::<PortSpec>().unwrap();
|
||||
match port_spec.data {
|
||||
PortSpecData::PortRange(start, end) => {
|
||||
assert_eq!(start, 80);
|
||||
assert_eq!(end, 90);
|
||||
}
|
||||
_ => panic!("Expected PortRange"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_of_ports() {
|
||||
let port_spec = "2144,3424".parse::<PortSpec>().unwrap();
|
||||
match port_spec.data {
|
||||
PortSpecData::ListOfPorts(ports) => {
|
||||
assert_eq!(ports[0], 2144);
|
||||
assert_eq!(ports[1], 3424);
|
||||
}
|
||||
_ => panic!("Expected ListOfPorts"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_port_spec() {
|
||||
let result = "invalid".parse::<PortSpec>();
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_input() {
|
||||
let result = "".parse::<PortSpec>();
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_only_coma() {
|
||||
let result = ",".parse::<PortSpec>();
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user