OpenKeel: the sovereign French Cloud solution of the inherent group, with a European reach Discover inherent

Reverse-spec · Services

neutron-fwaas — REST API

REST API — neutron-fwaas — branch stable/2026.2

Branch stable/2026.2 @ 5303a4deed1e — repository https://opendev.org/openstack/neutron-fwaas — analysed on 2026-10-28. Other repositories cited: neutron-lib: = neutron-lib stable/2026.2 @ 1ee72052251a; neutron: = neutron stable/2026.2 @ eef1a0f7ee36.

neutron-fwaas serves no HTTP of its own. Its API is the FWaaS v2 extension of neutron-server:

  • the resources and their attributes are declared in neutron-lib (neutron-lib:neutron_lib/api/definitions/firewall_v2.py:51);
  • the extension descriptor turns them into Routes resources under the prefix /fwaas (neutron_fwaas/extensions/firewall_v2.py:119, prefix neutron-lib:neutron_lib/api/definitions/firewall_v2.py:49);
  • Neutron's generic controller handles every request (neutron:neutron/api/v2/base.py:445 for create, neutron:neutron/api/v2/base.py:227 for member actions) and calls the service plugin FirewallPluginV2 (neutron_fwaas/services/firewall/fwaas_plugin_v2.py:42);
  • the plugin delegates persistence and back-end programming to the service driver selected by [service_providers] service_provider = FIREWALL_V2:…:default (neutron_fwaas/services/firewall/fwaas_plugin_v2.py:59). There are two drivers: the OVN driver (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:31, no agent) and the agent driver (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:162, RPC to L3/L2 agent extensions).

The route table below was extracted at run time from the real paste pipeline with an ML2/OVN profile and the OVN driver (_rewrite/routes_stable-2026.2.json, tool _rewrite/tools/fwaas_routes.py). The paths are those of the public endpoint (/v2.0 prefix).

Summary

MethodPathPlugin methodPolicy (default)SuccessErrors (see 02)
GET/v2.0/fwaas/firewall_groupsneutron_fwaas/services/firewall/fwaas_plugin_v2.py:347 get_firewall_groupsget_firewall_group (admin, project reader, or shared)200401
GET/v2.0/fwaas/firewall_groups/{id}neutron_fwaas/services/firewall/fwaas_plugin_v2.py:340 get_firewall_groupget_firewall_group200401, 404
POST/v2.0/fwaas/firewall_groupsneutron_fwaas/services/firewall/fwaas_plugin_v2.py:307 create_firewall_groupcreate_firewall_group (admin or project member); create_firewall_group:shared admin201400, 401, 403, 404, 409, 500
PUT/v2.0/fwaas/firewall_groups/{id}neutron_fwaas/services/firewall/fwaas_plugin_v2.py:358 update_firewall_groupupdate_firewall_group; :shared admin200400, 401, 403, 404, 409, 500
DELETE/v2.0/fwaas/firewall_groups/{id}neutron_fwaas/services/firewall/fwaas_plugin_v2.py:323 delete_firewall_groupdelete_firewall_group; :shared admin204401, 403, 404, 409
GET/v2.0/fwaas/firewall_policiesneutron_fwaas/services/firewall/fwaas_plugin_v2.py:399 get_firewall_policiesget_firewall_policy200401
GET/v2.0/fwaas/firewall_policies/{id}neutron_fwaas/services/firewall/fwaas_plugin_v2.py:392 get_firewall_policyget_firewall_policy200401, 404
POST/v2.0/fwaas/firewall_policiesneutron_fwaas/services/firewall/fwaas_plugin_v2.py:379 create_firewall_policycreate_firewall_policy; :shared admin201400, 401, 403, 404, 409
PUT/v2.0/fwaas/firewall_policies/{id}neutron_fwaas/services/firewall/fwaas_plugin_v2.py:405 update_firewall_policyupdate_firewall_policy; :shared admin200400, 401, 403, 404, 409
DELETE/v2.0/fwaas/firewall_policies/{id}neutron_fwaas/services/firewall/fwaas_plugin_v2.py:385 delete_firewall_policydelete_firewall_policy204401, 404, 409
PUT/v2.0/fwaas/firewall_policies/{id}/insert_ruleneutron_fwaas/services/firewall/fwaas_plugin_v2.py:448 insert_ruleinsert_rule200400, 401, 403, 404, 409
PUT/v2.0/fwaas/firewall_policies/{id}/remove_ruleneutron_fwaas/services/firewall/fwaas_plugin_v2.py:456 remove_ruleremove_rule200400, 401, 403, 404, 409
GET/v2.0/fwaas/firewall_rulesneutron_fwaas/services/firewall/fwaas_plugin_v2.py:434 get_firewall_rulesget_firewall_rule200401
GET/v2.0/fwaas/firewall_rules/{id}neutron_fwaas/services/firewall/fwaas_plugin_v2.py:427 get_firewall_ruleget_firewall_rule200401, 404
POST/v2.0/fwaas/firewall_rulesneutron_fwaas/services/firewall/fwaas_plugin_v2.py:414 create_firewall_rulecreate_firewall_rule; :shared admin201400, 401, 403, 409
PUT/v2.0/fwaas/firewall_rules/{id}neutron_fwaas/services/firewall/fwaas_plugin_v2.py:440 update_firewall_ruleupdate_firewall_rule; :shared admin200400, 401, 403, 404, 409, 500
DELETE/v2.0/fwaas/firewall_rules/{id}neutron_fwaas/services/firewall/fwaas_plugin_v2.py:420 delete_firewall_ruledelete_firewall_rule; :shared admin204401, 404, 409

