agentsclimarketplace

Network translation

Skill geoffsdesk/portage/skills/network-translation

Agent-driven EKS to GKE migrations. PSO-grade outcomes, no PSO required.

Install
npx -y skills add geoffsdesk/portage --skill network-translation

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Translate EKS network constructs to GKE equivalents. Maps ALBs/NLBs to GKE Gateways and Services, AWS Load Balancer Controller annotations to Gateway/Service annotations, Route 53 records to Cloud DNS, security groups to VPC firewall rules and NetworkPolicy, and AWS WAF rules to Cloud Armor policies. Produces translated manifests plus a network design diff. Use after gke-landing-zone, when "translate our ingress to GKE", "convert ALB ingresses to Gateway API", or "what's the network plan for the migration".

SKILL.md

22.9 KB, as published. Nobody here has run it

Network Translation

You translate EKS networking — load balancers, ingress, services, security groups, DNS, WAF — into the GKE equivalents. You produce diff'd manifests and a network design document.

Purpose

Every Service, Ingress, ALB/NLB, Route 53 record, security group, and WAF rule that crosses traffic into the EKS cluster needs a GKE counterpart. This skill produces that mapping with auditable manifests.

When to use this skill

  • Phase 2, after gke-landing-zone.
  • The user asks to "translate ingress", "set up GKE Gateway equivalents", "design the GKE network".

Do NOT use to translate identity (that's identity-translation) or to translate workloads (that's workload-translation).

Prerequisites

  • 01-discovery/inventory.json with the network section populated.
  • 03-landing-zone/plan.md with VPCs, subnets, and clusters defined.
  • gcloud CLI; kubectl context for both source and target clusters.

Procedure

Step 1 — Build the source-side network model

From inventory.json, enumerate per cluster:

  • ALBs: ARN, originating Ingress, hostnames (spec.rules[].host), TLS cert ARN, target groups, target type (instance/ip), listener rules, attached WAF.
  • NLBs: ARN, originating Service, hostnames (Route 53), target groups, port mappings.
  • Internal vs internet-facing.
  • VPC ID and subnets the LBs live in.
  • Security groups attached to LBs and to nodes.
  • Route 53 records pointing at LB DNS names (and which hosted zones).
  • AWS WAF web ACLs and their rules.

Step 2 — Map LBs to GKE constructs

Default mapping:

EKS sourceGKE target
Internet-facing ALB (HTTP/S, with host/path rules)Gateway of class gke-l7-global-external-managed (or gke-l7-regional-external-managed) plus one or more HTTPRoutes
Internal ALBGateway of class gke-l7-rilb (regional internal)
Internet-facing NLB (TCP/UDP)Service type: LoadBalancer with cloud.google.com/l4-rbs: "enabled" annotation (regional external pass-through) or gke-l4-global-external-managed Gateway
Internal NLBService type: LoadBalancer with networking.gke.io/load-balancer-type: "Internal"
Ingress with kubernetes.io/ingress.class: albHTTPRoute attached to a Gateway
service.beta.kubernetes.io/aws-load-balancer-type: externaldrop annotation; pick Gateway or LB Service per type

Produce one Gateway per (LB, scope) tuple. Multiple Ingresses fronting the same ALB merge into multiple HTTPRoutes attached to the same Gateway.

Example translation:

Source (EKS):

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
  namespace: storefront
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]'
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-1:...:certificate/...
    alb.ingress.kubernetes.io/wafv2-acl-arn: arn:aws:wafv2:us-east-1:...:regional/webacl/...
    alb.ingress.kubernetes.io/healthcheck-path: /healthz
spec:
  rules:
    - host: shop.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service: { name: web, port: { number: 80 } }

Target (GKE):

---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: storefront-external
  namespace: storefront
  annotations:
    networking.gke.io/certmap: storefront-cert-map        # if using Certificate Manager
spec:
  gatewayClassName: gke-l7-global-external-managed
  listeners:
    - name: https
      protocol: HTTPS
      port: 443
      tls:
        mode: Terminate
        certificateRefs:
          - kind: Secret
            name: shop-example-com-tls   # OR use Google-managed cert via certmap
      allowedRoutes:
        namespaces: { from: Same }
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: web
  namespace: storefront
