---
name: alibabacloud-cfw-nat-firewall-protect
description: >
  Manage Alibaba Cloud Firewall (CFW) NAT Firewall (NAT boundary firewall): query firewalls and
  protection status, enable/disable the protection switch, assess which NAT gateways can be
  protected and produce a plan, run the creation pre-check, diagnose route-entry inconsistency
  (route-diff), prepare manual-mode diversion assets, create a firewall (auto or manual vswitch
  diversion), and change engine strict mode. Deleting/releasing a firewall is NOT supported:
  protection can be switched off, but the resource must be released by the user in the console.
  Use when users mention NAT firewall, NAT boundary firewall, NAT gateway protection, protecting
  private-network outbound traffic, opening/closing the NAT firewall switch, assessing how to
  enable protection, or diagnosing creation failures from inconsistent custom route entries.
  管理阿里云云防火墙（CFW）NAT边界防火墙：查询防火墙与防护状态、开启/关闭防护开关、开墙评估与开墙方案、
  创建预检查、路由条目不一致诊断、手动引流准备、创建NAT防火墙、修改引擎模式。不支持删除/释放防火墙，
  需用户在控制台自行操作。适用于NAT防火墙、NAT网关防护、私网出方向防护、开关NAT墙、开墙评估等场景。
license: Apache-2.0
compatibility: >
  Requires aliyun CLI >= 3.3.3 with CFW plugin and Vpc plugin installed.
  Scripts compatible with bash >= 3.2 (macOS default). python3 required for
  the create command's route auto-discovery.
  Compatible engines: qwen-code, qoder, openclaw.
metadata:
  domain: aiops
  owner: cfw-team
  contact: cfw-agent@alibaba-inc.com
allowed-tools: Bash Read
---

## Operation Routing

Identify the user's intent, then route to the matching execution path:

| User Intent | Execution Path |
|---|---|
| Query NAT firewall list / which NAT gateways are protected | `nat-fw-switch.sh query` with filters |
| **Which NAT gateways are unprotected, and how should I protect them?** | `nat-fw-lifecycle.sh assess` (read-only assessment + per-gateway recommendation) |
| **Can THIS NAT gateway be protected? Give me the enablement plan** | `nat-fw-lifecycle.sh assess --nat-gateway-id <id>` (adds the server pre-check + a ready-to-run `plan`) |
| Enable/disable protection for **specific NAT firewalls** | `nat-fw-switch.sh enable/disable --proxy-ids "..."` (three-phase workflow below) |
| Check whether a NAT gateway can have a NAT firewall created | `nat-fw-lifecycle.sh precheck` |
| Query NAT firewall authorization quota | `nat-fw-lifecycle.sh quota` |
| **Diagnose inconsistent custom route entries** (creation failed with `ErrorNatCustomRouteEntryDifferent`, or assess warned about it) | `nat-fw-lifecycle.sh route-diff` (read-only per-table diff + alignment plan; NEVER modifies routes) |
| Prepare manual-mode assets (vswitch + custom route table) | `nat-fw-lifecycle.sh prepare` (idempotent; reuses qualifying assets) |
| Create a NAT firewall for a NAT gateway | Creation workflow: assess -> quota -> precheck -> `nat-fw-lifecycle.sh create` |
| Delete / release a NAT firewall | **NOT SUPPORTED - releasing the resource is the user's own action.** Offer `nat-fw-switch.sh disable` to stop protection instead, and point to the Cloud Firewall console (NAT Boundary Firewall -> Delete) for the actual release. `nat-fw-lifecycle.sh delete` only prints this guidance plus a read-only impact report |
| Rename a NAT firewall / change loose-strict engine mode | `nat-fw-lifecycle.sh update` (built-in verification runs automatically) |

Key concept: one NAT gateway maps to exactly one NAT firewall. The firewall must be **created** before its switch can be toggled. Status `normal` means the switch is open (protecting); `closed` means the switch is off.

## Check CLI Environment

Before any operation, validate the CLI environment:
```bash
bash scripts/validate-cli.sh --check-permission
```