The policies are defined in neutron_fwaas/policies/firewall_group.py:29, neutron_fwaas/policies/firewall_policy.py:30 and neutron_fwaas/policies/firewall_rule.py:30 (full matrix in _rewrite/policies_stable-2026.2.json). Member and reader roles are scoped to the project (scope_types=['project']), and the :shared variants require admin. Read access also matches the shared_firewall_* rules (field:<collection>:shared=True, neutron_fwaas/policies/firewall_group.py:23).

Cross-cutting behaviour (all endpoints)

These come from Neutron, not from neutron-fwaas, but a client sees them on every FWaaS call:

  • Authorization hides resources. A GET, PUT, DELETE or member action on a resource the caller may not see returns 404 "The resource could not be found.", never 403 (neutron:neutron/api/v2/base.py:400, neutron:neutron/api/v2/base.py:598, neutron:neutron/api/v2/base.py:683, neutron:neutron/api/v2/base.py:240). A 403 is only returned when the resource is visible but the action is not allowed (for example setting shared without admin).
  • No bulk create. The three collections are built without bulk support (allow_bulk: false in _rewrite/routes_stable-2026.2.json). A list body returns 400 "Bulk operation not supported" (neutron:neutron/api/v2/base.py:731).
  • Pagination and sorting are accepted but emulated in Python, not pushed to SQL (native_pagination: false, native_sorting: false in the route table).
  • Unknown attributes in a body return 400 "Unrecognized attribute(s)", and attributes with allow_post/allow_put: false return 400 (neutron:neutron/api/v2/base.py:712).
  • Quotas are checked on every create (register_quota=True, neutron_fwaas/extensions/firewall_v2.py:126; reservation in neutron:neutron/api/v2/base.py:491). Defaults: [QUOTAS] quota_firewall_group = 10, quota_firewall_policy = 10, quota_firewall_rule = 100 (neutron_fwaas/extensions/firewall_v2.py:97). Exceeding them returns 409 OverQuota.
  • Notifications: Neutron emits firewall_group.create.start/.end, .update.start/.end and .delete.start/.end (likewise for firewall_policy and firewall_rule) on the notification bus (neutron:neutron/api/v2/base.py:439, neutron:neutron/api/v2/base.py:511, neutron:neutron/api/v2/base.py:628, neutron:neutron/api/v2/base.py:701, neutron:neutron/api/v2/base.py:572, neutron:neutron/api/v2/base.py:605). Member actions (insert_rule, remove_rule) emit none (neutron:neutron/api/v2/base.py:227).
  • Standard attributes (created_at, updated_at, revision_number, tags, description) are present on the three resources, because the models carry standard_attr_id and tag_support = True (neutron_fwaas/db/firewall/v2/models.py:25). However, the extension standard-attr-fwaas-v2 is not advertised: the module neutron_fwaas/extensions/firewall_v2_stdattrs.py defines the class Standard_attr_fwaas (neutron_fwaas/extensions/firewall_v2_stdattrs.py:19), and Neutron's loader expects Firewall_v2_stdattrs. It logs "Did not find expected name" (neutron:neutron/api/extensions.py:459) and skips the file (seen at run time, see 07 § 8).
  • Implicit default firewall group (side effect on reads). Every list call (GET firewall_groups, GET firewall_policies, GET firewall_rules), and every group creation, first ensures that the caller's project has a firewall group named default, and creates it if needed (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:853, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:566, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:379, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:724). See § "Default firewall group" below.

