gcp-ncc-spoke-create-transit-pivot

A principal with effective networkconnectivity.spokes.create on a hub that does not require manual spoke acceptance (requireAcceptance=false) can attach an attacker-controlled VPC, VPN, or Interconnect to the hub's routing domain. The spoke becomes ACTIVE immediately, placing the attacker's network on the same routing fabric as all legitimate spoke- attached networks - a transit pivot without modifying any VPC firewall rule in the destination projects.

derived gcp emits CanModifyConfiguration

match (effective permission)

{ "action": "networkconnectivity.spokes.create", "resource_type": "networkconnectivity.googleapis.com/Hub" }

where

hub.requireAcceptance == false (auto-accept; new spokes become ACTIVE immediately) ?principal also has networkconnectivity.hubs.get on the hub (required to read hub details before attaching a spoke)

emit

source typeIdentity
target typeTransitGateway
source<principal>
target<NCC Hub (TransitGateway) node>
permissionsnetworkconnectivity.spokes.create networkconnectivity.hubs.get
conditionsiam_permission service_state
state logicACTIVE if ?principal holds networkconnectivity.spokes.create (effective) AND hub.requireAcceptance == false (confirmed from hub resource). CONDITIONAL(service_state) if hub.requireAcceptance == true or is unknown — the spoke enters PENDING_REVIEW and does not route until a hub admin explicitly accepts it (networkconnectivity.spokes.update on the hub side). CONDITIONAL(iam_permission) if the effective-permission evaluator cannot confirm both required permissions. BLOCKED if an org policy constraint prevents spoke creation in the project.

Narrative

{principal.name} holds networkconnectivity.spokes.create on project {project.name} and can read NCC Hub {hub.name} (requireAcceptance=false). Creating a new spoke attaching an attacker-controlled VPC to {hub.name} immediately places the attacker's network on the hub's routing domain, giving network-layer routing to all other spoke-attached VPCs and on- premises networks - without modifying any VPC firewall rule in the destination projects. This is a transit-pivot across project boundaries.

Raw rule rules/derived/gcp/ncc.yaml

id: gcp-ncc-spoke-create-transit-pivot
emits: CanModifyConfiguration
description: "A principal with effective networkconnectivity.spokes.create on a hub that does not require\
  \ manual spoke acceptance (requireAcceptance=false) can attach an attacker-controlled VPC, VPN, or Interconnect\
  \ to the hub's routing domain. The spoke becomes ACTIVE immediately, placing the attacker's network\
  \ on the same routing fabric as all legitimate spoke- attached networks \u2014 a transit pivot without\
  \ modifying any VPC firewall rule in the destination projects."
applies_to:
- gcp
match_effective_permission:
  action: networkconnectivity.spokes.create
  resource_type: networkconnectivity.googleapis.com/Hub
where:
- hub.requireAcceptance == false (auto-accept; new spokes become ACTIVE immediately)
- ?principal also has networkconnectivity.hubs.get on the hub (required to read hub details before attaching
  a spoke)
emit:
  source_type: Identity
  target_type: TransitGateway
  source: <principal>
  target: <NCC Hub (TransitGateway) node>
  permissions:
  - networkconnectivity.spokes.create
  - networkconnectivity.hubs.get
  conditions:
  - iam_permission
  - service_state
  state_logic: "ACTIVE if ?principal holds networkconnectivity.spokes.create (effective) AND hub.requireAcceptance\
    \ == false (confirmed from hub resource). CONDITIONAL(service_state) if hub.requireAcceptance == true\
    \ or is unknown \u2014 the spoke enters PENDING_REVIEW and does not route until a hub admin explicitly\
    \ accepts it (networkconnectivity.spokes.update on the hub side). CONDITIONAL(iam_permission) if the\
    \ effective-permission evaluator cannot confirm both required permissions. BLOCKED if an org policy\
    \ constraint prevents spoke creation in the project."
  confidence: '0.85'
  derived_from:
  - '<effective permission: networkconnectivity.spokes.create on spoke project>'
  - '<Hub resource: requireAcceptance=false>'
  false_positive_note: "(1) requireAcceptance=true is the critical gate: if set, new spokes are PENDING_REVIEW\
    \ and do NOT route until a hub admin accepts. Downgrade to CONDITIONAL(service_state) when requireAcceptance\
    \ is true or unknown. (2) VPC spokes must be in the same GCP organization as the hub (API constraint).\
    \ An attacker in a different org can only attach VPN or Interconnect spokes (not VPC spokes). Downgrade\
    \ to POTENTIAL when the attacker's project org cannot be confirmed to match the hub's org for VPC-spoke\
    \ scenarios. (3) networkconnectivity.spokes.create alone is insufficient if the principal lacks networkconnectivity.hubs.get\
    \ on the target hub \u2014 hub visibility is required to complete the spoke attachment. Confirm both\
    \ permissions. (4) GCP IAM Deny policies blocking networkconnectivity.spokes.create or VPC Service\
    \ Controls perimeters restricting networkconnectivity API access should downgrade the edge to BLOCKED.\
    \ (5) The CanModifyConfiguration edge here records the CAPABILITY to add an attacker spoke. The resulting\
    \ RoutesTo facts (from gcp-ncc-active-*- spoke-routes-* rules) are then derivable once the spoke exists.\
    \ Do not emit CanNetworkReach or RoutesTo from this rule \u2014 those come from the structural spoke-fact\
    \ rules, composing via the network-chains linchpin."
  narrative: "{principal.name} holds networkconnectivity.spokes.create on project {project.name} and can\
    \ read NCC Hub {hub.name} (requireAcceptance=false). Creating a new spoke attaching an attacker-controlled\
    \ VPC to {hub.name} immediately places the attacker's network on the hub's routing domain, giving\
    \ network-layer routing to all other spoke-attached VPCs and on- premises networks \u2014 without\
    \ modifying any VPC firewall rule in the destination projects. This is a transit-pivot across project\
    \ boundaries."
move · open · esc close