diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index 2397cd15f8..8f38da8da3 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -1404,7 +1404,7 @@ pub enum RouterRouteKind { /// its destination. #[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] pub struct RouterRoute { - /// common identifying metadata + /// Common identifying metadata #[serde(flatten)] pub identity: IdentityMetadata, /// The ID of the VPC Router to which the route belongs @@ -1420,22 +1420,22 @@ pub struct RouterRoute { /// A single rule in a VPC firewall #[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] pub struct VpcFirewallRule { - /// common identifying metadata + /// Common identifying metadata #[serde(flatten)] pub identity: IdentityMetadata, - /// whether this rule is in effect + /// Whether this rule is in effect pub status: VpcFirewallRuleStatus, - /// whether this rule is for incoming or outgoing traffic + /// Whether this rule is for incoming or outgoing traffic pub direction: VpcFirewallRuleDirection, - /// list of sets of instances that the rule applies to + /// Determine the set of instances that the rule applies to pub targets: Vec, - /// reductions on the scope of the rule + /// Reductions on the scope of the rule pub filters: VpcFirewallRuleFilter, - /// whether traffic matching the rule should be allowed or dropped + /// Whether traffic matching the rule should be allowed or dropped pub action: VpcFirewallRuleAction, - /// the relative priority of this rule + /// The relative priority of this rule pub priority: VpcFirewallRulePriority, - /// the VPC to which this rule belongs + /// The VPC to which this rule belongs pub vpc_id: Uuid, } @@ -1448,29 +1448,29 @@ pub struct VpcFirewallRules { /// A single rule in a VPC firewall #[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] pub struct VpcFirewallRuleUpdate { - /// name of the rule, unique to this VPC + /// Name of the rule, unique to this VPC pub name: Name, - /// human-readable free-form text about a resource + /// Human-readable free-form text about a resource pub description: String, - /// whether this rule is in effect + /// Whether this rule is in effect pub status: VpcFirewallRuleStatus, - /// whether this rule is for incoming or outgoing traffic + /// Whether this rule is for incoming or outgoing traffic pub direction: VpcFirewallRuleDirection, - /// list of sets of instances that the rule applies to + /// Determine the set of instances that the rule applies to + #[schemars(length(max = 256))] pub targets: Vec, - /// reductions on the scope of the rule + /// Reductions on the scope of the rule pub filters: VpcFirewallRuleFilter, - /// whether traffic matching the rule should be allowed or dropped + /// Whether traffic matching the rule should be allowed or dropped pub action: VpcFirewallRuleAction, - /// the relative priority of this rule + /// The relative priority of this rule pub priority: VpcFirewallRulePriority, } -/// Updateable properties of a `Vpc`'s firewall -/// Note that VpcFirewallRules are implicitly created along with a Vpc, -/// so there is no explicit creation. +/// Updated list of firewall rules. Will replace all existing rules. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] pub struct VpcFirewallRuleUpdateParams { + #[schemars(length(max = 1024))] pub rules: Vec, } @@ -1490,19 +1490,24 @@ pub struct VpcFirewallRuleUpdateParams { #[repr(transparent)] pub struct VpcFirewallRulePriority(pub u16); -/// Filter for a firewall rule. A given packet must match every field that is -/// present for the rule to apply to it. A packet matches a field if any entry -/// in that field matches the packet. +/// Filters reduce the scope of a firewall rule. Without filters, the rule +/// applies to all packets to the targets (or from the targets, if it's an +/// outbound rule). With multiple filters, the rule applies only to packets +/// matching ALL filters. The maximum number of each type of filter is 256. #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, JsonSchema)] pub struct VpcFirewallRuleFilter { - /// If present, the sources (if incoming) or destinations (if outgoing) - /// this rule applies to. + /// If present, host filters match the "other end" of traffic from the + /// target’s perspective: for an inbound rule, they match the source of + /// traffic. For an outbound rule, they match the destination. + #[schemars(length(max = 256))] pub hosts: Option>, /// If present, the networking protocols this rule applies to. + #[schemars(length(max = 256))] pub protocols: Option>, - /// If present, the destination ports this rule applies to. + /// If present, the destination ports or port ranges this rule applies to. + #[schemars(length(max = 256))] pub ports: Option>, } @@ -1536,8 +1541,11 @@ pub enum VpcFirewallRuleAction { Deny, } -/// A `VpcFirewallRuleTarget` is used to specify the set of `Instance`s to -/// which a firewall rule applies. +/// A `VpcFirewallRuleTarget` is used to specify the set of instances to which +/// a firewall rule applies. You can target instances directly by name, or +/// specify a VPC, VPC subnet, IP, or IP subnet, which will apply the rule to +/// traffic going to all matching instances. Targets are additive: the rule +/// applies to instances matching ANY target. #[derive( Clone, Debug, @@ -1697,7 +1705,7 @@ impl JsonSchema for L4PortRange { title: Some("A range of IP ports".to_string()), description: Some( "An inclusive-inclusive range of IP ports. The second port \ - may be omitted to represent a single port" + may be omitted to represent a single port." .to_string(), ), examples: vec!["22".into(), "6667-7000".into()], diff --git a/nexus/db-model/src/vpc_firewall_rule.rs b/nexus/db-model/src/vpc_firewall_rule.rs index 120d3e1473..4601de66c5 100644 --- a/nexus/db-model/src/vpc_firewall_rule.rs +++ b/nexus/db-model/src/vpc_firewall_rule.rs @@ -211,12 +211,34 @@ pub struct VpcFirewallRule { pub priority: VpcFirewallRulePriority, } +/// Cap on the number of rules in a VPC +/// +/// The choice of value is somewhat arbitrary, but the goal is to have a +/// large number that customers are unlikely to actually hit, but which still +/// meaningfully limits the ability to overload the DB with a single request. +const MAX_FW_RULES_PER_VPC: usize = 1024; + +/// Cap on targets and on each type of filter +const MAX_FW_RULE_PARTS: usize = 256; + +fn ensure_max_len( + items: &Vec, + label: &str, + max: usize, +) -> Result<(), external::Error> { + if items.len() > max { + let msg = format!("max length {}", max); + return Err(external::Error::invalid_value(label, msg)); + } + Ok(()) +} + impl VpcFirewallRule { pub fn new( rule_id: Uuid, vpc_id: Uuid, rule: &external::VpcFirewallRuleUpdate, - ) -> Self { + ) -> Result { let identity = VpcFirewallRuleIdentity::new( rule_id, external::IdentityMetadataCreateParams { @@ -224,7 +246,20 @@ impl VpcFirewallRule { description: rule.description.clone(), }, ); - Self { + + ensure_max_len(&rule.targets, "targets", MAX_FW_RULE_PARTS)?; + + if let Some(hosts) = rule.filters.hosts.as_ref() { + ensure_max_len(&hosts, "filters.hosts", MAX_FW_RULE_PARTS)?; + } + if let Some(ports) = rule.filters.ports.as_ref() { + ensure_max_len(&ports, "filters.ports", MAX_FW_RULE_PARTS)?; + } + if let Some(protocols) = rule.filters.protocols.as_ref() { + ensure_max_len(&protocols, "filters.protocols", MAX_FW_RULE_PARTS)?; + } + + Ok(Self { identity, vpc_id, status: rule.status.into(), @@ -248,7 +283,7 @@ impl VpcFirewallRule { }), action: rule.action.into(), priority: rule.priority.into(), - } + }) } pub fn vec_from_params( @@ -256,11 +291,12 @@ impl VpcFirewallRule { params: external::VpcFirewallRuleUpdateParams, ) -> Result, external::Error> { ensure_no_duplicates(¶ms)?; - Ok(params + ensure_max_len(¶ms.rules, "rules", MAX_FW_RULES_PER_VPC)?; + params .rules - .iter() - .map(|rule| VpcFirewallRule::new(Uuid::new_v4(), vpc_id, rule)) - .collect()) + .into_iter() + .map(|rule| VpcFirewallRule::new(Uuid::new_v4(), vpc_id, &rule)) + .collect() } } diff --git a/nexus/db-queries/src/db/datastore/vpc.rs b/nexus/db-queries/src/db/datastore/vpc.rs index 615ecdac93..be88068c19 100644 --- a/nexus/db-queries/src/db/datastore/vpc.rs +++ b/nexus/db-queries/src/db/datastore/vpc.rs @@ -221,20 +221,24 @@ impl DataStore { .map(|rule| (rule.name().clone(), rule)) .collect::>(); - fw_rules.entry(DNS_VPC_FW_RULE.name.clone()).or_insert_with(|| { - VpcFirewallRule::new( + // these have to be done this way because the contructor returns a result + if !fw_rules.contains_key(&DNS_VPC_FW_RULE.name) { + let rule = VpcFirewallRule::new( Uuid::new_v4(), *SERVICES_VPC_ID, &DNS_VPC_FW_RULE, - ) - }); - fw_rules.entry(NEXUS_VPC_FW_RULE.name.clone()).or_insert_with(|| { - VpcFirewallRule::new( + )?; + fw_rules.insert(DNS_VPC_FW_RULE.name.clone(), rule); + } + + if !fw_rules.contains_key(&NEXUS_VPC_FW_RULE.name) { + let rule = VpcFirewallRule::new( Uuid::new_v4(), *SERVICES_VPC_ID, &NEXUS_VPC_FW_RULE, - ) - }); + )?; + fw_rules.insert(NEXUS_VPC_FW_RULE.name.clone(), rule); + } let rules = fw_rules .into_values() diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 9d616c7e9c..e96718b752 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -5378,7 +5378,6 @@ async fn vpc_subnet_list_network_interfaces( // VPC Firewalls -// TODO Is the number of firewall rules bounded? /// List firewall rules #[endpoint { method = GET, @@ -5410,7 +5409,23 @@ async fn vpc_firewall_rules_view( .await } +// Note: the limits in the below comment come from the firewall rules model +// file, nexus/db-model/src/vpc_firewall_rule.rs. + /// Replace firewall rules +/// +/// The maximum number of rules per VPC is 1024. +/// +/// Targets are used to specify the set of instances to which a firewall rule +/// applies. You can target instances directly by name, or specify a VPC, VPC +/// subnet, IP, or IP subnet, which will apply the rule to traffic going to +/// all matching instances. Targets are additive: the rule applies to instances +/// matching ANY target. The maximum number of targets is 256. +/// +/// Filters reduce the scope of a firewall rule. Without filters, the rule +/// applies to all packets to the targets (or from the targets, if it's an +/// outbound rule). With multiple filters, the rule applies only to packets +/// matching ALL filters. The maximum number of each type of filter is 256. #[endpoint { method = PUT, path = "/v1/vpc-firewall-rules", diff --git a/nexus/tests/integration_tests/vpc_firewall.rs b/nexus/tests/integration_tests/vpc_firewall.rs index 83379bef88..b06e01b539 100644 --- a/nexus/tests/integration_tests/vpc_firewall.rs +++ b/nexus/tests/integration_tests/vpc_firewall.rs @@ -6,7 +6,7 @@ use http::method::Method; use http::StatusCode; use nexus_test_utils::http_testing::{AuthnMode, NexusRequest}; use nexus_test_utils::resource_helpers::{ - create_project, create_vpc, object_get, object_put_error, + create_project, create_vpc, object_get, object_put, object_put_error, }; use nexus_test_utils_macros::nexus_test; use nexus_types::external_api::views::Vpc; @@ -321,3 +321,261 @@ async fn test_firewall_rules_same_name(cptestctx: &ControlPlaneTestContext) { assert_eq!(error.error_code, Some("InvalidValue".to_string())); assert_eq!(error.message, "unsupported value for \"rules\": Rule names must be unique. Duplicates: [\"dupe\"]"); } + +#[nexus_test] +async fn test_firewall_rules_max_lengths(cptestctx: &ControlPlaneTestContext) { + let client = &cptestctx.external_client; + + let project_name = "my-project"; + create_project(&client, &project_name).await; + + let base_rule = VpcFirewallRuleUpdate { + name: "my-rule".parse().unwrap(), + description: "".to_string(), + status: VpcFirewallRuleStatus::Enabled, + direction: VpcFirewallRuleDirection::Inbound, + targets: vec![], + filters: VpcFirewallRuleFilter { + hosts: None, + protocols: None, + ports: None, + }, + action: VpcFirewallRuleAction::Allow, + priority: VpcFirewallRulePriority(65534), + }; + + let rule_n = |i: usize| VpcFirewallRuleUpdate { + name: format!("rule{}", i).parse().unwrap(), + ..base_rule.clone() + }; + + let url = + format!("/v1/vpc-firewall-rules?vpc=default&project={}", project_name); + + // fine with max count + let max_rules: usize = 1024; + let rules: VpcFirewallRules = object_put( + client, + &url, + &VpcFirewallRuleUpdateParams { + rules: (0..max_rules).map(rule_n).collect::>(), + }, + ) + .await; + assert_eq!(rules.rules.len(), max_rules); + + // fails with max + 1 + let error = object_put_error( + client, + &url, + &VpcFirewallRuleUpdateParams { + rules: (0..max_rules + 1).map(rule_n).collect::>(), + }, + StatusCode::BAD_REQUEST, + ) + .await; + assert_eq!(error.error_code, Some("InvalidValue".to_string())); + assert_eq!( + error.message, + format!("unsupported value for \"rules\": max length {max_rules}") + ); + + /////////////////////// + // TARGETS + /////////////////////// + + let max_parts: usize = 256; + + let target = VpcFirewallRuleTarget::Vpc("default".parse().unwrap()); + + // fine with max + let rules: VpcFirewallRules = object_put( + client, + &url, + &VpcFirewallRuleUpdateParams { + rules: vec![VpcFirewallRuleUpdate { + targets: (0..max_parts).map(|_i| target.clone()).collect(), + ..base_rule.clone() + }], + }, + ) + .await; + assert_eq!(rules.rules[0].targets.len(), max_parts); + + // fails with max + 1 + let error = object_put_error( + client, + &url, + &VpcFirewallRuleUpdateParams { + rules: vec![VpcFirewallRuleUpdate { + targets: (0..max_parts + 1).map(|_i| target.clone()).collect(), + ..base_rule.clone() + }], + }, + StatusCode::BAD_REQUEST, + ) + .await; + assert_eq!(error.error_code, Some("InvalidValue".to_string())); + assert_eq!( + error.message, + format!("unsupported value for \"targets\": max length {max_parts}") + ); + + /////////////////////// + // HOST FILTERS + /////////////////////// + + let host = VpcFirewallRuleHostFilter::Vpc("default".parse().unwrap()); + + // fine with max + let rules: VpcFirewallRules = object_put( + client, + &url, + &VpcFirewallRuleUpdateParams { + rules: vec![VpcFirewallRuleUpdate { + filters: VpcFirewallRuleFilter { + hosts: Some( + (0..max_parts).map(|_i| host.clone()).collect(), + ), + protocols: None, + ports: None, + }, + ..base_rule.clone() + }], + }, + ) + .await; + assert_eq!(rules.rules[0].filters.hosts.as_ref().unwrap().len(), max_parts); + + // fails with max + 1 + let error = object_put_error( + client, + &url, + &VpcFirewallRuleUpdateParams { + rules: vec![VpcFirewallRuleUpdate { + filters: VpcFirewallRuleFilter { + hosts: Some( + (0..max_parts + 1).map(|_i| host.clone()).collect(), + ), + protocols: None, + ports: None, + }, + ..base_rule.clone() + }], + }, + StatusCode::BAD_REQUEST, + ) + .await; + assert_eq!(error.error_code, Some("InvalidValue".to_string())); + assert_eq!( + error.message, + format!( + "unsupported value for \"filters.hosts\": max length {max_parts}" + ) + ); + + /////////////////////// + // PORT FILTERS + /////////////////////// + + let port: L4PortRange = "1234".parse().unwrap(); + + // fine with max + let rules: VpcFirewallRules = object_put( + client, + &url, + &VpcFirewallRuleUpdateParams { + rules: vec![VpcFirewallRuleUpdate { + filters: VpcFirewallRuleFilter { + ports: Some((0..max_parts).map(|_i| port).collect()), + protocols: None, + hosts: None, + }, + ..base_rule.clone() + }], + }, + ) + .await; + assert_eq!(rules.rules[0].filters.ports.as_ref().unwrap().len(), max_parts); + + // fails with max + 1 + let error = object_put_error( + client, + &url, + &VpcFirewallRuleUpdateParams { + rules: vec![VpcFirewallRuleUpdate { + filters: VpcFirewallRuleFilter { + ports: Some((0..max_parts + 1).map(|_i| port).collect()), + protocols: None, + hosts: None, + }, + ..base_rule.clone() + }], + }, + StatusCode::BAD_REQUEST, + ) + .await; + assert_eq!(error.error_code, Some("InvalidValue".to_string())); + assert_eq!( + error.message, + format!( + "unsupported value for \"filters.ports\": max length {max_parts}" + ) + ); + + /////////////////////// + // PROTOCOL FILTERS + /////////////////////// + + let protocol = VpcFirewallRuleProtocol::Tcp; + + // fine with max + let rules: VpcFirewallRules = object_put( + client, + &url, + &VpcFirewallRuleUpdateParams { + rules: vec![VpcFirewallRuleUpdate { + filters: VpcFirewallRuleFilter { + protocols: Some( + (0..max_parts).map(|_i| protocol).collect(), + ), + ports: None, + hosts: None, + }, + ..base_rule.clone() + }], + }, + ) + .await; + assert_eq!( + rules.rules[0].filters.protocols.as_ref().unwrap().len(), + max_parts + ); + + // fails with max + 1 + let error = object_put_error( + client, + &url, + &VpcFirewallRuleUpdateParams { + rules: vec![VpcFirewallRuleUpdate { + filters: VpcFirewallRuleFilter { + protocols: Some( + (0..max_parts + 1).map(|_i| protocol).collect(), + ), + ports: None, + hosts: None, + }, + ..base_rule.clone() + }], + }, + StatusCode::BAD_REQUEST, + ) + .await; + assert_eq!(error.error_code, Some("InvalidValue".to_string())); + assert_eq!( + error.message, + format!( + "unsupported value for \"filters.protocols\": max length {max_parts}" + ) + ); +} diff --git a/openapi/nexus.json b/openapi/nexus.json index 9e46a92039..d1dc4b277c 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -8297,6 +8297,7 @@ "vpcs" ], "summary": "Replace firewall rules", + "description": "The maximum number of rules per VPC is 1024.\nTargets are used to specify the set of instances to which a firewall rule applies. You can target instances directly by name, or specify a VPC, VPC subnet, IP, or IP subnet, which will apply the rule to traffic going to all matching instances. Targets are additive: the rule applies to instances matching ANY target. The maximum number of targets is 256.\nFilters reduce the scope of a firewall rule. Without filters, the rule applies to all packets to the targets (or from the targets, if it's an outbound rule). With multiple filters, the rule applies only to packets matching ALL filters. The maximum number of each type of filter is 256.", "operationId": "vpc_firewall_rules_update", "parameters": [ { @@ -16026,7 +16027,7 @@ "L4PortRange": { "example": "22", "title": "A range of IP ports", - "description": "An inclusive-inclusive range of IP ports. The second port may be omitted to represent a single port", + "description": "An inclusive-inclusive range of IP ports. The second port may be omitted to represent a single port.", "type": "string", "pattern": "^[0-9]{1,5}(-[0-9]{1,5})?$", "minLength": 1, @@ -20331,7 +20332,7 @@ "type": "object", "properties": { "action": { - "description": "whether traffic matching the rule should be allowed or dropped", + "description": "Whether traffic matching the rule should be allowed or dropped", "allOf": [ { "$ref": "#/components/schemas/VpcFirewallRuleAction" @@ -20343,7 +20344,7 @@ "type": "string" }, "direction": { - "description": "whether this rule is for incoming or outgoing traffic", + "description": "Whether this rule is for incoming or outgoing traffic", "allOf": [ { "$ref": "#/components/schemas/VpcFirewallRuleDirection" @@ -20351,7 +20352,7 @@ ] }, "filters": { - "description": "reductions on the scope of the rule", + "description": "Reductions on the scope of the rule", "allOf": [ { "$ref": "#/components/schemas/VpcFirewallRuleFilter" @@ -20372,13 +20373,13 @@ ] }, "priority": { - "description": "the relative priority of this rule", + "description": "The relative priority of this rule", "type": "integer", "format": "uint16", "minimum": 0 }, "status": { - "description": "whether this rule is in effect", + "description": "Whether this rule is in effect", "allOf": [ { "$ref": "#/components/schemas/VpcFirewallRuleStatus" @@ -20386,7 +20387,7 @@ ] }, "targets": { - "description": "list of sets of instances that the rule applies to", + "description": "Determine the set of instances that the rule applies to", "type": "array", "items": { "$ref": "#/components/schemas/VpcFirewallRuleTarget" @@ -20403,7 +20404,7 @@ "format": "date-time" }, "vpc_id": { - "description": "the VPC to which this rule belongs", + "description": "The VPC to which this rule belongs", "type": "string", "format": "uuid" } @@ -20438,24 +20439,26 @@ ] }, "VpcFirewallRuleFilter": { - "description": "Filter for a firewall rule. A given packet must match every field that is present for the rule to apply to it. A packet matches a field if any entry in that field matches the packet.", + "description": "Filters reduce the scope of a firewall rule. Without filters, the rule applies to all packets to the targets (or from the targets, if it's an outbound rule). With multiple filters, the rule applies only to packets matching ALL filters. The maximum number of each type of filter is 256.", "type": "object", "properties": { "hosts": { "nullable": true, - "description": "If present, the sources (if incoming) or destinations (if outgoing) this rule applies to.", + "description": "If present, host filters match the \"other end\" of traffic from the target’s perspective: for an inbound rule, they match the source of traffic. For an outbound rule, they match the destination.", "type": "array", "items": { "$ref": "#/components/schemas/VpcFirewallRuleHostFilter" - } + }, + "maxItems": 256 }, "ports": { "nullable": true, - "description": "If present, the destination ports this rule applies to.", + "description": "If present, the destination ports or port ranges this rule applies to.", "type": "array", "items": { "$ref": "#/components/schemas/L4PortRange" - } + }, + "maxItems": 256 }, "protocols": { "nullable": true, @@ -20463,7 +20466,8 @@ "type": "array", "items": { "$ref": "#/components/schemas/VpcFirewallRuleProtocol" - } + }, + "maxItems": 256 } } }, @@ -20585,7 +20589,7 @@ ] }, "VpcFirewallRuleTarget": { - "description": "A `VpcFirewallRuleTarget` is used to specify the set of `Instance`s to which a firewall rule applies.", + "description": "A `VpcFirewallRuleTarget` is used to specify the set of instances to which a firewall rule applies. You can target instances directly by name, or specify a VPC, VPC subnet, IP, or IP subnet, which will apply the rule to traffic going to all matching instances. Targets are additive: the rule applies to instances matching ANY target.", "oneOf": [ { "description": "The rule applies to all instances in the VPC", @@ -20690,7 +20694,7 @@ "type": "object", "properties": { "action": { - "description": "whether traffic matching the rule should be allowed or dropped", + "description": "Whether traffic matching the rule should be allowed or dropped", "allOf": [ { "$ref": "#/components/schemas/VpcFirewallRuleAction" @@ -20698,11 +20702,11 @@ ] }, "description": { - "description": "human-readable free-form text about a resource", + "description": "Human-readable free-form text about a resource", "type": "string" }, "direction": { - "description": "whether this rule is for incoming or outgoing traffic", + "description": "Whether this rule is for incoming or outgoing traffic", "allOf": [ { "$ref": "#/components/schemas/VpcFirewallRuleDirection" @@ -20710,7 +20714,7 @@ ] }, "filters": { - "description": "reductions on the scope of the rule", + "description": "Reductions on the scope of the rule", "allOf": [ { "$ref": "#/components/schemas/VpcFirewallRuleFilter" @@ -20718,7 +20722,7 @@ ] }, "name": { - "description": "name of the rule, unique to this VPC", + "description": "Name of the rule, unique to this VPC", "allOf": [ { "$ref": "#/components/schemas/Name" @@ -20726,13 +20730,13 @@ ] }, "priority": { - "description": "the relative priority of this rule", + "description": "The relative priority of this rule", "type": "integer", "format": "uint16", "minimum": 0 }, "status": { - "description": "whether this rule is in effect", + "description": "Whether this rule is in effect", "allOf": [ { "$ref": "#/components/schemas/VpcFirewallRuleStatus" @@ -20740,11 +20744,12 @@ ] }, "targets": { - "description": "list of sets of instances that the rule applies to", + "description": "Determine the set of instances that the rule applies to", "type": "array", "items": { "$ref": "#/components/schemas/VpcFirewallRuleTarget" - } + }, + "maxItems": 256 } }, "required": [ @@ -20759,14 +20764,15 @@ ] }, "VpcFirewallRuleUpdateParams": { - "description": "Updateable properties of a `Vpc`'s firewall Note that VpcFirewallRules are implicitly created along with a Vpc, so there is no explicit creation.", + "description": "Updated list of firewall rules. Will replace all existing rules.", "type": "object", "properties": { "rules": { "type": "array", "items": { "$ref": "#/components/schemas/VpcFirewallRuleUpdate" - } + }, + "maxItems": 1024 } }, "required": [ diff --git a/openapi/sled-agent.json b/openapi/sled-agent.json index 1323769da2..047656d584 100644 --- a/openapi/sled-agent.json +++ b/openapi/sled-agent.json @@ -3485,7 +3485,7 @@ "L4PortRange": { "example": "22", "title": "A range of IP ports", - "description": "An inclusive-inclusive range of IP ports. The second port may be omitted to represent a single port", + "description": "An inclusive-inclusive range of IP ports. The second port may be omitted to represent a single port.", "type": "string", "pattern": "^[0-9]{1,5}(-[0-9]{1,5})?$", "minLength": 1,