Resources and attributes

Source of the attribute maps: neutron-lib:neutron_lib/api/definitions/firewall_v2.py:52 (rules), neutron-lib:neutron_lib/api/definitions/firewall_v2.py:116 (groups), neutron-lib:neutron_lib/api/definitions/firewall_v2.py:165 (policies). "POST/PUT" means the attribute is accepted on create/update; every attribute listed is returned.

firewall_rule

AttributePOSTPUTDefaultValidation / conversionFilter
id——generateduuidyes
tenant_id / project_idyes (admin for another project)—caller's projectstring ≤ 255yes
nameyesyes""name string ≤ 255yes
descriptionyesyes""string ≤ 255no
firewall_policy_id———read-only; list of the policies that use the rule, or null (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:374, neutron_fwaas/objects/firewall_v2.py:115)no
sharedyesyesfalseboolean; setting it is governed by create/update_firewall_rule:shared (admin)yes
protocolyesyesnull (any)one of the iptables protocol names (tcp, udp, icmp, ipv6-icmp, sctp, gre, ah, esp, vrrp…) or null (neutron-lib:neutron_lib/api/definitions/firewall_v2.py:77, neutron-lib:neutron_lib/api/definitions/constants.py:29); numbers are refused by the value validatoryes
ip_versionyesyes44 or 6yes
source_ip_address / destination_ip_addressyesyesnullIP or CIDRyes
source_port / destination_portyesyesnull"N" or "min:max", 0–65535, min ≤ max (neutron-lib:neutron_lib/api/definitions/firewall_v2.py:95)no
position——nulldeclared but never filled: always null in responses [verified in the exchanges]no
actionyesyesdenyallow, deny, reject, case-insensitive (neutron-lib:neutron_lib/api/definitions/firewall_v2.py:107)yes
enabledyesyestruebooleanyes

firewall_policy

AttributePOSTPUTDefaultValidationFilter
id, tenant_id, name, description, sharedas above
firewall_rulesyesyes[]ordered list of rule UUIDs; the order is the evaluation order (neutron-lib:neutron_lib/api/definitions/firewall_v2.py:189)yes
auditedyesyesfalseboolean; **reset to false** by any change of the policy or of one of its rules, unless the PUT sets it explicitly (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:539, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:147, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:311)yes

firewall_group

AttributePOSTPUTDefaultValidationFilter
id, tenant_id, name, description, sharedas above
admin_state_upyesyestruebooleanyes
status——computedACTIVE, CREATED, DOWN, ERROR, INACTIVE, PENDING_CREATE, PENDING_UPDATE, PENDING_DELETE (neutron_fwaas/objects/firewall_v2.py:26)yes
portsyesyes[]list of port UUIDs; on PUT the list replaces the previous one (neutron-lib:neutron_lib/api/definitions/firewall_v2.py:141, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:765)yes
ingress_firewall_policy_id / egress_firewall_policy_idyesyesnulluuid or nullyes

Direction semantics. "Ingress" means traffic going towards the project network from the router, and "egress" traffic coming from it:

  • with iptables, ingress rules match -o qr-<port>, the router's output to the internal interface (neutron_fwaas/services/firewall/service_drivers/agents/drivers/linux/iptables_fwaas_v2.py:50);
  • with OVN, ingress rules become from-lport ACLs matching inport == @<port group> on the router-type logical switch port (neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:27, neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:127). That port's traffic enters the logical switch from the router, so the meaning is the same. [inferred: the equivalence of the two back ends was deduced from the code, not tested on a live system]

Firewall rules

POST /v2.0/fwaas/firewall_rules