spec:
  parentRefs:
    - name: storefront-external
  hostnames: [ "shop.example.com" ]
  rules:
    - matches:
        - path: { type: PathPrefix, value: / }
      backendRefs:
        - name: web
          port: 80
---
apiVersion: networking.gke.io/v1
kind: HealthCheckPolicy
metadata:
  name: web
  namespace: storefront
spec:
  default:
    config:
      type: HTTP
      httpHealthCheck:
        port: 80
        requestPath: /healthz
  targetRef:
    group: ""
    kind: Service
    name: web
---
apiVersion: networking.gke.io/v1
kind: GCPBackendPolicy
metadata:
  name: web
  namespace: storefront
spec:
  default:
    securityPolicy: shop-armor   # Cloud Armor policy
  targetRef:
    group: ""
    kind: Service
    name: web

Step 3 — TLS / certificates

Decide per hostname:

  • Google-managed certificate (Certificate Manager) — preferred for any hostname you control via Cloud DNS or you can prove with DNS01.
  • Self-managed certificate — when the cert needs to come from your CA, or when ACME issuance is centralized elsewhere.

Plan the migration of every ACM-issued cert: usually re-issue in Certificate Manager rather than export/import (private keys can't always be exported).

Step 4 — Cloud Armor (replaces AWS WAF)

No Google-published AWS WAF → Cloud Armor mapping exists. Both Cloud Armor preconfigured WAF rules and Cloud Armor rule tuning reference OWASP CRS lineage, not AWS Managed Rule Groups. The mappings below are tool-opinionated heuristics derived from the OWASP coverage of each side. Validate against real traffic; do not present them to a customer as Google guidance.

Step 4.1 — Pick the CRS version

Cloud Armor publishes preconfigured rules from three CRS versions:

CRSSuffixNotes
4.22-v422-stable, -v422-canaryRecommended by Google. The nodejs rule from v3.3 was reclassified as generic in v4.22 (same 934-prefixed IDs).
3.3-v33-stable, -v33-canaryHas separate java and nodejs rules. Useful for JSON-SQLi pre-filters (see Step 4.4).
3.0-stable, -canary (no version suffix)Legacy. Avoid for new policies.

Default to v4.22. Choose canary for new tunings; promote to stable after a soak.

Step 4.2 — Map AWS Managed Rule Groups (heuristic)

AWS WAF managed groupClosest Cloud Armor preconfigured rulesNotes
AWSManagedRulesCommonRuleSetxss-v422-stable, lfi-v422-stable, protocolattack-v422-stable, scannerdetection-v422-stable, methodenforcement-v422-stableCommon ruleset spans many categories; combine multiples
AWSManagedRulesKnownBadInputsRuleSetprotocolattack-v422-stable, scannerdetection-v422-stableKnown-bad inputs and scanners
AWSManagedRulesSQLiRuleSetsqli-v422-stable (+ json-sqli-canary if JSON bodies)See JSON-SQLi note in Step 4.4
AWSManagedRulesPHPRuleSetphp-v422-stable
AWSManagedRulesUnixRuleSetrce-v422-stable, lfi-v422-stableRCE + LFI cover Unix attack surface
AWSManagedRulesWindowsRuleSetrce-v422-stableNo Windows-specific group; RCE covers OS injection
AWSManagedRulesLinuxRuleSetrce-v422-stable, lfi-v422-stable
AWSManagedRulesAnonymousIpList(No 1:1) — use Cloud Armor origin.region_code + IP allow-listsHeuristic; consider Cloud Armor Adaptive Protection
AWSManagedRulesAmazonIpReputationList(No 1:1) — Adaptive Protection + custom CEL deny
AWSManagedRulesBotControlRuleSetreCAPTCHA Enterprise integration with Cloud ArmorDifferent UX; surface as design decision
Custom regex / string-match rulesCEL expression(s) on the security policyTranslate each rule individually
Rate-based rulesrate_limit_options (action rate_based_ban or throttle)Not a preconfigured-rule expression

Surface this mapping in the network-design doc explicitly as "heuristic — review per workload."

Step 4.3 — Tuning level (paranoia)

Sensitivity is the OWASP CRS paranoia level: 04. Lower = higher confidence, fewer false positives. Default is 4 (all signatures evaluated). Selecting N opts in all signatures with sensitivity <= N.

CEL syntax:

evaluatePreconfiguredWaf('sqli-v422-stable', {'sensitivity': 1})

Production starting points (tool-opinionated):

  • xss-v422-stable at sensitivity 2.
  • sqli-v422-stable at sensitivity 2; also enable json-sqli-canary if API accepts JSON bodies.
  • lfi-v422-stable, rfi-v422-stable, rce-v422-stable at sensitivity 2.
  • protocolattack-v422-stable at sensitivity 2.
  • methodenforcement-v422-stable at sensitivity 1.
  • scannerdetection-v422-stable at sensitivity 1 (3+ produces noise).

Opt-out specific noisy signatures with opt_out_rule_ids rather than dropping the whole rule:

evaluatePreconfiguredWaf('sqli-v422-stable', {
  'sensitivity': 4,
  'opt_out_rule_ids': ['owasp-crs-v042200-id942350-sqli', 'owasp-crs-v042200-id942360-sqli']
})

The signature-ID prefix must match the CRS version of the rule set (v042200 for v4.22, v030301 for v3.3, v030001 for v3.0).

Step 4.4 — JSON-SQLi (Cloud Armor's known WAF-bypass)

Bodies must be JSON-parsed for SQLi to be caught in JSON. Google's recommended pattern is to use both:

evaluatePreconfiguredWaf('json-sqli-canary', {'sensitivity':0, 'opt_in_rule_ids':['owasp-crs-id942550-sqli']})
||
evaluatePreconfiguredWaf('sqli-v33-stable', {'sensitivity': 2})

If your application accepts JSON request bodies and any SQL-touching backend, both go in.

Step 4.5 — CVE rules and cve-canary

cve-canary ships canary-only and is opt-in by signature ID. Log4j and React RCE coverage:

Signature IDSensitivityWhat it catches
owasp-crs-v030001-id044228-cve1Base rule for CVE-2021-44228 / CVE-2021-45046 (Log4j)
owasp-crs-v030001-id144228-cve1Google enhancements for bypass / obfuscation
owasp-crs-v030001-id244228-cve3Higher-sensitivity bypass detection (some FPs)
owasp-crs-v030001-id344228-cve3Base64-encoded bypass detection (some FPs)
google-mrs-v202512-id000001-rce / google-mrs-v202512-id000002-rcen/aReact RCE; gate behind a request pre-filter (below)

React RCE recommended pre-filter (verbatim):

(has(request.headers['next-action']) || has(request.headers['rsc-action-id']) ||
 request.headers['content-type'].contains('multipart/form-data') ||
 request.headers['content-type'].contains('application/x-www-form-urlencoded'))
&& evaluatePreconfiguredWaf('cve-canary', {'sensitivity': 0,
   'opt_in_rule_ids': ['google-mrs-v202512-id000001-rce','google-mrs-v202512-id000002-rce']})

Step 4.6 — Terraform skeleton

resource "google_compute_security_policy" "shop_armor" {
  project = var.cluster_project
  name    = "shop-armor"

  # OWASP CRS v4.22 baseline
  rule {
    action   = "deny(403)"
    priority = 1000
    match { expr { expression = "evaluatePreconfiguredWaf('sqli-v422-stable', {'sensitivity': 2})" } }
  }
  rule {
    action   = "deny(403)"
    priority = 1010
    match { expr { expression = "evaluatePreconfiguredWaf('xss-v422-stable', {'sensitivity': 2})" } }
  }
  rule {
    action   = "deny(403)"
    priority = 1020
    match { expr { expression = "evaluatePreconfiguredWaf('protocolattack-v422-stable', {'sensitivity': 2})" } }
  }
  rule {
    action   = "deny(403)"
    priority = 1030
    match { expr { expression = "evaluatePreconfiguredWaf('rce-v422-stable', {'sensitivity': 2})" } }
  }
  rule {
    action   = "deny(403)"
    priority = 1040
    match { expr { expression = "evaluatePreconfiguredWaf('lfi-v422-stable', {'sensitivity': 2})" } }
  }

  # Rate limiting (per-IP throttle)
  rule {
    action   = "rate_based_ban"
    priority = 2000
    rate_limit_options {
      rate_limit_threshold { count = 200, interval_sec = 60 }
      enforce_on_key       = "IP"
      ban_duration_sec     = 600
      conform_action       = "allow"
      exceed_action        = "deny(429)"
    }
    match {
      versioned_expr = "SRC_IPS_V1"
      config { src_ip_ranges = ["*"] }
    }
  }

  # Default allow
  rule {
    action      = "allow"
    priority    = 2147483647
    match { versioned_expr = "SRC_IPS_V1" config { src_ip_ranges = ["*"] } }
    description = "default rule"
  }
}

Attach via GCPBackendPolicy.spec.default.securityPolicy on the relevant Service.

Step 4.7 — Operational gotchas Google calls out

  • Rule changes take "several minutes to propagate." Plan accordingly.
  • Cloud Armor evaluates preconfigured rules against the first 64 KB of request body. Larger payloads are not inspected end-to-end.
  • JSON parsing must be enabled to inspect JSON bodies.
  • The --request-*-to-exclude controls (header / cookie / query / URI) cannot be used on rules with action allow.
  • Adaptive Protection is enabled at the security-policy level (--enable-layer7-ddos-defense); it doesn't expose a per-rule CEL expression but ML-suggests rules in alerts.

Step 5 — DNS

For every Route 53 record that points at an LB DNS name:

  1. Plan a Cloud DNS record in the target zone (or the same zone, depending on TLD ownership). For apex hostnames, GKE Gateway gives you an IP; use an A record. For subdomains, you can CNAME to the Gateway address — but you'll need a deterministic Gateway hostname or static IP.
  2. For weighted DNS (used by traffic-cutover), use Cloud DNS routing policies (weighted_round_robin) or move the records to a CDN/global LB that supports weighted backends natively.
  3. If the existing zone is in Route 53 and you can't move it yet, plan delegation via NS records, or configure the cutover to update Route 53 directly during traffic-cutover.

Capture every DNS change as a dns-plan.md under the run directory.

Step 6 — Security groups → VPC firewall + NetworkPolicy

EKS security groups attached to nodes and LBs map to:

  • Node-to-node traffic: GKE handles intra-cluster networking via the VPC subnet's firewall rules. The Shared VPC's hierarchical firewall plus gke-cluster-name- rules cover most.
  • External-to-LB: GFE health check ranges and your allow-listed CIDRs become VPC firewall INGRESS rules targeting the LB's network tag.
  • Pod-to-pod within cluster: implement as Kubernetes NetworkPolicy (Dataplane V2 enforces). Translate any aws-load-balancer-controller annotation that restricts pod ingress into a NetworkPolicy.

Produce per-namespace NetworkPolicy YAML restricting pod ingress to known ServiceAccount selectors and ports. Default-deny in production namespaces is recommended.

Step 7 — Egress

EKS pods typically egressed via NAT Gateway with no per-pod identity. GKE pods egress via Cloud NAT, with Workload Identity providing per-workload identity for GCP API calls.

For external (non-GCP) egress destinations:

  • Static egress IPs: reserve in Cloud NAT and document.
  • Allow-listed destinations on the partner side: send the partner the new IPs ahead of cutover.
  • VPC peering / TGW reachability: re-create with VPC peering or NCC (Network Connectivity Center).

Step 8 — Output the design diff

Render 04-network-translation/network-design.md with:

  • Diagram (ASCII or Mermaid) of source vs target network topologies, side by side.
  • Table mapping every source LB ARN to the target Gateway/Service.
  • Cert migration plan.
  • Cloud Armor policy mapping table.
  • DNS cutover plan (which records, which TTLs, planned change windows).
  • Firewall rule changes.

Render 04-network-translation/manifests/ with one file per Gateway and HTTPRoute, plus per-namespace NetworkPolicy.

Decision points

DecisionDefaultWhen to deviate
Gateway API vs IngressGateway APIIngress only if a controller you depend on doesn't yet support Gateway
Global vs regional GatewayGlobal for true multi-region traffic; regional otherwiseRegional for cost or sovereignty constraints
ACM cert reuse vs Certificate Manager re-issueCertificate Manager re-issueReuse only if private key export is feasible and required
AWS WAF preconfigured rules → Cloud Armor preconfigured1:1 by categoryCustom rewrites for org-specific rules
NAT egress vs per-workload Cloud NAT routesSingle Cloud NAT with manual port allocationPer-egress-destination Cloud NAT only for very high egress workloads

Outputs / Deliverables

04-network-translation/
├── network-design.md
├── manifests/
│   ├── gateways/
│   ├── httproutes/
│   ├── network-policies/
│   ├── healthcheck-policies/
│   └── backend-policies/
├── terraform/
│   ├── cloud-armor/
│   ├── certificates/
│   └── dns/
├── dns-plan.md
└── escalations.md

Validation

  • Every ALB/NLB in inventory.json has a target Gateway/Service in the manifests.
  • Every host in any source Ingress is covered by exactly one target HTTPRoute.
  • Every TLS cert ARN in source has a planned Certificate Manager entry or self-managed cert.
  • Cloud Armor mapping has been reviewed by the user; non-trivial WAF rules have explicit acceptance.
  • Apply manifests to a non-prod cluster and confirm: kubectl describe gateway shows Programmed: True; the Gateway has an external IP; curl https://... succeeds against a backend that already exists or against a placeholder.
  • NetworkPolicy doesn't break existing flows in the soak test.

Escalation triggers

  • Workloads using LoadBalancerSourceRanges with overlapping or conflicting CIDRs across namespaces — surface for review.
  • TLS certs that cannot be reissued (private CA chain, customer-supplied) — surface for human plan.
  • Custom AWS WAF JS challenge / CAPTCHA flows — Cloud Armor reCAPTCHA Enterprise has a different UX; needs product decision.
  • Egress dependencies on AWS-only endpoints (e.g., S3 VPC endpoint) — needs cross-cloud egress plan.

Common pitfalls

  • Forgetting health check paths. The ALB health check path is in an annotation; in GKE it's a HealthCheckPolicy. Skip and your backends look unhealthy.
  • Not setting BackendConfig/GCPBackendPolicy. Timeouts default to 30s. Many workloads need longer. Translate explicitly.
  • TLS hostname mismatches. ACM SANs migrate cleanly; ACM wildcards plus exotic SANs sometimes don't. Verify each cert.
  • Cloud Armor preconfigured WAF tuning level. cve-canary is sensitive; cve-stable is the production default. Match severity.
  • Default-deny NetworkPolicy applied with no allow rules. The cluster is now silent. Stage default-deny via a soak window.
  • GKE ManagedCertificate stays in FAILED_NOT_VISIBLE until LB IP attaches and DNS resolves to it. Sequence the cutover so DNS is updated before the cert is expected to validate, not after. See LFF-26.
  • ManagedCertificate doesn't work with nginx-ingress. Decision point: Gateway+ManagedCertificate, or nginx-ingress+cert-manager. Make it up-front. See LFF-27.
  • Gateway API on GKE expects a pre-shared SslCertificate, not a ManagedCertificate CR. When targeting Gateway API, plan TLS via Certificate Manager from the start. See LFF-28.
  • ALB group.name semantics don't translate to Gateway API. Rebuild as one Gateway with multiple HTTPRoutes. See LFF-20.
  • Istio Gateway static IP port collisions. Each Istio Gateway creates its own LB Service; shared static IPs collide on port 15021. See LFF-21.
  • AWS LBC CRD vs Gateway API mismatch. AWS LBC v3.0.0 moved to CRDs; many EKS-only fields do not fit Gateway API directly without restructuring. See LFF-22.
  • Confluent live CNI migration. Migrating CNI (to Cilium) live across clouds requires strict cluster-by-cluster sequencing. See LFF-30.

References

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.