azure-vnet-cross-sub-peering
A Connected VNet peering that crosses Azure subscriptions (emitted from explicit azure-vnet-peering-record) extends network reachability AND establishes a cross-subscription trust boundary - a critical lateral-movement fact.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
where
?localVNet.subscription_id != ?remoteVNet.subscription_id (subscription boundary crossed)
?localVNet.tenant_id == ?remoteVNet.tenant_id (same tenant; for cross-tenant see azure-vnet-cross-tenant-peering)
peeringState == Connected on both sides (from explicit record)
emit
Raw rule rules/derived/azure/vnet.yaml
id: azure-vnet-cross-sub-peering
emits:
- PeeredWith
- CrossAccountTrust
description: "A Connected VNet peering that crosses Azure subscriptions (emitted from explicit azure-vnet-peering-record)\
\ extends network reachability AND establishes a cross-subscription trust boundary \u2014 a critical\
\ lateral-movement fact."
match:
- - localVNet: null
- PeeredWith
- remoteVNet: null
where:
- ?localVNet.subscription_id != ?remoteVNet.subscription_id (subscription boundary crossed)
- ?localVNet.tenant_id == ?remoteVNet.tenant_id (same tenant; for cross-tenant see azure-vnet-cross-tenant-peering)
- peeringState == Connected on both sides (from explicit record)
emit:
- edge_type: PeeredWith
source_type: VirtualNetwork
target_type: VirtualNetwork
source: <local VNet node>
target: <remote VNet node, peered across the subscription boundary>
permissions: []
state_logic: ACTIVE when peeringState is Connected on both sides. CONDITIONAL(network_reachability)
if remote-side state is unconfirmed (collection gap on remote subscription).
derived_from:
- azure-vnet-peering-record
- edge_type: CrossAccountTrust
source_type: Account
target_type: Account
source: <Subscription node (Account class) containing local VNet>
target: <Subscription node (Account class) containing remote VNet>
permissions: []
properties:
cross_subscription: true
local_subscription: '{localVNet.subscription_id}'
remote_subscription: '{remoteVNet.subscription_id}'
state_logic: ACTIVE when peeringState is Connected on both sides and subscription mismatch is confirmed.
CONDITIONAL(network_reachability) if remote-side state is unconfirmed (collection gap on remote subscription).
derived_from:
- azure-vnet-peering-record
false_positive_note: "This rule emits TWO edges: (1) PeeredWith (network-layer reachability, already\
\ in explicit), (2) CrossAccountTrust (subscription boundary crossing). ARM control does not cross\
\ subscription boundaries via peering. An attacker with a foothold in the remote subscription's VNet\
\ gains network-layer reach into this VNet subject to NSG rules. Peering is non-transitive \u2014\
\ do not chain these edges."
narrative: VNet {localVNet.name} (subscription {source.id}) is peered with VNet {remoteVNet.name} (subscription
{target.id}); a foothold in that subscription grants network-layer reach across the account boundary.