Functional description

  • Purpose: define one filtering criterion (protocol, addresses, ports, action) that can then be placed in one or more policies.
  • Actor: project member (create_firewall_rule, neutron_fwaas/policies/firewall_rule.py:30); only an admin can create a shared rule (neutron_fwaas/policies/firewall_rule.py:82).
  • Preconditions: quota firewall_rule not exhausted.
  • Business rules (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:248):
    • ports are allowed only with tcp or udp. Ports with another protocol raise FirewallRuleInvalidICMPParameter (400, neutron_fwaas/common/utils.py:29); this includes sctp, even though OVN could match SCTP ports. Ports without a protocol raise FirewallRuleWithPortWithoutProtocolInvalid (400, neutron_fwaas/common/utils.py:33);
    • source and destination addresses must have the rule's ip_version, otherwise FirewallIpAddressConflict (400, neutron_fwaas/common/utils.py:48);
    • a port range must have min ≤ max. The API validator answers first with "First port in a port range must be lower than the second port" (400); the plugin check FirewallRuleInvalidPortValue (neutron_fwaas/common/utils.py:54) is a second line of defence;
    • the range is stored as two integers, and a range whose two bounds are equal is shown back as a single port (neutron_fwaas/common/utils.py:67).
  • Flow:
    1. (synchronous) API validation, policy, quota (Neutron).
    2. (synchronous) Business validation and insertion into firewall_rules_v2 (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:257), inside the driver's transaction (neutron_fwaas/services/firewall/service_drivers/driver_api.py:318).
    3. (synchronous) Driver pre/post-commit hooks: neither driver acts on rule creation; a rule only takes effect once it is in a policy that is attached to a group.
    4. (synchronous) Internal callback firewall_rule AFTER_CREATE (neutron_fwaas/services/firewall/service_drivers/driver_api.py:331).
  • Outcome: the rule exists, with firewall_policy_id: null. 201.
  • Limitations: a protocol name that has no OVN mapping (gre, ah, esp, vrrp…) is accepted, but the OVN driver writes no protocol clause for it, so the ACL matches all IP traffic of that version (neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:82; proved by the generated vectors _rewrite/cases-ovn-acl-matrix_stable-2026.2.json).

Technical sheet

  • Request: {"firewall_rule": {"name": "web", "protocol": "tcp", "destination_port": "80:81", "action": "allow", "ip_version": 4, "source_ip_address": "10.0.0.0/24"}}.
  • Response 201: {"firewall_rule": {"id": "…", "name": "web", "description": "", "protocol": "tcp", "ip_version": 4, "source_ip_address": "10.0.0.0/24", "destination_ip_address": null, "source_port": null, "destination_port": "80:81", "action": "allow", "enabled": true, "shared": false, "firewall_policy_id": null, "position": null, "project_id": "…", "tenant_id": "…", "created_at": "…", "updated_at": "…", "revision_number": 0, "tags": []}} (shape from _rewrite/response-schemas_stable-2026.2.json).
  • Errors: 400 (validation above), 403 (shared without admin), 409 OverQuota.