Check results and remediate:
- `cli_installed` = false -> run `--install-guide` for installation instructions
- `cli_version_ok` = false -> CLI version below 3.3.3, reinstall or update
- `auto_plugin_install` = false -> run `aliyun configure set --auto-plugin-install true`
- `credential_valid` = false -> no profile configured, run `aliyun configure` to add one
- `permission_check` = false -> credentials are invalid/expired or the identity lacks `yundun-cloudfirewall:DescribeSecurityProxy`; check AccessKey status and refer to `references/ram-policies.md`

**Manual-mode permission pre-probe:** before the manual diversion workflow (especially before `prepare`), probe the VPC WRITE permissions in one shot - it calls the write APIs with fake resource IDs (nothing is created) and classifies each action as `granted` / `missing` / `unknown`:
```bash
bash scripts/validate-cli.sh --check-permission --mode manual [--region cn-hangzhou]
```
Check `manual_mode_ready` and `missing_required_permissions` in the output. VPC write permissions are granted per-action - this probe prevents discovering `Forbidden.RAM` failures one action at a time mid-workflow. See `references/ram-policies.md` section Manual Mode Preparation Permissions.

Note: `credential_valid` only reflects whether a profile exists in `aliyun configure list`. Real credential validity is verified by `permission_check`, which calls the actual CFW business API - invalid/expired AccessKey will fail there.

**Install or upgrade the CLI (required when `cli_installed` or `cli_version_ok` is false):** this skill requires aliyun CLI >= 3.3.3. The official installer handles both fresh installation and in-place upgrade - re-run it to reach the required version:
```bash
curl -fsSL --connect-timeout 10 --max-time 120 https://aliyuncli.alicdn.com/setup.sh | bash
```
Verify with `aliyun version`. Show the full step-by-step installation guide:
```bash
bash scripts/validate-cli.sh --install-guide
```

After environment checks pass, ensure plugins are up-to-date:
```bash
aliyun plugin update
```

> **API Version Note:** Cloudfw uses CLI plugin mode (`aliyun-cli-cloudfw`). The API version is managed internally by the plugin (actual version: `2017-12-07`). Call CFW commands using the default invocation - do **NOT** pass `--version`. The plugin rejects any external version override and will error with `unchecked version`. The `call_cfw_api` function in `common.sh` is designed accordingly and does not include `--version`.

## Observability

**User-Agent template** (every `aliyun` CLI call must carry it):

```
AlibabaCloud-Agent-Skills/alibabacloud-cfw-nat-firewall-protect/{session-id}
```

**session-id generation rule**: 32-character lowercase hex, generated **once per session** and reused **identically by every call** in that session, so all API calls of one session can be correlated. `scripts/common.sh` implements this on load - it reuses `SKILL_SESSION_ID` when already exported (to join an existing session), otherwise generates one (`uuidgen` with a `/dev/urandom` fallback) and exports `ALIBABA_CLOUD_USER_AGENT` with the template above. All four API wrappers (`call_cfw_api` / `call_vpc_api` / `call_ecs_api` / `call_quotas_api`) pass it via `--user-agent`, so scripted calls need no extra work. Any ad-hoc CLI call outside the scripts MUST add `--user-agent "$ALIBABA_CLOUD_USER_AGENT"` explicitly.

This script-level `--user-agent` injection is the ONLY UA mechanism. Do NOT enable, configure, or disable any global agent-mode or global user-agent setting via `aliyun configure` - UA injection happens per-call, exclusively through the scripts.

> **CLI invocation mode:** Cloudfw / Vpc / Ecs run as CLI plugins that keep PascalCase API names and flags (`aliyun Cloudfw DescribeSecurityProxy --PageNo 1`), and MUST NOT receive `--version`. The `quotas` plugin instead requires kebab-case for BOTH the API name and its flags (`aliyun quotas get-product-quota --product-code vpc --quota-action-code q_e1mq5l`).

## RAM Policy and Permission Handling

