Skip to content

Commit 2262eb2

Browse files
authored
Represent resolved firewall filter_hosts as a HashSet (#8315)
Previously, we have been adding one `filter_host` entry for *every NIC* in the VPC when using a `VpcFirewallRuleHostFilter::Vpc(...)`. This PR fixes this in two ways: * We no longer linear-scan the NIC list to attempt to resolve `vpc_name`->`Vni` mappings per rule. We build a `HashMap` of these early on. * This isn't *practically* a problem given that we're only working in terms of a single VPC today, but the principle is correct for when we do support e.g. VPC peering. * `filter_hosts` is now encoded as a `HashSet` in nexus and sled-agent, which ensures this property holds even under users' attempts to introduce duplicate host specifiers. Closes #8305.
1 parent fc1f4a6 commit 2262eb2

File tree

6 files changed

+40
-45
lines changed

6 files changed

+40
-45
lines changed

clients/sled-agent-client/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ progenitor::generate_api!(
7575
PortFec = omicron_common::api::internal::shared::PortFec,
7676
PortSpeed = omicron_common::api::internal::shared::PortSpeed,
7777
RouterId = omicron_common::api::internal::shared::RouterId,
78+
ResolvedVpcFirewallRule = omicron_common::api::internal::shared::ResolvedVpcFirewallRule,
7879
ResolvedVpcRoute = omicron_common::api::internal::shared::ResolvedVpcRoute,
7980
ResolvedVpcRouteSet = omicron_common::api::internal::shared::ResolvedVpcRouteSet,
8081
RouterTarget = omicron_common::api::internal::shared::RouterTarget,

common/src/api/internal/nexus.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,9 @@ pub struct ProducerRegistrationResponse {
241241
/// A `HostIdentifier` represents either an IP host or network (v4 or v6),
242242
/// or an entire VPC (identified by its VNI). It is used in firewall rule
243243
/// host filters.
244-
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
244+
#[derive(
245+
Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Hash, JsonSchema,
246+
)]
245247
#[serde(tag = "type", content = "value", rename_all = "snake_case")]
246248
pub enum HostIdentifier {
247249
Ip(oxnet::IpNet),

common/src/api/internal/shared.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -784,12 +784,12 @@ pub struct ResolvedVpcRoute {
784784
}
785785

786786
/// VPC firewall rule after object name resolution has been performed by Nexus
787-
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
787+
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
788788
pub struct ResolvedVpcFirewallRule {
789789
pub status: external::VpcFirewallRuleStatus,
790790
pub direction: external::VpcFirewallRuleDirection,
791791
pub targets: Vec<NetworkInterface>,
792-
pub filter_hosts: Option<Vec<HostIdentifier>>,
792+
pub filter_hosts: Option<HashSet<HostIdentifier>>,
793793
pub filter_ports: Option<Vec<external::L4PortRange>>,
794794
pub filter_protocols: Option<Vec<external::VpcFirewallRuleProtocol>>,
795795
pub action: external::VpcFirewallRuleAction,

nexus/networking/src/firewall_rules.rs

Lines changed: 30 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use omicron_common::api::external::Error;
2222
use omicron_common::api::external::ListResultVec;
2323
use omicron_common::api::internal::nexus::HostIdentifier;
2424
use omicron_common::api::internal::shared::NetworkInterface;
25+
use omicron_common::api::internal::shared::ResolvedVpcFirewallRule;
2526
use oxnet::IpNet;
2627
use slog::Logger;
2728
use slog::debug;
@@ -48,7 +49,7 @@ pub async fn resolve_firewall_rules_for_sled_agent(
4849
vpc: &db::model::Vpc,
4950
rules: &[db::model::VpcFirewallRule],
5051
log: &Logger,
51-
) -> Result<Vec<sled_agent_client::types::ResolvedVpcFirewallRule>, Error> {
52+
) -> Result<Vec<ResolvedVpcFirewallRule>, Error> {
5253
// Collect the names of instances, subnets, and VPCs that are either
5354
// targets or host filters. We have to find the sleds for all the
5455
// targets, and we'll need information about the IP addresses or
@@ -133,6 +134,7 @@ pub async fn resolve_firewall_rules_for_sled_agent(
133134
}
134135

135136
let mut vpc_interfaces: NicMap = HashMap::new();
137+
let mut vpc_vni_map = HashMap::new();
136138
for vpc_name in &vpcs {
137139
if let Ok((.., authz_vpc)) = LookupPath::new(opctx, datastore)
138140
.project_id(vpc.project_id)
@@ -144,6 +146,7 @@ pub async fn resolve_firewall_rules_for_sled_agent(
144146
.derive_vpc_network_interface_info(opctx, &authz_vpc)
145147
.await?
146148
{
149+
vpc_vni_map.insert(&vpc_name.0, iface.vni);
147150
vpc_interfaces
148151
.entry(vpc_name.0.clone())
149152
.or_insert_with(Vec::new)
@@ -328,7 +331,7 @@ pub async fn resolve_firewall_rules_for_sled_agent(
328331
AllowedSourceIps::List(list) => Some(
329332
list.iter()
330333
.copied()
331-
.map(|ip| HostIdentifier::Ip(ip).into())
334+
.map(HostIdentifier::Ip)
332335
.collect(),
333336
),
334337
}
@@ -355,50 +358,40 @@ pub async fn resolve_firewall_rules_for_sled_agent(
355358
// There are host filters, but we don't need to apply the allowlist
356359
// to this VPC either, so insert the rules as-is.
357360
(Some(hosts), None) => {
358-
let mut host_addrs = Vec::with_capacity(hosts.len());
361+
let mut host_addrs = HashSet::with_capacity(hosts.len());
359362
for host in hosts {
360363
match &host.0 {
361364
external::VpcFirewallRuleHostFilter::Instance(name) => {
362365
for interface in instance_interfaces
363366
.get(&name)
364367
.unwrap_or(&no_interfaces)
365368
{
366-
host_addrs.push(
367-
HostIdentifier::Ip(IpNet::host_net(
368-
interface.ip,
369-
))
370-
.into(),
371-
)
369+
host_addrs.insert(HostIdentifier::Ip(
370+
IpNet::host_net(interface.ip),
371+
));
372372
}
373373
}
374374
external::VpcFirewallRuleHostFilter::Subnet(name) => {
375375
for subnet in subnet_networks
376376
.get(name)
377377
.unwrap_or(&no_networks)
378378
{
379-
host_addrs.push(
380-
HostIdentifier::Ip(IpNet::from(*subnet))
381-
.into(),
382-
);
379+
host_addrs.insert(HostIdentifier::Ip(
380+
IpNet::from(*subnet),
381+
));
383382
}
384383
}
385384
external::VpcFirewallRuleHostFilter::Ip(addr) => {
386-
host_addrs.push(
387-
HostIdentifier::Ip(IpNet::host_net(*addr))
388-
.into(),
389-
)
385+
host_addrs.insert(HostIdentifier::Ip(
386+
IpNet::host_net(*addr),
387+
));
390388
}
391389
external::VpcFirewallRuleHostFilter::IpNet(net) => {
392-
host_addrs.push(HostIdentifier::Ip(*net).into())
390+
host_addrs.insert(HostIdentifier::Ip(*net));
393391
}
394392
external::VpcFirewallRuleHostFilter::Vpc(name) => {
395-
for interface in vpc_interfaces
396-
.get(name)
397-
.unwrap_or(&no_interfaces)
398-
{
399-
host_addrs.push(
400-
HostIdentifier::Vpc(interface.vni).into(),
401-
)
393+
if let Some(vni) = vpc_vni_map.get(name) {
394+
host_addrs.insert(HostIdentifier::Vpc(*vni));
402395
}
403396
}
404397
}
@@ -414,25 +407,23 @@ pub async fn resolve_firewall_rules_for_sled_agent(
414407
let filter_ports = rule
415408
.filter_ports
416409
.as_ref()
417-
.map(|ports| ports.iter().map(|v| v.0.into()).collect());
410+
.map(|ports| ports.iter().map(|v| v.0).collect());
418411

419412
let filter_protocols = rule
420413
.filter_protocols
421414
.as_ref()
422-
.map(|protocols| protocols.iter().map(|v| v.0.into()).collect());
415+
.map(|protocols| protocols.iter().map(|v| v.0).collect());
423416

424-
sled_agent_rules.push(
425-
sled_agent_client::types::ResolvedVpcFirewallRule {
426-
status: rule.status.0.into(),
427-
direction: rule.direction.0.into(),
428-
targets,
429-
filter_hosts,
430-
filter_ports,
431-
filter_protocols,
432-
action: rule.action.0.into(),
433-
priority: rule.priority.0.0,
434-
},
435-
);
417+
sled_agent_rules.push(ResolvedVpcFirewallRule {
418+
status: rule.status.0,
419+
direction: rule.direction.0,
420+
targets,
421+
filter_hosts,
422+
filter_ports,
423+
filter_protocols,
424+
action: rule.action.0,
425+
priority: rule.priority.0,
426+
});
436427
}
437428
debug!(
438429
log,

nexus/src/app/vpc.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ use omicron_common::api::external::ServiceIcmpConfig;
2727
use omicron_common::api::external::UpdateResult;
2828
use omicron_common::api::external::VpcFirewallRuleUpdateParams;
2929
use omicron_common::api::external::http_pagination::PaginatedBy;
30+
use omicron_common::api::internal::shared::ResolvedVpcFirewallRule;
3031
use std::sync::Arc;
3132
use uuid::Uuid;
3233

@@ -262,8 +263,7 @@ impl super::Nexus {
262263
opctx: &OpContext,
263264
vpc: &db::model::Vpc,
264265
rules: &[db::model::VpcFirewallRule],
265-
) -> Result<Vec<sled_agent_client::types::ResolvedVpcFirewallRule>, Error>
266-
{
266+
) -> Result<Vec<ResolvedVpcFirewallRule>, Error> {
267267
nexus_networking::resolve_firewall_rules_for_sled_agent(
268268
&self.db_datastore,
269269
opctx,

openapi/sled-agent.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6500,7 +6500,8 @@
65006500
"type": "array",
65016501
"items": {
65026502
"$ref": "#/components/schemas/HostIdentifier"
6503-
}
6503+
},
6504+
"uniqueItems": true
65046505
},
65056506
"filter_ports": {
65066507
"nullable": true,

0 commit comments

Comments
 (0)