Guía de implementación
Palo Alto VM-Series detrás de Gateway Load Balancer en AWS
Inspección de tráfico en AWS con dos firewalls Palo Alto repartidos en dos zonas de disponibilidad. Modo bump-in-the-wire: sin NAT, sin overlay routing y sin que el firewall rutee.
- Zonas
- 2 · una por AZ
- Encapsulado
- GENEVE · UDP 6081
- Health check
- TCP 80
- Cross-zone
- desactivado
- PAN-OS
- 11.1
- Plugin
- vm_series 5.x
ArquitecturaQué se construye
Un Gateway Load Balancer reparte el tráfico hacia dos VM-Series, uno por zona. El firewall no rutea ni reescribe direcciones: recibe el paquete encapsulado en GENEVE, lo inspecciona y lo devuelve al mismo túnel.
La simetría por zona es deliberada. Con cross-zone load balancing desactivado, el tráfico que entra por el endpoint de una zona se inspecciona en el firewall de esa misma zona y vuelve por donde vino. Eso mantiene la sesión en un solo firewall, que es lo que un equipo con estado necesita, y evita cargos de tráfico entre zonas.
fwdata: por eso el health check
llega desde una dirección de esa misma red y es tráfico intrazona.
DireccionamientoCinco subnets por zona
Cada rol necesita su propia subnet porque cada una lleva una tabla de rutas distinta. Mezclarlas es la forma más rápida de crear un bucle entre el endpoint y el workload.
| Rol | Qué vive ahí | AZ a | AZ b |
|---|---|---|---|
| public | NAT gateway | 10.20.0.0/24 | 10.20.10.0/24 |
| mgmt | eth0 del VM-Series | 10.20.1.0/24 | 10.20.11.0/24 |
| fwdata | eth1 del VM-Series y los nodos del GWLB | 10.20.2.0/24 | 10.20.12.0/24 |
| gwlbe | endpoint del GWLB | 10.20.3.0/24 | 10.20.13.0/24 |
| app | workloads | 10.20.4.0/24 | 10.20.14.0/24 |
Qué ruta lleva cada tabla
- app —
0.0.0.0/0al endpoint de su zona. Todo lo que sale del workload va a inspección. - gwlbe —
0.0.0.0/0al NAT gateway, más propagación del virtual private gateway. Es la tabla que decide a dónde va el tráfico ya inspeccionado. - fwdata — solo la ruta local. El GENEVE nace y muere dentro de la VPC.
- edge del VGW — las subnets de app apuntando a su endpoint. Sin esta tabla el tráfico que llega del túnel entra directo al workload sin pasar por el firewall.
AlcanceQué se inspecciona y qué no
Conviene decidirlo antes de escribir la política, porque no los tres caminos salen gratis.
| Camino | Cómo llega a inspección | Estado |
|---|---|---|
| Híbrido on-prem ↔ workload |
Edge route table del virtual private gateway | funciona |
| Egress workload → internet |
0.0.0.0/0 de la tabla de app hacia el endpoint |
funciona |
| Este-oeste app-a ↔ app-b |
Requiere rutas más específicas que la local del VPC |
ver limitaciones |
CostosQué cuesta tener esto encendido
Casi 3 dólares por hora, y cuatro de cada cinco se los lleva la licencia del firewall, no la infraestructura de AWS.
Tarifas de us-east-1: la infraestructura consultada contra la API
de precios de AWS, el software contra la tabla de dimensiones del listing PAYG.
No incluyen transferencia de datos.
| Concepto | USD/hora | × | USD/mes |
|---|---|---|---|
Licencia VM-Series — PAYG, m5.xlarge | 1,1700 | 2 | 1 708,20 |
VM-Series — cómputo m5.xlarge | 0,1920 | 2 | 280,32 |
| NAT gateway | 0,0450 | 2 | 65,70 |
| Conexión VPN site-to-site | 0,0500 | 1 | 36,50 |
Workloads t3.micro | 0,0104 | 2 | 15,18 |
| GWLB endpoint | 0,0100 | 2 | 14,60 |
| IPv4 pública en uso | 0,0050 | 4 | 14,60 |
| EBS gp3 — 2×60 GB + 2×8 GB | 0,0149 | — | 10,88 |
| Gateway Load Balancer | 0,0125 | 1 | 9,12 |
| GWLB — capacidad (LCU mínima) | 0,0040 | 1 | 2,92 |
| Total | 2,9562 | 2 158,03 |
| Si queda encendido | Costo | Composición |
|---|---|---|
| una hora | 2,96 USD | licencia 79 % · cómputo 13 % · red 8 % |
| un día | 70,95 USD | |
| un mes | 2 158 USD |
La licencia manda
El cargo de software del listing PAYG es seis veces el costo de cómputo de la misma instancia: 1,17 contra 0,192 la hora. Se factura por AWS Marketplace y por eso no aparece en la API de precios ni en las estimaciones que solo miran recursos de AWS.
El precio va por tamaño de instancia, no por familia: todas las
xlarge pagan 1,17, las large 0,99, las
2xlarge 1,80 y las 4xlarge 3,69. Bajar de
m5.xlarge a m5.large ahorraría 263 USD al mes en
licencia — pero el GWLB necesita al menos 10.0.2 y una instancia con
suficientes NIC, así que hay que verificar el soporte antes de achicar.
Qué apagar
- Parar los dos firewalls corta el 92 % del gasto por hora — licencia más cómputo. Es la única palanca que mueve la aguja.
- Con todo apagado siguen corriendo 0,20 USD/hora — unos 143 al mes: los NAT gateway, el GWLB, los endpoints, la VPN y las IPv4 públicas cobran por hora aunque no pase un paquete.
- Para un lab intermitente,
terraform destroyy recrear sale mejor que apagar: son 85 recursos y el ciclo completo son minutos.
Referencia del despliegue: AMI PA-VM-AWS-11.1.15, product code
e9yfvyj3uag5uo5j2hjikv74n, listing «VM-Series Next-Gen Virtual
Firewall w/Advanced Threat Prevention (PAYG)».
Fase 1Infraestructura en AWS
terraform init
terraform plan # leerlo entero, incluidas las lineas con "-"
terraform apply
# comprobar que el direccionamiento interno del tunel quedo bien
terraform output pa440_tunnel1
terraform output pa440_tunnel2
# antes de dar por buena cualquier regla de security group
terraform plan -detailed-exitcode # 0 = sin deriva
Lea el plan completo, no solo el resumen. El
Plan: N to add, M to change no distingue entre «agrega un CIDR» y
«revoca los que había»: en un aws_security_group con bloques
ingress inline, un cambio in-place aparece como el conjunto viejo
con - y el nuevo con +.
Los parámetros que no admiten variación:
| Recurso | Valor | Por qué |
|---|---|---|
| Target group | GENEVE puerto 6081 | Es el único protocolo que acepta un GWLB |
| Tipo de destino | ip | Con instance se registra la interfaz primaria, que es la de gestión |
| Health check | TCP puerto 80 | El GWLB no puede consultar un path arbitrario de PAN-OS |
| Stickiness | source_ip_dest_ip_proto | El firewall no reescribe la 5-tupla; el flujo debe volver siempre al mismo equipo |
| ENI de datos | source_dest_check = false | Sin esto AWS descarta el tráfico que no está dirigido a la interfaz |
| Security group | UDP 6081 y TCP 80 desde el CIDR del VPC | GENEVE y health check |
Cuidado con el AMI
Use el parámetro público de SSM del listing al que esté suscrito.
Un AMI de otro listing arranca igual pero factura contra una suscripción que no tiene. Y no lo resuelva con most_recent: en el catálogo de
Palo Alto hay versiones más nuevas cuyo AMI tiene fecha anterior, así que
ordenar por fecha de creación elige la versión equivocada.
Verificación
Los endpoints en Available y la edge route table asociada al virtual private gateway, con las rutas de las subnets de app apuntando a sus endpoints.
TerraformLos recursos que importan
El despliegue completo son 85 recursos. La mayoría es plomería repetida por zona; estos son los que llevan la lógica del diseño.
| Grupo | Recursos | Detalle |
|---|---|---|
| Red base | 1 + 10 | VPC y cinco subnets por zona |
| Tablas de ruta | 8 + 11 rutas | public, mgmt, fwdata, gwlbe, app y la edge del VGW |
| Salida a internet | 1 + 2 + 2 | IGW, NAT gateway y sus EIP |
| GWLB | 5 | balanceador, listener, target group, servicio de endpoint y 2 endpoints |
| Firewalls | 2 + 4 + 2 | instancias, dos ENI cada una y las EIP de gestión |
| VPN | 4 | customer gateway, VGW, conexión y propagación de rutas |
| Seguridad | 3 | un security group por rol |
El balanceador y su target group
El GWLB vive en las subnets fwdata, junto a los firewalls. El
target se registra por IP y no por instancia: con
instance AWS usaría la interfaz primaria, que acá es la de
gestión.
resource "aws_lb" "gwlb" {
load_balancer_type = "gateway"
subnets = [for k, v in aws_subnet.fwdata : v.id]
enable_cross_zone_load_balancing = false
}
resource "aws_lb_target_group" "fw" {
target_type = "ip"
protocol = "GENEVE"
port = 6081
health_check {
protocol = "TCP"
port = 80
}
# El firewall no reescribe la 5-tupla: el flujo debe volver
# siempre a la misma instancia.
stickiness {
type = "source_ip_dest_ip_proto"
enabled = true
}
}
resource "aws_lb_target_group_attachment" "fw" {
for_each = local.subnets
target_id = aws_network_interface.fw_data[each.key].private_ip
availability_zone = local.az[each.key]
port = 6081
}
La asociación de borde
Es la pieza que hace que el tráfico entrante se inspeccione. Una tabla de
rutas asociada al virtual private gateway con gateway_id, no a
una subnet, y con rutas más específicas que la local del VPC.
resource "aws_route" "vgw_edge_to_app" {
for_each = local.subnets
route_table_id = aws_route_table.vgw_edge.id
destination_cidr_block = each.value.app # 10.20.4.0/24 y 10.20.14.0/24
vpc_endpoint_id = aws_vpc_endpoint.gwlbe[each.key].id
}
resource "aws_route_table_association" "vgw_edge" {
gateway_id = aws_vpn_gateway.vgw.id # asociacion de BORDE
route_table_id = aws_route_table.vgw_edge.id
}
Las interfaces del firewall
Dos NIC: gestión en device_index 0 y datos en el 1, que es la
que el VM-Series ve como ethernet1/1. El
source_dest_check va en false solo en la de datos.
resource "aws_network_interface" "fw_data" {
for_each = local.subnets
subnet_id = aws_subnet.fwdata[each.key].id
security_groups = [aws_security_group.fwdata.id]
source_dest_check = false
}
resource "aws_instance" "fw" {
for_each = local.subnets
instance_type = "m5.xlarge"
network_interface { network_interface_id = ...fw_mgmt[each.key].id, device_index = 0 }
network_interface { network_interface_id = ...fw_data[each.key].id, device_index = 1 }
user_data = <<-EOT
type=dhcp-client
hostname=fw-gwlb-${each.key}
dhcp-accept-server-hostname=no
dhcp-accept-server-domain=no
EOT
}
La selección del AMI
Con parámetro público de SSM, nunca con most_recent: en el
catálogo de Palo Alto hay versiones más nuevas cuyo AMI tiene fecha anterior,
así que ordenar por fecha de creación elige la versión equivocada.
data "aws_ssm_parameter" "panos" {
name = "/aws/service/marketplace/prod-<listing>/pan-os-<version>"
}
Los tres security groups
| Grupo | Ingress | Desde |
|---|---|---|
fwdata | UDP 6081, TCP 80, ICMP | CIDR del VPC |
mgmt | TCP 22, TCP 443, ICMP | lista de administración + prefijos on-prem |
app | todo | CIDR del VPC + prefijos on-prem |
El de gestión toma una lista de CIDR, no un string. Con un
solo valor la única salida es agregar la IP real a mano en la consola, y como
los bloques ingress son inline, el siguiente apply
la revoca en silencio.
CódigoLa configuración completa
Los siete archivos tal cual se aplican, con las direcciones públicas y los identificadores de cuenta sustituidos por marcadores. Es el despliegue entero: no hay módulos externos ni estado compartido.
| Archivo | Contenido |
|---|---|
00-variables.tf | Proveedor, variables y el mapa de subnets por zona. |
01-vpc.tf | VPC, las diez subnets, IGW, NAT gateway y los tres security groups. |
02-gwlb.tf | Balanceador, target group, listener, servicio de endpoint y endpoints. |
03-firewalls.tf | AMI por SSM, las ENI, los VM-Series y los workloads. |
04-vpn.tf | Customer gateway, VGW y la conexión con sus dos túneles. |
05-routing.tf | Las seis tablas de rutas, incluida la edge asociada al VGW. |
06-outputs.tf | Salidas, entre ellas el direccionamiento interno de los túneles. |
00-variables.tfProveedor, variables y el mapa de subnets por zona.
terraform {
required_version = ">= 1.5"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
random = { source = "hashicorp/random", version = "~> 3.6" }
}
}
provider "aws" {
region = var.region
default_tags {
tags = {
Project = "kf-gwlb-lab"
ManagedBy = "terraform"
Owner = "csegovia"
}
}
}
variable "region" {
type = string
default = "us-east-1"
}
variable "vpc_cidr" {
description = "Verificado libre contra la tabla de rutas del PA-410."
type = string
default = "10.20.0.0/16"
}
variable "azs" {
type = list(string)
default = ["us-east-1a", "us-east-1b"]
}
# Cinco subnets por AZ. Cada una tiene un rol distinto en el path de GENEVE.
locals {
az = { a = var.azs[0], b = var.azs[1] }
subnets = {
a = {
public = "10.20.0.0/24" # NAT GW
mgmt = "10.20.1.0/24" # eth0 del VM-Series
fwdata = "10.20.2.0/24" # eth1, GENEVE, target del GWLB
gwlbe = "10.20.3.0/24" # endpoints del GWLB
app = "10.20.4.0/24" # workloads
}
b = {
public = "10.20.10.0/24"
mgmt = "10.20.11.0/24"
fwdata = "10.20.12.0/24"
gwlbe = "10.20.13.0/24"
app = "10.20.14.0/24"
}
}
# Rutas app -> GWLBE para cada prefijo del lab, por AZ.
app_lab_routes = {
for p in setproduct(keys(local.subnets), var.lab_cidrs) :
"${p[0]}|${p[1]}" => { az = p[0], cidr = p[1] }
}
}
# --- VPN / BGP ---------------------------------------------------------------
variable "cgw_public_ip" {
description = "IP publica del PA-410 (ethernet1/1)."
type = string
default = "<IP-PUBLICA-CGW>"
}
variable "cgw_asn" {
description = "AS local del PA-410, ya existente en el VR default."
type = number
default = 65000
}
variable "vgw_asn" {
type = number
default = 64512
}
variable "tunnel1_inside_cidr" {
description = "169.254.100.0/30 NO se puede: la usa el peer HEX-CASA."
type = string
default = "169.254.200.0/30"
}
variable "tunnel2_inside_cidr" {
type = string
default = "169.254.201.0/30"
}
variable "lab_cidrs" {
description = "Prefijos del home lab que se alcanzan por el tunel y se inspeccionan."
type = list(string)
default = [
"192.168.100.0/24",
"192.168.106.0/24",
"10.1.0.0/16",
"10.142.0.0/16",
]
}
# --- Firewalls ---------------------------------------------------------------
# Parametro publico de SSM que publica AWS Marketplace. Resuelve al AMI
# correcto por region sin hardcodear IDs.
#
# prod-<ID-DEL-LISTING> = listing "VM-Series Next-Gen Virtual Firewall
# w/Advanced Threat Prevention (PAYG)"
#
# OJO: usar el AMI de OTRO listing hace que la instancia facture contra una
# suscripcion que no tiene. El product code va atado al listing.
variable "panos_ssm_parameter" {
type = string
default = "/aws/service/marketplace/prod-<ID-DEL-LISTING>/pan-os-11.1.15"
}
# Override manual. Si se setea, gana sobre el parametro SSM.
variable "panos_ami_id" {
type = string
default = ""
}
variable "panos_instance_type" {
description = "GWLB necesita minimo 10.0.2. m5.xlarge soporta las NICs necesarias."
type = string
default = "m5.xlarge"
}
variable "key_pair_name" {
type = string
}
# Lista, no string: la IP publica de salida del equipo desde el que se
# administra NO siempre es la de ethernet1/1 del PA-410. Con un solo valor la
# unica salida era agregar la IP real a mano en la consola, y como los bloques
# ingress son inline, el siguiente terraform apply la revocaba en silencio.
variable "admin_cidrs" {
description = "CIDRs con acceso de gestion a los VM-Series (SSH, HTTPS, ICMP)."
type = list(string)
default = ["<IP-PUBLICA-CGW>/32"]
}
01-vpc.tfVPC, las diez subnets, IGW, NAT gateway y los tres security groups.
resource "aws_vpc" "lab" {
cidr_block = var.vpc_cidr
enable_dns_support = true
enable_dns_hostnames = true
tags = { Name = "kf-gwlb-vpc" }
}
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.lab.id
tags = { Name = "kf-gwlb-igw" }
}
# --- Subnets -----------------------------------------------------------------
resource "aws_subnet" "public" {
for_each = local.subnets
vpc_id = aws_vpc.lab.id
cidr_block = each.value.public
availability_zone = local.az[each.key]
tags = { Name = "kf-public-${each.key}" }
}
resource "aws_subnet" "mgmt" {
for_each = local.subnets
vpc_id = aws_vpc.lab.id
cidr_block = each.value.mgmt
availability_zone = local.az[each.key]
tags = { Name = "kf-mgmt-${each.key}" }
}
resource "aws_subnet" "fwdata" {
for_each = local.subnets
vpc_id = aws_vpc.lab.id
cidr_block = each.value.fwdata
availability_zone = local.az[each.key]
tags = { Name = "kf-fwdata-${each.key}" }
}
resource "aws_subnet" "gwlbe" {
for_each = local.subnets
vpc_id = aws_vpc.lab.id
cidr_block = each.value.gwlbe
availability_zone = local.az[each.key]
tags = { Name = "kf-gwlbe-${each.key}" }
}
resource "aws_subnet" "app" {
for_each = local.subnets
vpc_id = aws_vpc.lab.id
cidr_block = each.value.app
availability_zone = local.az[each.key]
tags = { Name = "kf-app-${each.key}" }
}
# --- NAT Gateway por AZ ------------------------------------------------------
# Uno por AZ. Con uno solo compartido el retorno cruza AZ y el path se vuelve
# dificil de explicar. Son US$0.045/hr cada uno: el segundo item mas caro
# despues de los firewalls.
resource "aws_eip" "nat" {
for_each = local.subnets
domain = "vpc"
tags = { Name = "kf-eip-nat-${each.key}" }
depends_on = [aws_internet_gateway.igw]
}
resource "aws_nat_gateway" "nat" {
for_each = local.subnets
allocation_id = aws_eip.nat[each.key].id
subnet_id = aws_subnet.public[each.key].id
tags = { Name = "kf-nat-${each.key}" }
depends_on = [aws_internet_gateway.igw]
}
# --- Security groups ---------------------------------------------------------
resource "aws_security_group" "mgmt" {
name = "kf-sg-mgmt"
description = "Gestion de los VM-Series"
vpc_id = aws_vpc.lab.id
ingress {
description = "HTTPS GUI"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = concat(var.admin_cidrs, var.lab_cidrs)
}
ingress {
description = "SSH"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = concat(var.admin_cidrs, var.lab_cidrs)
}
ingress {
description = "ICMP"
from_port = -1
to_port = -1
protocol = "icmp"
cidr_blocks = concat(var.admin_cidrs, var.lab_cidrs)
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = { Name = "kf-sg-mgmt" }
}
# GOTCHA GWLB #1: si falta el 6081 el target queda unhealthy y AWS no dice
# por que. Es el error mas comun de toda la integracion.
resource "aws_security_group" "fwdata" {
name = "kf-sg-fwdata"
description = "Interfaz de datos: GENEVE + health check del GWLB"
vpc_id = aws_vpc.lab.id
ingress {
description = "GENEVE desde el GWLB"
from_port = 6081
to_port = 6081
protocol = "udp"
cidr_blocks = [var.vpc_cidr]
}
ingress {
description = "Health check del target group"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = [var.vpc_cidr]
}
ingress {
from_port = -1
to_port = -1
protocol = "icmp"
cidr_blocks = [var.vpc_cidr]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = { Name = "kf-sg-fwdata" }
}
resource "aws_security_group" "app" {
name = "kf-sg-app"
description = "Workloads"
vpc_id = aws_vpc.lab.id
ingress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = concat([var.vpc_cidr], var.lab_cidrs)
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = { Name = "kf-sg-app" }
}
02-gwlb.tfBalanceador, target group, listener, servicio de endpoint y endpoints.
# =============================================================================
# Gateway Load Balancer
# El GWLB encapsula en GENEVE (UDP 6081) y reparte hacia los VM-Series.
# Los firewalls trabajan como bump-in-the-wire: no rutean, no hacen NAT.
# =============================================================================
resource "aws_lb" "gwlb" {
name = "kf-gwlb"
load_balancer_type = "gateway"
subnets = [for k, v in aws_subnet.fwdata : v.id]
# Probado en true (2026-08-17) para descartar que fuera la causa de que el
# GWLB no entregue al target: NO cambio nada, el trafico sigue muriendo entre
# el endpoint y el target. Se vuelve a false, que es el diseno buscado
# (inspeccion local por AZ) y evita cargos de trafico cross-AZ.
enable_cross_zone_load_balancing = false
tags = { Name = "kf-gwlb" }
}
# GOTCHA GWLB #2: el health check no puede pegarle a un path cualquiera del
# PAN-OS. Se usa TCP:80 y hay que habilitar HTTP en el interface management
# profile de la interfaz de datos, o el target nunca pasa a healthy.
resource "aws_lb_target_group" "fw" {
name = "kf-gwlb-tg"
target_type = "ip"
protocol = "GENEVE"
port = 6081
vpc_id = aws_vpc.lab.id
health_check {
protocol = "TCP"
port = 80
interval = 10
healthy_threshold = 3
unhealthy_threshold = 3
}
# El firewall no reescribe la 5-tupla, asi que el flujo debe volver siempre
# a la misma instancia.
stickiness {
type = "source_ip_dest_ip_proto"
enabled = true
}
tags = { Name = "kf-gwlb-tg" }
}
# Se registra la IP de la ENI de datos, no el instance-id: con target_type
# "instance" el GWLB usaria la interfaz primaria, que aca es la de gestion.
resource "aws_lb_target_group_attachment" "fw" {
for_each = local.subnets
target_group_arn = aws_lb_target_group.fw.arn
target_id = aws_network_interface.fw_data[each.key].private_ip
availability_zone = local.az[each.key]
port = 6081
}
resource "aws_lb_listener" "gwlb" {
load_balancer_arn = aws_lb.gwlb.arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.fw.arn
}
}
# --- Endpoint service + endpoints por AZ -------------------------------------
resource "aws_vpc_endpoint_service" "gwlb" {
acceptance_required = false
gateway_load_balancer_arns = [aws_lb.gwlb.arn]
tags = { Name = "kf-gwlb-endpoint-service" }
}
resource "aws_vpc_endpoint" "gwlbe" {
for_each = local.subnets
vpc_id = aws_vpc.lab.id
service_name = aws_vpc_endpoint_service.gwlb.service_name
vpc_endpoint_type = "GatewayLoadBalancer"
subnet_ids = [aws_subnet.gwlbe[each.key].id]
tags = { Name = "kf-gwlbe-${each.key}" }
}
03-firewalls.tfAMI por SSM, las ENI, los VM-Series y los workloads.
# No se usa data "aws_ami" con most_recent: en la lista de AMIs de Palo Alto
# la 11.1.15 es una release MAS nueva que la 11.1.6-h35, pero su AMI tiene
# fecha ANTERIOR. Ordenar por CreationDate elige la version equivocada.
data "aws_ssm_parameter" "panos" {
count = var.panos_ami_id == "" ? 1 : 0
name = var.panos_ssm_parameter
}
locals {
panos_ami = var.panos_ami_id != "" ? var.panos_ami_id : data.aws_ssm_parameter.panos[0].value
}
# Dos NICs: gestion y datos. Sin overlay routing, sin NAT, sin interfaz
# untrust. Todo el trafico entra y sale encapsulado en GENEVE por eth1.
resource "aws_network_interface" "fw_mgmt" {
for_each = local.subnets
subnet_id = aws_subnet.mgmt[each.key].id
security_groups = [aws_security_group.mgmt.id]
tags = { Name = "kf-fw-${each.key}-mgmt" }
}
resource "aws_network_interface" "fw_data" {
for_each = local.subnets
subnet_id = aws_subnet.fwdata[each.key].id
security_groups = [aws_security_group.fwdata.id]
source_dest_check = false
tags = { Name = "kf-fw-${each.key}-data" }
}
resource "aws_eip" "fw_mgmt" {
for_each = local.subnets
domain = "vpc"
network_interface = aws_network_interface.fw_mgmt[each.key].id
tags = { Name = "kf-eip-fw-${each.key}-mgmt" }
depends_on = [aws_internet_gateway.igw]
}
resource "aws_instance" "fw" {
for_each = local.subnets
ami = local.panos_ami
instance_type = var.panos_instance_type
key_name = var.key_pair_name
network_interface {
network_interface_id = aws_network_interface.fw_mgmt[each.key].id
device_index = 0
}
network_interface {
network_interface_id = aws_network_interface.fw_data[each.key].id
device_index = 1
}
user_data = <<-EOT
type=dhcp-client
hostname=kf-fw-gwlb-${each.key}
dns-primary=169.254.169.253
dns-secondary=8.8.8.8
dhcp-accept-server-hostname=no
dhcp-accept-server-domain=no
EOT
root_block_device {
volume_size = 60
volume_type = "gp3"
}
tags = { Name = "kf-fw-gwlb-${each.key}" }
}
# --- Workloads ---------------------------------------------------------------
data "aws_ami" "al2023" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-2023.*-x86_64"]
}
}
# Sin IP publica. Se prueban desde el home lab por el tunel, que es
# justamente el escenario que se quiere demostrar.
resource "aws_instance" "app" {
for_each = local.subnets
ami = data.aws_ami.al2023.id
instance_type = "t3.micro"
subnet_id = aws_subnet.app[each.key].id
private_ip = cidrhost(local.subnets[each.key].app, 10)
vpc_security_group_ids = [aws_security_group.app.id]
key_name = var.key_pair_name
user_data = <<-EOT
#!/bin/bash
dnf install -y nginx
echo "kf-gwlb-lab · stack ${each.key} · $(hostname)" > /usr/share/nginx/html/index.html
systemctl enable --now nginx
EOT
tags = { Name = "kf-app-${each.key}" }
}
04-vpn.tfCustomer gateway, VGW y la conexión con sus dos túneles.
# PSK: 8-64 chars, alfanumerico mas . y _ , no puede empezar con 0.
resource "random_password" "psk" {
count = 2
length = 32
special = true
override_special = "._"
numeric = true
upper = true
lower = true
}
resource "aws_customer_gateway" "cgw" {
bgp_asn = var.cgw_asn
ip_address = var.cgw_public_ip
type = "ipsec.1"
tags = { Name = "kf-cgw-pa410" }
}
resource "aws_vpn_gateway" "vgw" {
vpc_id = aws_vpc.lab.id
amazon_side_asn = var.vgw_asn
tags = { Name = "kf-vgw" }
}
resource "aws_vpn_connection" "s2s" {
customer_gateway_id = aws_customer_gateway.cgw.id
vpn_gateway_id = aws_vpn_gateway.vgw.id
type = "ipsec.1"
static_routes_only = false # BGP
# --- Tunel 1 ---------------------------------------------------------------
tunnel1_inside_cidr = var.tunnel1_inside_cidr
tunnel1_preshared_key = random_password.psk[0].result
tunnel1_ike_versions = ["ikev2"]
tunnel1_phase1_encryption_algorithms = ["AES256"]
tunnel1_phase1_integrity_algorithms = ["SHA2-256"]
tunnel1_phase1_dh_group_numbers = [14]
tunnel1_phase1_lifetime_seconds = 28800
tunnel1_phase2_encryption_algorithms = ["AES256"]
tunnel1_phase2_integrity_algorithms = ["SHA2-256"]
tunnel1_phase2_dh_group_numbers = [14]
tunnel1_phase2_lifetime_seconds = 3600
tunnel1_startup_action = "start" # AWS inicia; tenemos IP fija
tunnel1_dpd_timeout_action = "restart"
tunnel1_dpd_timeout_seconds = 30
# --- Tunel 2 ---------------------------------------------------------------
tunnel2_inside_cidr = var.tunnel2_inside_cidr
tunnel2_preshared_key = random_password.psk[1].result
tunnel2_ike_versions = ["ikev2"]
tunnel2_phase1_encryption_algorithms = ["AES256"]
tunnel2_phase1_integrity_algorithms = ["SHA2-256"]
tunnel2_phase1_dh_group_numbers = [14]
tunnel2_phase1_lifetime_seconds = 28800
tunnel2_phase2_encryption_algorithms = ["AES256"]
tunnel2_phase2_integrity_algorithms = ["SHA2-256"]
tunnel2_phase2_dh_group_numbers = [14]
tunnel2_phase2_lifetime_seconds = 3600
tunnel2_startup_action = "start"
tunnel2_dpd_timeout_action = "restart"
tunnel2_dpd_timeout_seconds = 30
tags = { Name = "kf-vpn-pa410" }
}
05-routing.tfLas seis tablas de rutas, incluida la edge asociada al VGW.
# =============================================================================
# ROUTING
#
# El path completo de un flujo de egress:
# app -> GWLBE (misma AZ) -> GWLB -> firewall -> GWLB -> GWLBE -> NAT GW -> IGW
#
# El path de un flujo hibrido entrante:
# home lab -> VGW -> [edge route table] -> GWLBE -> firewall -> GWLBE -> app
# =============================================================================
# --- Public: NAT GW + IGW ----------------------------------------------------
# El retorno del NAT GW tiene que volver por el firewall, si no el flujo queda
# asimetrico y solo se inspecciona la ida.
resource "aws_route_table" "public" {
for_each = local.subnets
vpc_id = aws_vpc.lab.id
tags = { Name = "kf-rt-public-${each.key}" }
}
resource "aws_route" "public_default" {
for_each = aws_route_table.public
route_table_id = each.value.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
resource "aws_route" "public_return_to_fw" {
for_each = local.subnets
route_table_id = aws_route_table.public[each.key].id
destination_cidr_block = each.value.app
vpc_endpoint_id = aws_vpc_endpoint.gwlbe[each.key].id
}
resource "aws_route_table_association" "public" {
for_each = aws_subnet.public
subnet_id = each.value.id
route_table_id = aws_route_table.public[each.key].id
}
# --- Mgmt --------------------------------------------------------------------
# Trafico de gestion, no se inspecciona a proposito: si el firewall se cae,
# igual se puede llegar a administrarlo.
resource "aws_route_table" "mgmt" {
vpc_id = aws_vpc.lab.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
tags = { Name = "kf-rt-mgmt" }
}
resource "aws_route_table_association" "mgmt" {
for_each = aws_subnet.mgmt
subnet_id = each.value.id
route_table_id = aws_route_table.mgmt.id
}
# --- Fwdata ------------------------------------------------------------------
# Solo la ruta local. El GENEVE nace y muere dentro de la VPC.
resource "aws_route_table" "fwdata" {
vpc_id = aws_vpc.lab.id
tags = { Name = "kf-rt-fwdata" }
}
resource "aws_route_table_association" "fwdata" {
for_each = aws_subnet.fwdata
subnet_id = each.value.id
route_table_id = aws_route_table.fwdata.id
}
# --- GWLBE: salida despues de inspeccion -------------------------------------
# Aca cae el trafico ya inspeccionado que devuelve el firewall.
resource "aws_route_table" "gwlbe" {
for_each = local.subnets
vpc_id = aws_vpc.lab.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.nat[each.key].id
}
tags = { Name = "kf-rt-gwlbe-${each.key}" }
}
resource "aws_route_table_association" "gwlbe" {
for_each = aws_subnet.gwlbe
subnet_id = each.value.id
route_table_id = aws_route_table.gwlbe[each.key].id
}
# Los prefijos del lab llegan por BGP. GOTCHA #1 del post anterior sigue
# vigente: la propagacion viene apagada por defecto.
resource "aws_vpn_gateway_route_propagation" "gwlbe" {
for_each = aws_route_table.gwlbe
vpn_gateway_id = aws_vpn_gateway.vgw.id
route_table_id = each.value.id
}
resource "aws_vpn_gateway_route_propagation" "mgmt" {
vpn_gateway_id = aws_vpn_gateway.vgw.id
route_table_id = aws_route_table.mgmt.id
}
# --- App: todo sale por el GWLBE ---------------------------------------------
# Sin propagacion del VGW aca: si se propagaran los prefijos del lab, el
# trafico iria directo al VGW sin pasar por el firewall.
resource "aws_route_table" "app" {
for_each = local.subnets
vpc_id = aws_vpc.lab.id
tags = { Name = "kf-rt-app-${each.key}" }
}
resource "aws_route" "app_default" {
for_each = local.subnets
route_table_id = aws_route_table.app[each.key].id
destination_cidr_block = "0.0.0.0/0"
vpc_endpoint_id = aws_vpc_endpoint.gwlbe[each.key].id
}
resource "aws_route" "app_to_lab" {
for_each = local.app_lab_routes
route_table_id = aws_route_table.app[each.value.az].id
destination_cidr_block = each.value.cidr
vpc_endpoint_id = aws_vpc_endpoint.gwlbe[each.value.az].id
}
resource "aws_route_table_association" "app" {
for_each = aws_subnet.app
subnet_id = each.value.id
route_table_id = aws_route_table.app[each.key].id
}
# --- Edge route table del VGW ------------------------------------------------
# Esto es lo que hace que el trafico hibrido se inspeccione. Sin esta
# asociacion el trafico del tunel entra directo a las subnets app.
resource "aws_route_table" "vgw_edge" {
vpc_id = aws_vpc.lab.id
tags = { Name = "kf-rt-vgw-edge" }
}
resource "aws_route" "vgw_edge_to_app" {
for_each = local.subnets
route_table_id = aws_route_table.vgw_edge.id
destination_cidr_block = each.value.app
vpc_endpoint_id = aws_vpc_endpoint.gwlbe[each.key].id
}
resource "aws_route_table_association" "vgw_edge" {
gateway_id = aws_vpn_gateway.vgw.id
route_table_id = aws_route_table.vgw_edge.id
}
06-outputs.tfSalidas, entre ellas el direccionamiento interno de los túneles.
output "firewalls" {
description = "EIP de gestion de cada VM-Series y la IP de datos registrada en el target group."
value = {
for k in keys(local.subnets) : k => {
mgmt_eip = aws_eip.fw_mgmt[k].public_ip
data_ip = aws_network_interface.fw_data[k].private_ip
}
}
}
output "app_servers" {
description = "Workloads. Sin IP publica: se prueban desde el home lab por el tunel."
value = { for k, v in aws_instance.app : k => v.private_ip }
}
output "gwlb_endpoints" {
value = { for k, v in aws_vpc_endpoint.gwlbe : k => v.id }
}
output "target_group_arn" {
description = "Para vigilar el estado con: aws elbv2 describe-target-health --target-group-arn ..."
value = aws_lb_target_group.fw.arn
}
# --- Datos para armar el tunel en el PA-410 ---------------------------------
# inside_local / inside_remote salen de los atributos que devuelve AWS, no de
# cidrhost(). AWS asigna el PRIMER host del /30 al VGW y el segundo al customer
# gateway; calcularlo a mano invita a dejarlo al reves, que es justo lo que
# paso antes: el PA quedaba con la .1 (que es del VGW) y peereaba contra la .2.
output "pa440_tunnel1" {
value = {
aws_peer_ip = aws_vpn_connection.s2s.tunnel1_address
inside_local = aws_vpn_connection.s2s.tunnel1_cgw_inside_address
inside_remote = aws_vpn_connection.s2s.tunnel1_vgw_inside_address
bgp_peer_asn = var.vgw_asn
}
}
output "pa440_tunnel2" {
value = {
aws_peer_ip = aws_vpn_connection.s2s.tunnel2_address
inside_local = aws_vpn_connection.s2s.tunnel2_cgw_inside_address
inside_remote = aws_vpn_connection.s2s.tunnel2_vgw_inside_address
bgp_peer_asn = var.vgw_asn
}
}
# CREDENCIAL VIVA. Nunca en el blog ni en screenshots.
output "psk_tunnel1" {
value = aws_vpn_connection.s2s.tunnel1_preshared_key
sensitive = true
}
output "psk_tunnel2" {
value = aws_vpn_connection.s2s.tunnel2_preshared_key
sensitive = true
}
Fase 2Configuración del VM-Series
Idéntica en los dos equipos: la interfaz de datos toma su IP por DHCP, así que no hay nada que personalizar por zona.
configure
set network profiles interface-management-profile GWLB-HC http yes
set network profiles interface-management-profile GWLB-HC ping yes
set network interface ethernet ethernet1/1 layer3 dhcp-client enable yes
set network interface ethernet ethernet1/1 layer3 dhcp-client create-default-route no
set network interface ethernet ethernet1/1 layer3 interface-management-profile GWLB-HC
set network virtual-router default interface ethernet1/1
set zone gwlb network layer3 ethernet1/1
El http yes del management profile es lo que hace que el firewall
responda el health check. La ruta de la zona depende de multi-vsys:
con off es set zone; con on es
set vsys vsys1 zone. Verificalo con
show system info | match multi-vsys antes de escribir la línea:
si la zona no se crea, después fallan todas las reglas que la referencian.
Política: una sola zona, todo intrazona
El tráfico entra y sale por la misma interfaz, así que origen y destino caen en la misma zona. La política tiene que permitirlo explícitamente.
El health check necesita su propia regla
Habilitar HTTP en el management profile es necesario pero no suficiente. El
health check sale de un nodo del GWLB en la misma subnet
hacia la IP de datos del firewall: es tráfico intrazona y pasa por la
política. Cualquier regla de cleanup any/any lo mata antes, y el
target queda unhealthy para siempre sin que AWS explique nada.
set service TCP-80 protocol tcp port 80
set address-group GWLB-NODES static [ FWDATA-A FWDATA-B ]
set rulebase security rules GWLB-HEALTHCHECK from gwlb
set rulebase security rules GWLB-HEALTHCHECK to gwlb
set rulebase security rules GWLB-HEALTHCHECK source GWLB-NODES
set rulebase security rules GWLB-HEALTHCHECK destination any
set rulebase security rules GWLB-HEALTHCHECK application any
set rulebase security rules GWLB-HEALTHCHECK service TCP-80
set rulebase security rules GWLB-HEALTHCHECK action allow
move rulebase security rules GWLB-HEALTHCHECK top
El move no es cosmético: las reglas nuevas se agregan al final,
o sea debajo del cleanup, donde nunca matchean.
Para las reglas de inspección use service any. La combinación
application any con service application-default no
matchea nada: sin aplicaciones concretas en la regla no hay puerto default del
cual derivar el servicio.
Verificación
Los dos targets en healthy. Tarda unos 30 segundos con el intervalo por defecto.
aws elbv2 describe-target-health --region <region> \
--target-group-arn <arn>
Fase 3Habilitar el parsing GENEVE
Este es el paso que hace que el diseño funcione, y el único que no produce ningún error si se lo salta.
PAN-OS no desencapsula el GENEVE hasta que se lo pide. Sin eso recibe los paquetes UDP 6081, no ve las direcciones internas, no inspecciona nada y nunca los devuelve al túnel.
En los dos equipos
request plugins vm_series aws gwlb inspect enable yes
request plugins vm_series aws gwlb overlay-routing enable no
show plugins vm_series aws gwlb
GWLB enabled tiene que decir True. El mensaje
vpc-endpoint association not found es normal en
bump-in-the-wire: esa asociación pertenece al modo overlay routing, que
acá se descarta a propósito.
Verificación final
Que el workload responda prueba que el tráfico pasa, no que se inspeccione.
La prueba real está en el log: la aplicación identificada tiene que ser la
real y no not-applicable.
show rule-hit-count vsys vsys-name vsys1 rule-base security rules all
show log traffic rule equal <REGLA>
web-browsing gwlb 49880 <origen>
HIBRIDO-IN allow gwlb 80 <workload>
tcp-fin
No use direction equal backward: devuelve las entradas más
viejas del log, y con los health checks corriendo cada pocos segundos nunca verá ahí su prueba.
BGPEl plano de control del lado híbrido
Solo aplica si además termina una VPN contra la misma arquitectura. Salida real del equipo de borde, con el identificador del router y el peer preexistente sustituidos.
Una sola pantalla con todo
El summary es el comando que más rinde: en una vista da el estado
de cada sesión y cuántos prefijos entran y salen por cada una.
admin@fw-borde> show routing protocol bgp summary
==========
router id: <router-id>
virtual router: default
Local AS: 65000
Install BGP routes: yes
Graceful Restart: supported
Default local preference: 100
mp-bgp-enable: yes
afi-safi-ipv4-unicast: yes
rib-out entries: current 140, peak 142
peer PEER-PREEXISTENTE: AS 65010, Established, IP <inside-peer>
bgpAfiIpv4/unicast pfx: Accepted pfx: 1, Advertised pfx: 54
peer AWS-VGW-1: AS 64512, Established, IP 169.254.200.1
bgpAfiIpv4/unicast pfx: Accepted pfx: 3, Advertised pfx: 43
peer AWS-VGW-2: AS 64512, Established, IP 169.254.201.1
bgpAfiIpv4/unicast pfx: Accepted pfx: 3, Advertised pfx: 43
Lo que hay que leer acá: los dos peers de AWS en Established, 3 prefijos aceptados de cada uno —el VPC y sus dos subnets de app— y 43 anunciados hacia cada uno. Que los dos túneles muestren números idénticos es la señal de que la redundancia está activa y no solo configurada.
El peer que ya existía sigue con su propio contador: Advertised pfx: 54.
Si al agregar el grupo nuevo ese número cambia, tocó una política que era de
otro peer.
El detalle de una sesión
admin@fw-borde> show routing protocol bgp peer
==========
Peer: AWS-VGW-2 (id 5)
virtual router: default
Peer router id: 169.254.201.1
Remote AS: 64512
Peer group: AWS (id 1)
Peer status: Established, for 20118 seconds
Passive: no
Multi-hop TTL: 1
Remote Address: 169.254.201.1:179
Local Address: 169.254.201.2:40411
Prefix limit: 100
Holdtime: 30 (config 30)
Keep-Alive interval: 10 (config 10)
Update messages: in 4, out 5
Total messages: in 2018, out 2318
Last error:
Flap counts: 1, established 1 times
Nexthop set to self: no
----------
remove private AS number: no
----------
Capability: Multiprotocol Extensions(1) value: IPv4 Unicast
Capability: Route Refresh(yes)
Capability: 4-Byte AS Number(65) value: 64512
----------
Prefix counter for: bgpAfiIpv4 / unicast
Incoming Prefix: Accepted 3, Rejected 0, Policy Rej 0, Total 3
Outgoing Prefix: 43
Advertised Prefix: 43
Cuatro líneas que conviene mirar y que suelen pasarse por alto:
Local Address: 169.254.201.2contraRemote Address: 169.254.201.1— la local es la.2. Invertido, el túnel levanta igual y la sesión nunca establece.remove private AS number: no— con todos los ASN privados, quitarlos del AS_PATH elimina la detección de bucles.Flap counts: 1, established 1 times— estableció una vez y no volvió a caer. Un contador que sube apunta a MTU o a inestabilidad del túnel.Accepted 3, Rejected 0, Policy Rej 0— separa «no llegan rutas» de «llegan y las descarta una política de import».
Lo que queda en la tabla de reenvío
admin@fw-borde> show routing route destination 10.20.0.0/16
flags: A:active, ?:loose, C:connect, H:host, S:static, ~:internal, R:rip, O:ospf, B:bgp,
Oi:ospf intra-area, Oo:ospf inter-area, O1:ospf ext-type-1, O2:ospf ext-type-2, E:ecmp, M:multicast
VIRTUAL ROUTER: default (id 1)
==========
destination nexthop metric flags age next-AS
10.20.0.0/16 169.254.201.1 100 A?B 20117 64512
10.20.4.0/24 169.254.201.1 100 A?B 20117 64512
10.20.14.0/24 169.254.201.1 100 A?B 20117 64512
total routes shown: 3
Los tres prefijos entran por los dos túneles, pero en la FIB queda uno solo:
el de mejor camino, con flags A?B —activo, origen incompleto,
aprendido por BGP—. Si ese túnel cae, el otro toma el relevo sin intervención.
Para ver las seis entradas antes de la selección hay que mirar la tabla BGP con
show routing protocol bgp loc-rib.
Si el firewall anuncia y AWS no acepta
Cuando el summary muestra prefijos anunciados pero la consola de
AWS reporta cero rutas aceptadas, el sospechoso es
export-nexthop: con resolve, las rutas reaprendidas
de un tercer peer pueden salir con un next-hop que AWS descarta.
use-self lo corrige.
VerificaciónCómo se ve cuando está bien
Capturas de una implementación funcionando. Las dos que más importan son la asociación de borde y el log con la aplicación identificada: la primera explica por qué el tráfico entra a inspección, la segunda prueba que efectivamente se inspecciona.
En la consola de AWS
Lo que tiene que reportar el plano de control.
Target group · pestaña de destinos