The RAM Action prefix for Cloud Firewall is `yundun-cloudfirewall`, NOT `cloudfw`. Read `references/ram-policies.md` for the full permission list (including the two `vpc:Describe*` read permissions used by `create`'s route auto-discovery).

> **Permission Failure Handling:** When any command or API call fails due to permission errors at any point during execution, follow this process:
> 1. Read `references/ram-policies.md` to get the full list of permissions required by this Skill
> 2. Guide the user through requesting the necessary permissions
> 3. Pause and wait until the user confirms that the required permissions have been granted

## Query NAT Firewalls

```bash
bash scripts/nat-fw-switch.sh query [options]
```

Available filters: `--region`, `--status`, `--nat-gateway-id`, `--vpc-id`, `--proxy-id`, `--proxy-name`, `--member-uid`. Each filter accepts a single value. For multi-region queries, make separate calls and merge the results.

Pagination: `--page` (default 1), `--page-size` (default 10, max 50). Check `TotalCount` in the response to determine if more pages exist.

The response returns the raw API JSON with a `ProxyList` array and `TotalCount`. Key fields per entry: `ProxyId`, `ProxyName`, `NatGatewayId`, `NatGatewayName`, `VpcId`, `VpcName`, `RegionNo`, `Status`, `CidrBlock`, `SnatIpList`, `VSwitchId`, `MemberUid`, `Detail`.

**Status semantics** (present in a human-friendly way):

| Status | Meaning |
|---|---|
| `normal` | Protection enabled (switch open) |
| `closed` | Protection disabled (switch closed) |
| `opening` / `closing` | Enabling / disabling in progress |
| `configuring` | NAT firewall being created |
| `deleting` | NAT firewall being deleted |
| `abnormal` | Abnormal - show the `Detail` field and suggest checking the console |

> ** MANDATORY: Table Presentation Rule**
> Whenever presenting NAT firewall query results to the user, you MUST render them as a **Markdown table** - never dump raw JSON, and never describe the assets in prose only. This applies to every query-driven answer: asset inventory requests, impact previews (Phase 1), and result verifications (Phase 3).
>
> **Columns** (fixed order, keep all of them):
> `ProxyId | ProxyName | NatGatewayId | Region | Status | SnatIp | VpcName`
>
> Rules:
> - `Status` must be shown as **English semantics paired with the Chinese console wording**, e.g. `normal (protection on)`, `closed (protection off)`, `abnormal`.
> - `SnatIp` joins `SnatIpList` with `, `.
> - For `abnormal` rows, append the `Detail` content right below the table or in the Status cell - it is the key troubleshooting clue.
> - Below the table, always add one summary line: `Total N | Page X/Y | Filters: ...`. If more pages exist, tell the user explicitly.
> - **Empty result is NOT silence**: if `ProxyList` is empty, still output the table header plus `(no matching records)`, the summary line, and suggest retrying without (or with looser) filters to distinguish "no assets" from "filter too strict".
>
> Template:
>
> ```
> | ProxyId | ProxyName | NatGatewayId | Region | Status | SnatIp | VpcName |
> |---|---|---|---|---|---|---|
> | proxy-bp1xxxx | nat-fw-prod | ngw-bp1xxxx | cn-hangzhou | normal (protection on) | 47.96.x.x | prod-vpc |
>
> Total 1 | Page 1/1 | Filters: status=normal
> ```

## Enable / Disable Switch Workflow

All enable/disable operations follow a three-phase workflow: **Impact Preview -> User Confirmation -> Execute -> Result Verification**. Skipping any phase may lead to unintended changes or unverified outcomes.

> ** MANDATORY: Business Impact Warning**
> Every switch operation (enable AND disable) triggers NAT route switching, causing a **1~2 second interruption of long-lived connections** (short connections unaffected). In Phase 1 you MUST warn the user and recommend operating during business off-peak hours.

> ** MANDATORY: Non-Interactive Execution Rule**
> When running as an Agent (non-interactive mode), `--yes`-style confirmation is implicit. You **MUST** complete ALL three phases **unconditionally**:
> - Phase 1 shows **0 affected firewalls** -> **still execute Phase 2 and Phase 3**. Do NOT stop.
> - Phase 1 shows all firewalls already in desired state -> **still execute Phase 2 and Phase 3**. Do NOT stop.
> - **NEVER** terminate the workflow after Phase 1 alone. NEVER output "no action needed" without completing Phase 2+3.

### Phase 1: Impact Preview

Query the NAT firewalls that will be affected and present a human-friendly summary:

- **enable**: `nat-fw-switch.sh query --status closed` (+ user's region/gateway filters)
- **disable**: `nat-fw-switch.sh query --status normal` (+ user's region/gateway filters)

Summarize to the user:

- If total <= 5: list all (ProxyId, ProxyName, NatGatewayId, RegionNo)
- If total > 5: list 3 representative examples, then summarize the rest by region

Example format:
```
This operation will ENABLE the NAT firewall switch for the following 3 NAT firewalls:

1. proxy-bp1xxxx (nat-fw-prod, NAT gateway ngw-bp1xxxx, cn-hangzhou)
2. proxy-bp2yyyy (nat-fw-test, NAT gateway ngw-bp2yyyy, cn-shanghai)
3. proxy-bp3zzzz (nat-fw-dev, NAT gateway ngw-bp3zzzz, cn-beijing)

WARNING: Switching triggers NAT route changes and causes a 1~2 second interruption
of long-lived connections (short connections unaffected). Recommend off-peak hours.

Confirm execution?
```

For **disable**, additionally warn: once closed, private-to-internet traffic no longer passes through Cloud Firewall - access control policies and threat intelligence on the NAT boundary stop working.

Wait for **explicit user confirmation** before proceeding to Phase 2.

### Phase 2: Execute

After user confirms, run the switch command with the ProxyIds collected in Phase 1:

```bash
bash scripts/nat-fw-switch.sh enable --proxy-ids "proxy-xxxx,proxy-yyyy"
bash scripts/nat-fw-switch.sh disable --proxy-ids "proxy-xxxx"
```

> ** MANDATORY: Idempotent Execution Rule**
> Even if Phase 1 shows all target firewalls already in the desired state (or 0 matches), you **MUST still execute** the command. Do NOT skip Phase 2 based on current status or zero-count results.

> ** KNOWN API BEHAVIOR:** `SwitchSecurityProxy` returns success even for non-existent ProxyIds. A successful API response does NOT prove the switch changed - Phase 3 verification is the only reliable confirmation. See `references/api-errors.md`.

### Phase 3: Result Verification

Poll the firewall status to verify the operation took effect:

1. **Initial wait**: sleep 5 seconds
2. **Query**: `nat-fw-switch.sh query --proxy-id <id>` for each operated proxy (or a filtered list query)
3. **Adaptive polling**: if not transitioned, poll every 5 seconds, maximum 30 seconds total
4. **Report results**:
   - All transitioned: "Done. NAT firewall switch enabled for all 3 NAT firewalls."
   - Partial: "2/3 completed. proxy-bp3zzzz still in status `opening`. Check the Cloud Firewall console to confirm final status."
   - Proxy not found in query results after a switch call: treat as FAILED (API silently accepts unknown IDs) and report to the user with the RequestId.

Expected status transitions:
- enable -> `closed` -> `opening` -> `normal`
- disable -> `normal` -> `closing` -> `closed`

## Assessment (run BEFORE creation)

Assessment happens in **two stages**: a region-wide inventory to pick the gateway, then a deep single-gateway evaluation that produces the enablement plan.

### Stage 1 - inventory (pick the gateway)

When the user asks to enable/configure NAT firewall protection - especially without specifying a gateway - ALWAYS run the read-only assessment first and present its result as tables before touching anything:

```bash
bash scripts/nat-fw-lifecycle.sh assess --region cn-hangzhou [--vpc-id vpc-xxx]
```

### Stage 2 - enablement plan (immediately after the gateway is chosen)

> ** MANDATORY: Plan-First Rule.** As soon as the target gateway is known, run the single-gateway evaluation and present ONE complete plan. Do NOT ask the diversion mode, the name, the switch state or the engine mode as separate sequential questions - the mode is a *conclusion of the evaluation*, not an opening question, and asking it before the evaluation means deciding on incomplete data.

```bash
bash scripts/nat-fw-lifecycle.sh assess --region cn-hangzhou --nat-gateway-id ngw-xxx
```

This adds the authoritative **server-side pre-check** and a `plan` object: `actionable`, `mode`, `gateway` facts, `quota`, `parameters` (recommended diversion + defaults needing confirmation), `blockers`, `risks` (each with `what` / `impact` / `options`), `estimated_duration`, `business_impact`, `reversibility`, the ready-to-run `command`, and `post_steps`. The pre-check result sits in `precheck`: `status`, `item_count`, `items` (ALL items, each with `name` / `display` / `status` / `suggestion`) and `failed_items`. Present every item using its `display` label - there is no need to run the `precheck` subcommand separately. When the gateway already has a firewall, `plan.actionable` is `false` with the existing firewall and the switch-workflow next step.

Present the plan as a compact table (diversion mode + parameters, duration, business impact, blockers, risks), then ask for confirmation with a SINGLE question that bundles the parameters still needing a decision (name / switch after creation / engine mode) plus an option to change the diversion mode. Skip straight to remediation when `blockers` is non-empty - never offer to create.

The report contains, per region:

- **Quota** (`total` / `used`) and **already-protected gateways** (`protected_firewalls`)
- **Every unprotected NAT gateway** (`unprotected_gateways`): VPC, availability zone, EIP count, SNAT/DNAT status, VPC CIDR
- **Free /28 CIDR candidates** inside the VPC (`free_cidr_candidates`) - options for auto mode
- **Eligible existing vswitches** (`manual_candidates`: same zone, >= /28, free IPs > EIP count, bound to a custom route table without a 0.0.0.0/0 entry, and carrying no attached ENI) - options for manual mode. Candidates whose route table holds **business routes** (next hop `Instance` / `HaVip` / `NatGateway` ...) or whose vswitch already hosts ENIs are **dropped** with the reason in `notes`, because manual mode requires a route table freshly created for the firewall. Per candidate: `route_table_clean` (`yes` = empty table, `no` = holds tolerated cross-VPC return routes), `route_table_entry_count`, `route_table_entries` (the actual entries) and `attached_eni_count`. Clean candidates are ranked first, so `suggested_vswitch_id` prefers an empty table; when only a `route_table_clean: no` candidate exists, the recommendation `reason` carries a WARNING - relay it and have the user confirm the pre-existing entries are the intended return routes, or run `prepare` to build clean assets
- **Recommendation** per gateway (`recommendation.mode` = `auto` / `manual` / `none`, with `reason` and `suggested_vswitch_cidr` / `suggested_vswitch_id`)
- **Post-creation quota projection** (`quota_projection` per gateway): whether creation would exhaust VPC resources - custom route tables (`vpc_quota_route_tables_num`, auto mode +1), vswitches (`vpc_quota_vswitches_num`, auto mode +1), SNAT entries (`natgw_quota_snat_entry_num`, +1 in both modes) and **VPN-pointing custom routes** (`vpc_quota_vpn_custom_route_entry`, auto mode inherits the diversion-scope VPN entries into the firewall route table). Each item carries `current` / `limit` / `after_create` / `status` (`ok` / `warning` >= 80% / `exceeded` / `unknown`). Limits are resolved live via Quotas Center (`quotas:GetProductQuota`, opaque `q_*` action codes - see `references/ram-policies.md`) - documentation defaults are deliberately NOT assumed, because customers may have raised their quotas. `status: exceeded` on route tables / vswitches / VPN routes downgrades the recommendation from `auto` to `manual` (or `none`). **Caveat when recommending manual mode as the workaround for VPN-route quota exceeded**: manual mode only avoids the AUTOMATIC inheritance (`+0` in the projection); if the user wants to protect cross-VPC traffic they must manually add the return routes - including the VPN-pointing ones - to the firewall route table, which can still exhaust the same quota. 