PUT /v2.0/fwaas/firewall_rules/{id}

  • Purpose: change a rule; the change is propagated to every firewall group that uses it, through its policies.
  • Preconditions: no firewall group that uses the rule (through any policy of the same project as the rule) may be in a PENDING_* state, otherwise FirewallGroupInPendingState (409, neutron_fwaas/services/firewall/fwaas_plugin_v2.py:114, neutron_fwaas/services/firewall/fwaas_plugin_v2.py:97). Policies of other projects that use a shared rule are not checked (filter on the rule's project_id, neutron_fwaas/services/firewall/fwaas_plugin_v2.py:237).
  • Business rules: the same validations as on create, applied to the stored rule merged with the change (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:280). Every policy that contains the rule gets audited = false (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:308).
  • Flow:
    1. (synchronous) DB update.
    2. (synchronous, OVN driver) If one of enabled, protocol, ip_version, addresses, ports or action changed (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:320):
      • enabling the rule adds its ACL to every port group concerned;
      • disabling it deletes the ACL;
      • otherwise the ACL match and action columns are rewritten in place. The priority is unchanged.
    3. (asynchronous, agent driver) For each policy that contains the rule, each group with ports goes to PENDING_UPDATE, and a fanout cast update_firewall_group carries the full rule set to the agents (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:350, neutron_fwaas/services/firewall/service_drivers/agents/agents.py:207). The agents report the status back (05 § "update_firewall_group").
  • Outcome: 200 with the rule. With OVN the ACLs are already changed when the response is sent. With agents the groups are PENDING_UPDATE until the agents answer.
  • Limitations (OVN): the OVN update looks for the ACLs of the policies listed in the old rule's firewall_policy_id (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:331). If the policy list is empty, nothing happens.

DELETE /v2.0/fwaas/firewall_rules/{id}

  • Purpose: delete a rule that no policy uses.
  • Business rules: a rule still associated with a policy raises FirewallRuleInUse (409, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:320). Unknown id: FirewallRuleNotFound (404, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:74).
  • Flow: DB delete (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:315); callback firewall_rule AFTER_DELETE (neutron_fwaas/services/firewall/service_drivers/driver_api.py:353). No back-end action.
  • Outcome: 204.

GET /v2.0/fwaas/firewall_rules[/{id}]

  • Purpose: read the rules the caller may see (own project, plus shared rules).
  • Side effect: the list call creates the caller's default firewall group if missing (§ "Default firewall group"). In a project without one, the first list therefore returns the four rules default ingress ipv4/ipv6 and default egress ipv4/ipv6.
  • Filters: every attribute marked "Filter" above. firewall_policy_id cannot be filtered.

Firewall policies

POST /v2.0/fwaas/firewall_policies

  • Purpose: group an ordered list of rules into a policy that can be attached to firewall groups as their ingress and/or egress policy.
  • Actor: project member; shared requires admin (neutron_fwaas/policies/firewall_policy.py:82).
  • Business rules:
    • the names default ingress and default egress are reserved: FirewallDefaultParameterExists (409, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:519, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:841);
    • every rule must exist: FirewallRuleNotFound (404, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:417);
    • a shared policy may only contain shared rules: FirewallRuleSharingConflict. It is returned with 404, because neutron-lib derives it from NotFound (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:421, neutron-lib:neutron_lib/exceptions/firewall_v2.py:64);
    • an unshared rule must belong to the policy's project: FirewallRuleConflict (409, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:432);
    • the position of each rule is its index in firewall_rules, starting at 1 (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:402).
  • Flow: insertion into firewall_policies_v2 and firewall_policy_rule_associations_v2 (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:502); no back-end action (a new policy is attached to no group).
  • Outcome: 201.

PUT /v2.0/fwaas/firewall_policies/{id}

  • Purpose: rename, (un)share, mark audited, or replace the rule list of a policy; the result is propagated to the groups that use it.
  • Preconditions: no group of the policy's project that uses it is PENDING_* (409 FirewallGroupInPendingState, neutron_fwaas/services/firewall/fwaas_plugin_v2.py:101).
  • Business rules (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:522):
    • the default policies cannot be updated at all: FirewallDefaultObjectUpdateRestricted (409; its message says "of type Firewall Group" even for a policy, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:839);
    • un-sharing (shared: false) is refused when a group of another project uses the policy: FirewallPolicyInUse (409, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:467);
    • sharing without changing the rules requires all current rules to be shared: FirewallPolicySharingConflict (409, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:443);
    • a new firewall_rules list is validated as on create, then replaces the associations (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:474). An empty list removes all rules;
    • audited becomes false unless the body sets it (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:539).
  • Flow (OVN): when the rule list changed, every port group of a group that uses the policy is rebuilt: its ACLs are reset to the four default drops, then the ACLs of both policies of the group are re-added (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:311, neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:157). If the port group is missing, it is created.
  • Flow (agents): a PENDING_UPDATE and fanout cast for each group with ports (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:346).
  • Outcome: 200 with the policy.

PUT /v2.0/fwaas/firewall_policies/{id}/insert_rule

  • Purpose: insert one rule at a precise place in the policy, without resending the whole list.
  • Request: {"firewall_rule_id": "<uuid>", "insert_before": "<uuid>"} or {"firewall_rule_id": "<uuid>", "insert_after": "<uuid>"}. Both are optional. When both are given, insert_before wins. When neither is given, the rule goes first (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:323).
  • Business rules:
    • firewall_rule_id missing: FirewallRuleInfoMissing (400). Not a UUID: FirewallRuleNotFound (404) (neutron_fwaas/services/firewall/fwaas_plugin_v2.py:243);
    • the rule is already in the policy: FirewallRuleAlreadyAssociated (409, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:161);
    • the rule is unshared and belongs to another project: FirewallRuleConflict (409, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:119);
    • the reference rule is not in the policy: FirewallRuleNotAssociatedWithPolicy (400, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:175);
    • the policy's groups must not be PENDING_* (409);
    • a shared policy accepts an unshared rule here (no sharing check, unlike PUT) [verified: insert_rule only calls _check_firewall_rule_conflict, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:338].
  • Flow: the associations are deleted and rewritten with positions 1..n, and audited becomes false (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:123). OVN: the port groups of the policy's groups are rebuilt (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:356). Agents: PENDING_UPDATE and a cast (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:357).
  • Response 200: the policy without the firewall_policy wrapper ({"id": …, "firewall_rules": [...], …}): the member action returns the plugin result as is (neutron:neutron/api/v2/base.py:262) [verified in the exchanges].

PUT /v2.0/fwaas/firewall_policies/{id}/remove_rule

  • Request: {"firewall_rule_id": "<uuid>"}.
  • Business rules: as for insert_rule. The rule must be associated with the policy, otherwise 400 FirewallRuleNotAssociatedWithPolicy (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:361).
  • Flow: the positions are renumbered and audited becomes false. OVN: the rule's ACL is deleted from each port group (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:360). Agents: a cast.
  • Response 200: the unwrapped policy.

DELETE /v2.0/fwaas/firewall_policies/{id}

  • Business rules: refused with FirewallPolicyInUse (409) while any group, of any project, uses the policy as ingress or egress policy (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:548). The rule associations are deleted with it (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:554).
  • Note: the internal callback published after a policy deletion is AFTER_UPDATE, not AFTER_DELETE (neutron_fwaas/services/firewall/service_drivers/driver_api.py:272).
  • Outcome: 204.

Firewall groups

POST /v2.0/fwaas/firewall_groups

Functional description

  • Purpose: apply an ingress and/or egress policy to a set of ports: router interfaces (L3) or VM ports (L2, agent driver with OVS only).
  • Actor: project member; shared requires admin (neutron_fwaas/policies/firewall_group.py:81).
  • Preconditions:
    • each policy exists and is either shared or of the group's project: FirewallPolicyNotFound (404) or FirewallPolicyConflict (404, neutron_fwaas/services/firewall/fwaas_plugin_v2.py:141, neutron-lib:neutron_lib/exceptions/firewall_v2.py:54);
    • each port exists, is a router interface port (network:router_interface, network:router_interface_distributed, network:ha_router_replicated_interface) or a compute port (compute:*), and is supported by the driver (neutron_fwaas/services/firewall/fwaas_plugin_v2.py:144):
      • another device_owner raises FirewallGroupPortInvalid (409);
      • a port the driver does not support raises FirewallGroupPortNotSupported (409, neutron_fwaas/common/exceptions.py:22). The OVN driver supports router ports only (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:42). The agent driver supports compute ports bound with vif_type=ovs and no hybrid plug (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:182); an unbound or failed-binding port is refused (neutron_fwaas/services/firewall/fwaas_plugin_v2.py:178);
    • no port may already belong to another group of the project: FirewallGroupPortInUse (409, neutron_fwaas/services/firewall/fwaas_plugin_v2.py:207). The database enforces it globally with a unique key on port_id (neutron_fwaas/db/firewall/v2/models.py:97), which raises the same error (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:596);
    • the port ownership is not checked against the group's project. The plugin reads the port with the caller's context (neutron_fwaas/services/firewall/fwaas_plugin_v2.py:157), so a member can only name ports they can see, but an admin can attach any port [inferred]. FirewallGroupPortInvalidProject exists in neutron-lib but is never raised.
  • Business rules:
    • the name default is reserved: FirewallDefaultParameterExists (409, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:832);
    • creating any group first creates the project's default group if missing (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:724).
  • Flow (OVN driver):
    1. (synchronous, inside the DB transaction) Insertion of the group and port associations with status CREATED (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:713).
    2. (synchronous, pre-commit) The status becomes INACTIVE without ports, PENDING_CREATE with ports. The OVN Port_Group pg_<uuid with _> is created with external_ids:neutron:firewall_group_id, and the four default drop ACLs are added in separate OVSDB transactions (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:199, neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:219).
    3. (synchronous, "post-commit") One OVSDB transaction adds the ACLs of the policies' rules and the ports to the Port_Group. On success the status becomes ACTIVE if there are ports (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:210). On failure the Port_Group is deleted and the error propagates (500). Despite its name, this hook still runs inside the plugin method's @db_api.CONTEXT_WRITER transaction (neutron_fwaas/services/firewall/fwaas_plugin_v2.py:307), which the driver's inner CONTEXT_WRITER.using(context) joins (neutron_fwaas/services/firewall/service_drivers/driver_api.py:143). So on failure the group row is rolled back too. Only the project's default group, created in its own admin transaction, stays [inferred from the decorator nesting].
  • Flow (agent driver):
    1. (synchronous) The status becomes INACTIVE without ports or without any policy, otherwise PENDING_CREATE. It becomes CREATED when router_distributed = True (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:254).
    2. (asynchronous) If not INACTIVE, a fanout cast create_firewall_group with the group, its ordered rules and the details of its ports (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:268).
    3. (asynchronous) Each L3 agent hosting one of the router ports programs iptables and answers set_firewall_group_status(ACTIVE|DOWN|ERROR) (05 § "create_firewall_group (L3 agent)").
  • Outcome: OVN: ACTIVE or INACTIVE in the response, and the ACLs are in the OVN Northbound DB. Agents: PENDING_CREATE, INACTIVE or CREATED; the final ACTIVE/DOWN/ERROR is observed with GET.
  • Effects visible elsewhere: the internal callback firewall_group AFTER_CREATE (neutron_fwaas/services/firewall/service_drivers/driver_api.py:158) drives the FWaaS logging extension when the log plugin is loaded (neutron_fwaas/services/logapi/common/fwg_callback.py:27).

Technical sheet

  • Request: {"firewall_group": {"name": "fwg1", "ingress_firewall_policy_id": "<uuid>", "egress_firewall_policy_id": "<uuid>", "ports": ["<router interface port uuid>"], "admin_state_up": true}}.
  • Response 201: {"firewall_group": {"id": …, "name": …, "description": "", "status": "ACTIVE", "admin_state_up": true, "shared": false, "ports": [...], "ingress_firewall_policy_id": …, "egress_firewall_policy_id": …, "project_id": …, "tenant_id": …, "created_at": …, "updated_at": …, "revision_number": …, "tags": []}}.
  • Limitations:
    • admin_state_up: false is stored but ignored by the OVN driver: the ACLs are applied anyway (no reference to admin_state_up in neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py);
    • with the agent driver, admin_state_up: false installs the default drop-all policy (neutron_fwaas/services/firewall/service_drivers/agents/drivers/linux/iptables_fwaas_v2.py:158).

PUT /v2.0/fwaas/firewall_groups/{id}

  • Purpose: change the ports or policies of a group, or its name, description, admin_state_up and shared.
  • Preconditions: the group is not PENDING_* (409 FirewallGroupInPendingState, neutron_fwaas/services/firewall/fwaas_plugin_v2.py:362).
  • Business rules:
    • the same policy and port checks as on create, ignoring the group itself for the "port in use" check (neutron_fwaas/services/firewall/fwaas_plugin_v2.py:370);
    • the name cannot become default (409 FirewallDefaultParameterExists, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:750);
    • on the default group, a member may only change ports and shared (subject to policy), and an admin may change everything except name. Otherwise 409 FirewallDefaultObjectUpdateRestricted (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:753);
    • ports replaces the whole list (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:765).
  • Flow (OVN): if the ports or a policy changed, the status goes to PENDING_UPDATE, then synchronously to ACTIVE or INACTIVE (no ports) once the OVSDB transaction succeeds (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:238, neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:251):
    • ports added and removed are applied to the Port_Group;
    • a policy change resets the ACLs to the four defaults and re-adds the rules;
    • a missing Port_Group is recreated.
  • Flow (agents): PENDING_UPDATE when the ports changed and the group has a policy, or when a policy changed and the group has ports (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:278). Then a fanout cast update_firewall_group with add-port-ids, del-port-ids and last-port (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:300). Special case: adding one non-ACTIVE port to the default group sends no cast; the L2 agent applies it when the port comes up (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:327).
  • Outcome: 200. OVN: final status in the response. Agents: PENDING_UPDATE until the agents report.

DELETE /v2.0/fwaas/firewall_groups/{id}

  • Business rules:
    • a group that still has ports cannot be deleted: FirewallGroupInUse (409, message "is still active", neutron_fwaas/services/firewall/fwaas_plugin_v2.py:332);
    • an unknown id returns 204, not 404 (neutron_fwaas/services/firewall/fwaas_plugin_v2.py:326). In practice Neutron's controller fetches the resource first for the policy check, and returns 404 for an id that does not exist (neutron:neutron/api/v2/base.py:582) [inferred: the plugin branch only matters for a concurrent delete];
    • the default group can only be deleted by an admin, otherwise 409 FirewallGroupCannotRemoveDefault (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:813). When an admin deletes it, its two default policies are deleted too, but their four rules are left behind [verified: neutron_fwaas/db/firewall/v2/firewall_db_v2.py:806 deletes only the policies].
  • Flow: OVN: the Port_Group is deleted before the DB row (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:305). Agents: the row is deleted; no cast is sent, since a deletable group has no ports (neutron_fwaas/services/firewall/service_drivers/driver_api.py:170). Then the callback firewall_group AFTER_DELETE.
  • Outcome: 204.

GET /v2.0/fwaas/firewall_groups[/{id}]

  • **Filter on ports**: ?ports=<port uuid> returns the group attached to the port, through the association table (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:855).
  • Side effect: creates the default group of the caller's project if missing.

Default firewall group

For each project, neutron-fwaas lazily creates (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:643):

  • four rules, default ingress ipv4, default ingress ipv6, default egress ipv4, default egress ipv6, built from the [default_fwg_rules] options. By default, ingress is deny and egress is allow, any protocol and address (neutron_fwaas/extensions/firewall_v2.py:28, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:180);
  • the policies default ingress and default egress;
  • the group default (status INACTIVE, no ports), recorded in default_firewall_groups (primary key project_id, neutron_fwaas/db/firewall/v2/models.py:76).

It is created with an admin context, in its own transaction. A concurrent creation is detected by the unique key and ignored (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:698). It is triggered by the list calls and by the creation of any group. The default group is created without driver calls (_create_firewall_group is called directly, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:688):

  • with OVN, it gets a Port_Group only once it is updated with ports;
  • the OVN DB sync skips it (neutron_fwaas/services/firewall/service_drivers/ovn/ovn_db_sync.py:89).

Automatic attachment of new VM ports: when a compute port is bound for the first time (its old binding:vif_type was unbound) and the driver supports it as an L2 port, the plugin adds it to the project's default group (neutron_fwaas/services/firewall/fwaas_plugin_v2.py:257). With the OVN driver is_supported_l2_port is always false, so this never happens on OVN.

Lifecycle: firewall_group (OVN driver)

stateDiagram-v2
  [*] --> CREATED: POST (DB insert)
  CREATED --> INACTIVE: pre-commit, no ports
  CREATED --> PENDING_CREATE: pre-commit, ports given
  PENDING_CREATE --> ACTIVE: post-commit OVSDB txn OK (same request)
  PENDING_CREATE --> [*]: OVSDB txn fails (500, Port_Group and row rolled back)
  ACTIVE --> PENDING_UPDATE: PUT ports or policies
  INACTIVE --> PENDING_UPDATE: PUT ports or policies
  PENDING_UPDATE --> ACTIVE: OVSDB txn OK, ports
  PENDING_UPDATE --> INACTIVE: OVSDB txn OK, no ports
  ACTIVE --> [*]: DELETE (only without ports)
  INACTIVE --> [*]: DELETE

Lifecycle: firewall_group (agent driver)

stateDiagram-v2
  [*] --> INACTIVE: POST without ports or without policy
  [*] --> PENDING_CREATE: POST with ports and policy (legacy routers)
  [*] --> CREATED: POST with ports and policy, router_distributed=True
  PENDING_CREATE --> ACTIVE: set_firewall_group_status (L3/L2 agent, admin_state_up)
  PENDING_CREATE --> DOWN: set_firewall_group_status (admin_state_up=false)
  PENDING_CREATE --> ERROR: driver error on agent (FirewallInternalDriverError)
  ACTIVE --> PENDING_UPDATE: PUT ports/policies, rule or policy change
  PENDING_UPDATE --> ACTIVE: set_firewall_group_status
  PENDING_UPDATE --> INACTIVE: last port removed (last-port)
  PENDING_UPDATE --> ERROR: driver error on agent
  ERROR --> [*]: firewall_group_deleted (agent)
  ACTIVE --> [*]: DELETE (only without ports)
  INACTIVE --> [*]: DELETE

A status sent by an agent that is not ACTIVE, DOWN or INACTIVE is stored as ERROR, and a group in PENDING_DELETE keeps that status (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:51). Nothing in the server sets PENDING_DELETE any more, so that branch is dead [verified: no assignment of PENDING_DELETE outside the agent-side sync code].

Spec / code gaps

GapDeclaredCode
Error codes of POST /fwaas/firewall_groupsapi-ref: 400, 401 (neutron-lib:api-ref/source/v2/fwaas-v2.inc:138)also 403, 404 (FirewallPolicyNotFound, FirewallPolicyConflict), 409 (port checks, reserved name, quota)
DELETE of an unknown groupapi-ref: 404 (neutron-lib:api-ref/source/v2/fwaas-v2.inc:259)plugin returns silently; Neutron's pre-fetch gives 404
insert_rule/remove_rule responseapi-ref documents a policy bodybody is not wrapped in firewall_policy
position of a rulelisted as an attributealways null
standard-attr-fwaas-v2 extensiondefined in neutron-lib and in neutron_fwaas/extensions/firewall_v2_stdattrs.py:19never loaded (class name), not listed in /v2.0/extensions
Routes generated by mapper.resource—GET …/new and GET …/{id}/edit exist for the three collections and have no controller method (routes_artifact in the route table)

Source: reverse-spec-neutron-fwaas/01-rest-api_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.