Skip to content
Back to skills

Firewall Networking

ASecurity

Firewall rules and service-to-service communication on Control Plane. Use when the user asks about inbound/outbound rules, CIDR whitelisting, IP blocking, hostname filtering, geo-blocking, header routing, internal endpoints, or network security.

  • 10 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added August 31, 2026
devopsrustgoawstestingapifrontendbackendsecurity

Works with

  • cli
  • api
  • mcp

Security analysis

A100/100

Scanned October 1, 2026

npx -y skills add controlplane-com/ai-plugin --skill firewall-networking --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Firewall Networking?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Firewall Networking
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/controlplane-com-firewall-networking/badge)](https://www.skillsdirectory.com/skills/controlplane-com-firewall-networking)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: firewall-networking
description: "Firewall rules and service-to-service communication on Control Plane. Use when the user asks about inbound/outbound rules, CIDR whitelisting, IP blocking, hostname filtering, geo-blocking, header routing, internal endpoints, or network security."
---

# Firewall & Networking

Deep detail for `spec.firewallConfig` and the enforcement model behind it; the `workload` skill owns the summary (deny-by-default, exposure decided at create time, LB picker). Set `firewallConfig` with `create_workload` / `update_workload` — or `public: true`, the shortcut that opens inbound AND outbound to `0.0.0.0/0` (mutually exclusive with an explicit `firewallConfig`). A firewall change creates a new deployment version — a rolling replace, live in about a minute (`vm` workloads are the exception: firewall updates apply in place without restarting the VM).

## How rules are enforced

**Inbound** is checked per request at the mesh sidecar. It counts as fully open only when `inboundAllowCIDR` contains the literal `0.0.0.0/0` AND `inboundBlockedCIDR` is empty; anything else is allow-list mode. Blocked beats allowed; a bare IP means /32. Header and geo filters apply to HTTP traffic only — `tcp`-protocol ports are CIDR-filtered at the connection level instead.

**Outbound** has two separate paths, which is why CIDR rules beat hostname rules:

- **CIDR path** — traffic to `outboundAllowCIDR` ranges bypasses the sidecar and exits directly, on ALL ports unless `outboundAllowPort` is set.
- **Hostname path** — everything else transits the sidecar, which only admits `outboundAllowHostname` entries, matched by Host header (HTTP) or TLS SNI, on ports 80, 443, and 445 (SMB) by default.
- `outboundBlockedCIDR` is subtracted at the network layer and beats both paths — an allowed hostname that resolves into a blocked range still fails.
- Outbound is fully open only with the literal `0.0.0.0/0` in `outboundAllowCIDR`.

## External inbound

```yaml
firewallConfig:
  external:
    inboundAllowCIDR:        # max 250 entries; deduped and sorted on save
      - 0.0.0.0/0            # or specific: 203.0.113.0/24, 198.51.100.10
    inboundBlockedCIDR:      # no max; wins over the allow list
      - 192.0.2.0/24
```

## External outbound

```yaml
firewallConfig:
  external:
    outboundAllowCIDR:
      - 198.51.100.0/24      # all ports open to this range while outboundAllowPort is unset
    outboundAllowHostname:   # lowercase; single wildcard on the prefix only; max 128 chars
      - api.stripe.com
      - "*.amazonaws.com"
    outboundBlockedCIDR:
      - 203.0.113.7
```

Source-verified traps:

- **`outboundAllowPort` REPLACES the hostname defaults 80/443/445** — re-list 80 and 443 if you still need them. It also restricts the CIDR path to the listed ports. `protocol` is required (`http`, `https`, or `tcp` — how the proxy treats the port); `number` must be 80 to 65000 and not platform-reserved (8012, 8022, 9090, 9091, 15000, 15001, 15006, 15020, 15021, 15090, 41000).
- **Ports below 80 (22, 25, 53) cannot be listed.** To reach a low port, allow the CIDR and leave `outboundAllowPort` unset — the CIDR path then opens all ports.
- **Private ranges are silently stripped from `outboundAllowCIDR` on managed locations** (10/8, 172.16/12, 192.168/16, 127/8, 169.254/16, 100.64/10, IPv6 ULA): allowing them does nothing, with no error. Reaching a VPC or datacenter takes a wormhole agent (`native-networking`). BYOK clusters keep private ranges.

## Header filters (inbound, HTTP only)

Each filter names a header `key` (max 128 chars) plus exactly ONE of `allowedValues` or `blockedValues` — RE2 regexes; anchor with `^...$` (a bare `bar` also matches `barbell`).

```yaml
firewallConfig:
  external:
    inboundAllowCIDR: [0.0.0.0/0]
    http:
      inboundHeaderFilter:
        - key: x-api-version
          allowedValues: ["^v2$"]
        - key: user-agent
          blockedValues: ["^BadBot.*", "^Scraper.*"]
```

Matching is OR across everything: a request is rejected if ANY `blockedValues` pattern matches (checked first), and — once at least one allow filter exists — admitted only if ANY `allowedValues` pattern matches. Two allow filters on different headers are alternatives, not both-required; a request missing the header fails its allow filter. **Mesh-internal traffic (10.0.0.0/8 sources) bypasses header filters entirely** — test from outside, not from another workload.

## Geo filtering (country / region / city / ASN)

Two steps: enable geo headers on the workload load balancer (you pick the header names), then filter on those names:

```yaml
spec:
  loadBalancer:
    geoLocation:
      enabled: true
      headers:               # at least one; names unique; values overwrite client-sent headers
        country: x-country
  firewallConfig:
    external:
      inboundAllowCIDR: [0.0.0.0/0]
      http:
        inboundHeaderFilter:
          - key: x-country
            allowedValues: ["^US$", "^CA$"]
```

The proxy resolves values from MaxMind GeoLite2 on each request: `country` is the two-letter ISO code (`US`, never `United States`), `region` the subdivision code, `city` the English city name, `asn` the AS number. Echo the headers from the app once before writing filters. HTTP ports only.

## Internal firewall (workload to workload)

`internal.inboundAllowType`: `none` (default), `same-gvc`, `same-org`, or `workload-list`. The admitted identity is the calling workload itself — all its replicas.

```yaml
firewallConfig:
  internal:
    inboundAllowType: workload-list
    inboundAllowWorkload:
      - //gvc/GVC/workload/frontend      # GVC segment REQUIRED; //workload/NAME is rejected
      - /org/ORG/gvc/OTHER-GVC/workload/backend
      - cpln://internal/keda             # required when a KEDA trigger source is a CP workload
      - //agent/DC-AGENT                 # inbound from behind a wormhole agent (native-networking)
```

- `mcp__cpln__allow_workload_access` admits named callers without hand-editing this block.
- `inboundAllowWorkload` is honored under `same-gvc` too — add specific cross-GVC callers without going `same-org`.
- Links are validated for shape only, never existence — a typo silently denies the caller.
- Internal calls use `http://WORKLOAD.GVC.cpln.local:PORT` (the container port) — plain `http://`, the sidecar adds mTLS. Cross-GVC calls may span locations and then incur egress charges.

## Load balancers (summary)

| Type | Scope | Ports | Static IPs | Wildcard hosts |
|---|---|---|---|---|
| Shared (default) | all workloads | HTTP/HTTPS on 80/443 | no | no |
| Direct | per workload | TCP/UDP, externalPort 22 to 32768 | via IP set | no |
| Dedicated | per GVC (`update_gvc`) | custom domain ports/protocols | via IP set | yes |

```yaml
spec:
  loadBalancer:
    direct:
      enabled: true
      ports:
        - externalPort: 5432   # 22 to 32768
          protocol: TCP        # TCP or UDP
          containerPort: 5432
```

Direct LB does not terminate TLS (the workload owns its certificates), and its traffic still passes the inbound CIDR rules. `geoLocation` and `replicaDirect` (stateful only) also live under `spec.loadBalancer`. Dedicated LB is a GVC setting (`loadBalancer.dedicated: true`, charged per location) that also carries `trustedProxies` (0 to 2 — which X-Forwarded-For hop counts as the client IP for logging) and a GVC-level `ipSet`. Static IPs and full LB detail: `ipset-load-balancing`.

## Verify

1. `mcp__cpln__get_resource` (kind="workload") — read `spec.firewallConfig` before changing it, and send the COMPLETE desired `firewallConfig` on update (it replaces as a unit, not field-by-field).
2. `mcp__cpln__list_deployments` — wait for the new version to report ready in every location.
3. Probe inbound with `curl` from an allowed and a blocked vantage. For an outbound probe from inside the container, use the `cpln` CLI after reading the `cpln` skill.

## Troubleshooting

| Symptom | Cause / fix |
|---|---|
| Outbound to a VPC/private IP fails though its CIDR is allowed | Private ranges are stripped on managed locations — use a wormhole agent (`native-networking`) |
| Hostname egress broke after adding `outboundAllowPort` | The list replaced 80/443/445 — add 80/443 back |
| Need outbound to port 22/25/53 | Below the allowed 80-65000 range — allow the CIDR and leave `outboundAllowPort` unset |
| Header/geo filter not enforced in tests | Testing from another workload (10.0.0.0/8 bypasses header filters), or the port is `tcp` protocol (filters are HTTP-only) |
| Geo allow-list blocks everyone | Values are ISO codes (`^US$`) — full country names never match; echo the header to confirm |
| `workload-list` caller still denied | Link is missing the GVC segment, or has a typo (existence is never validated) |
| KEDA scaler cannot reach its workload trigger source | Add `cpln://internal/keda` to that workload's `inboundAllowWorkload` |
| Firewall seems ignored for one container | `runAsUser: 1337` escapes the mesh and its firewall (see `workload`) |

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.