Edge route table · rutas

local del VPC, que es lo que las hace ganar.Edge route table · asociación de borde

Endpoints del Gateway Load Balancer

gwlbe de su zona.Conexión VPN · detalles del túnel

Instancias del despliegue

En los VM-Series
Lo que confirma que hay inspección y no solo tránsito.
Policies · Security

Monitor · Traffic filtrado por regla

not-applicable: App-ID está viendo el paquete interno, o sea el decap GENEVE funciona. Si ahí aparece not-applicable, el tráfico pasa pero no se inspecciona.Network · Interfaces

En el equipo de borde
Solo si además termina una VPN contra la misma arquitectura.
IPSec Tunnels en el equipo de borde

BGP · peers

.2 y el peer en la .1 de cada /30. Arriba, un peer preexistente con su propio uptime: al agregar el peer group nuevo, conviene confirmar que los que ya estaban no flapearon.ErroresDiez que no dan mensaje
Todos aparecieron construyendo esta arquitectura. Ninguno produce un error que apunte a la causa.
E1 · GWLB
El parsing GENEVE no viene habilitado
- Síntoma
- Todo se ve sano y no se inspecciona un solo paquete. Sin errores.
- Causa
- PAN-OS no desencapsula el UDP 6081 hasta que se lo pide.
overlay-routinges un ajuste distinto. - Fix
request plugins vm_series aws gwlb inspect enable yes
E2 · política
El deny final impide que el target llegue a healthy
- Síntoma
Target.Timeoutpermanente, aunque el management profile tenga HTTP.- Causa
- El health check es intrazona y pasa por la política, donde el deny final matchea primero.
- Fix
- Regla de allow para TCP 80 desde las subnets de datos, en la primera posición.
E3 · política
application any con application-default no matchea nada
- Síntoma
- Reglas de inspección en 0 hits desde el día uno, sin explicación.
- Causa
- Sin aplicaciones concretas no hay puerto default del cual derivar el servicio.
- Fix
service any, o listar aplicaciones explícitas.
E4 · política
Las reglas nuevas nacen debajo del deny final
- Síntoma
- Se agrega una regla correcta y no matchea nunca.
- Causa
set rulebase security rulesagrega al final del rulebase.- Fix
move ... topobefore <cleanup>, y verificar el orden antes de commitear.
E5 · CLI
La ruta de la zona cambia según multi-vsys
- Síntoma
Invalid syntaxal crear la zona, y después fallos en cascada pornot a valid reference.- Causa
- Con multi-vsys
offno existe el nodovsysen la raíz. - Fix
set zonecon multi-vsys off;set vsys vsys1 zonecon multi-vsys on.
E6 · híbrido
AWS da el primer host del /30 al gateway, no al cliente
- Síntoma
- IPsec levanta y BGP nunca sale de Connect.
- Causa
- En el
/30interno del túnel, la.1es del virtual private gateway y la.2del customer gateway. - Fix
- Leer los atributos que devuelve AWS en vez de calcular las direcciones a mano.
E7 · híbrido
Una regla any/any final también mata el tráfico intrazona
- Síntoma
- BGP no establece sobre las
/30del túnel. - Causa
- Una regla
from any to anyes de tipo universal: matchea intrazona además de interzona. - Fix
- Regla intrazona explícita para las IP internas del túnel, por encima del deny.
E8 · Terraform
Los ingress inline revocan lo que agregues a mano
- Síntoma
- Se pierde el acceso de gestión después de un
applyque no tocaba ese recurso. - Causa
- Con bloques inline, Terraform es autoritativo sobre el security group entero.
- Fix
- Que la variable de acceso sea una lista y que las IP vivan en el código. Verificar con
terraform plan -detailed-exitcode.
E9 · CLI
Un PTY de 80 columnas corrompe los comandos largos
- Síntoma
Invalid syntaxintermitente en comandos que son correctos.- Causa
- El CLI wrapea insertando un espacio en el medio:
ethernet1/1llega comoethern et1/1. - Fix
- Agrandar el PTY antes del spawn, o bajar con
edit <nodo>para acortar cada línea.
E10 · logs
direction equal backward muestra lo más viejo
- Síntoma
- El tráfico de prueba no aparece en el log aunque la regla sume hits.
- Causa
- Devuelve las entradas más antiguas, y el log está dominado por health checks.
- Fix
show log traffic rule equal <NOMBRE>
LimitacionesLo que este diseño no resuelve
El este-oeste entre subnets de app no se inspecciona
En la tabla de rutas de cada subnet de app, la ruta local del
VPC le gana por longest-prefix match al default hacia el endpoint. El
tráfico entre workloads va directo, y la regla que lo contempla queda en
cero hits.
Agregar rutas más específicas que la local no alcanza: cada dirección
entraría por el endpoint de su propia zona y sería inspeccionada por un
firewall distinto. Con un equipo con estado eso no funciona, y el
stickiness no lo salva porque al invertirse origen y destino
el hash cambia. Las salidas reales son mandar el este-oeste por un solo
endpoint —perdiendo independencia entre zonas— o aceptar que ese camino no
se inspecciona y sacar la regla para no prometer algo que no ocurre.
Sin MSS clamp en las interfaces de túnel
si además termina una VPN contra el mismo equipo, tenga en cuenta que
adjust-tcp-mss no existe bajo tunnel units en PAN-OS
11.1: el nodo solo aparece en subinterfaces ethernet. Sin clamp el ping
pasa y las transferencias TCP grandes se cuelgan. La alternativa es
clamear en las interfaces LAN de ingreso, a costa de afectar todo su
tráfico.
Los workloads dependen de la inspección para aprovisionarse
Su salida a internet pasa por el endpoint, el GWLB y el firewall. Mientras
la inspección no funcione no tienen internet: cloud-init no
alcanza el metadata service y el user_data nunca corre, sin
ningún error visible en la consola. Por eso la prueba de extremo a extremo
va al final del procedimiento y no en el medio.