[vpc] Add VPC peering support for multi-tenant environments

Implement bilateral VPC peering using Kube-OVN's native vpcPeerings
mechanism. Each VPC can declare peers by specifying the remote VPC name
and tenant namespace. Peering is only activated by Kube-OVN when both
sides declare each other, ensuring mutual consent.

Key features:
- Deterministic remote VPC ID resolution via sha256 hash
- Auto-allocated link-local peering IPs (169.254.0.0/16) derived from
  sorted pair hash, eliminating manual IP coordination
- Static routes support for fine-grained inter-VPC routing
- ConfigMap enrichment with peer discovery info
- Schema validation enforcing tenant- namespace prefix pattern

Signed-off-by: Mattia Eleuteri <mattia.eleuteri@hidora.io>
Signed-off-by: mattia-eleuteri <mattia@hidora.io>
This commit is contained in:
Mattia Eleuteri 2026-03-05 11:22:34 +01:00 committed by Andrei Kvapil
parent 9bb6625c28
commit 2c0a9fb2cc
No known key found for this signature in database
GPG key ID: 931CF7FEACEAF765
4 changed files with 107 additions and 2 deletions

View file

@ -15,6 +15,39 @@ spec:
enableExternal: false
namespaces:
- {{ .Release.Namespace }}
{{- if .Values.peers }}
{{- $hexLookup := dict "0" 0 "1" 1 "2" 2 "3" 3 "4" 4 "5" 5 "6" 6 "7" 7 "8" 8 "9" 9 "a" 10 "b" 11 "c" 12 "d" 13 "e" 14 "f" 15 }}
vpcPeerings:
{{- range .Values.peers }}
{{- $remoteRelease := printf "virtualprivatecloud-%s" .vpcName }}
{{- $remoteVpcId := printf "vpc-%s" (printf "%s/%s" .tenantNamespace $remoteRelease | sha256sum | trunc 6) }}
{{- $sorted := list $vpcId $remoteVpcId | sortAlpha }}
{{- $pairKey := join "/" $sorted }}
{{- $pairHash := sha256sum $pairKey }}
{{- $h0 := int (get $hexLookup (substr 0 1 $pairHash)) }}
{{- $h1 := int (get $hexLookup (substr 1 2 $pairHash)) }}
{{- $h2 := int (get $hexLookup (substr 2 3 $pairHash)) }}
{{- $h3 := int (get $hexLookup (substr 3 4 $pairHash)) }}
{{- $byte0 := add (mul $h0 16) $h1 }}
{{- $byte1 := add (mul $h2 16) $h3 }}
{{- $oct3 := int (add (mod $byte0 254) 1) }}
{{- $base4 := int (mul (mod $byte1 64) 4) }}
{{- if eq $vpcId (index $sorted 0) }}
- remoteVpc: {{ $remoteVpcId }}
localConnectIP: {{ printf "169.254.%d.%d" $oct3 (int (add $base4 1)) }}
{{- else }}
- remoteVpc: {{ $remoteVpcId }}
localConnectIP: {{ printf "169.254.%d.%d" $oct3 (int (add $base4 2)) }}
{{- end }}
{{- end }}
{{- end }}
{{- if .Values.routes }}
staticRoutes:
{{- range .Values.routes }}
- cidr: {{ .cidr | quote }}
nextHopIP: {{ .nextHopIP | quote }}
{{- end }}
{{- end }}
{{- range .Values.subnets }}
{{- $subnetId := print "subnet-" (print $.Release.Namespace "/" $vpcId "/" .name | sha256sum | trunc 8) }}
@ -70,6 +103,12 @@ data:
{{ .name }}.ID: {{ print "subnet-" (print $.Release.Namespace "/" $vpcId "/" .name | sha256sum | trunc 8) }}
{{ .name }}.CIDR: {{ .cidr }}
{{- end }}
{{- range .Values.peers }}
{{- $remoteRelease := printf "virtualprivatecloud-%s" .vpcName }}
{{- $remoteVpcId := printf "vpc-%s" (printf "%s/%s" .tenantNamespace $remoteRelease | sha256sum | trunc 6) }}
peer.{{ .tenantNamespace }}.{{ .vpcName }}.vpcId: {{ $remoteVpcId | quote }}
peer.{{ .tenantNamespace }}.{{ .vpcName }}.tenantNamespace: {{ .tenantNamespace | quote }}
{{- end }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding

View file

@ -2,6 +2,50 @@
"title": "Chart Values",
"type": "object",
"properties": {
"peers": {
"description": "VPC peering connections (bidirectional declaration required)",
"type": "array",
"default": [],
"items": {
"type": "object",
"required": [
"tenantNamespace",
"vpcName"
],
"properties": {
"tenantNamespace": {
"description": "Namespace of the remote tenant",
"type": "string"
},
"vpcName": {
"description": "Logical name of the remote VPC (without \"virtualprivatecloud-\" prefix)",
"type": "string"
}
}
}
},
"routes": {
"description": "Static routes for the VPC",
"type": "array",
"default": [],
"items": {
"type": "object",
"required": [
"cidr",
"nextHopIP"
],
"properties": {
"cidr": {
"description": "Destination CIDR",
"type": "string"
},
"nextHopIP": {
"description": "Next hop IP address",
"type": "string"
}
}
}
},
"subnets": {
"description": "Subnets of a VPC",
"type": "array",

View file

@ -14,3 +14,25 @@ subnets: []
## cidr: "172.16.0.0/24"
## - name: mysubnet1
## cidr: "172.16.1.0/24"
## @typedef {struct} Peer - VPC peering target
## @field {string} vpcName - Logical name of the remote VPC (without "virtualprivatecloud-" prefix)
## @field {string} tenantNamespace - Namespace of the remote tenant
## @param {[]Peer} peers - VPC peering connections (bidirectional declaration required)
peers: []
## Example:
## peers:
## - vpcName: "other-vpc"
## tenantNamespace: "tenant-other"
## @typedef {struct} Route - Static route entry
## @field {string} cidr - Destination CIDR
## @field {string} nextHopIP - Next hop IP address
## @param {[]Route} routes - Static routes for the VPC
routes: []
## Example:
## routes:
## - cidr: "172.16.1.0/24"
## nextHopIP: "10.0.0.1"

View file

@ -8,7 +8,7 @@ spec:
plural: virtualprivateclouds
singular: virtualprivatecloud
openAPISchema: |-
{"title":"Chart Values","type":"object","properties":{"subnets":{"description":"Subnets of a VPC","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"cidr":{"description":"IP address range","type":"string"},"name":{"description":"Subnet name","type":"string"}}}}}}
{"title":"Chart Values","type":"object","properties":{"peers":{"description":"VPC peering connections (bidirectional declaration required)","type":"array","default":[],"items":{"type":"object","required":["tenantNamespace","vpcName"],"properties":{"tenantNamespace":{"description":"Namespace of the remote tenant","type":"string"},"vpcName":{"description":"Logical name of the remote VPC (without \"virtualprivatecloud-\" prefix)","type":"string"}}}},"routes":{"description":"Static routes for the VPC","type":"array","default":[],"items":{"type":"object","required":["cidr","nextHopIP"],"properties":{"cidr":{"description":"Destination CIDR","type":"string"},"nextHopIP":{"description":"Next hop IP address","type":"string"}}}},"subnets":{"description":"Subnets of a VPC","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"cidr":{"description":"IP address range","type":"string"},"name":{"description":"Subnet name","type":"string"}}}}}}
release:
prefix: "virtualprivatecloud-"
labels:
@ -23,7 +23,7 @@ spec:
plural: VPCs
description: "Isolated networks"
icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl8xMDI1XzMpIi8+CjxwYXRoIGQ9Ik0xMDkuNiA4Ni4xSDExNC4zQzExNi44ODUgODYuMSAxMTkgODguMjE1IDExOSA5MC44NDdWMTA0Ljg1M0MxMTkgMTA3LjQ4NSAxMTYuODg1IDEwOS42IDExNC4zIDEwOS42SDk1LjVDOTIuOTE1IDEwOS42IDkwLjggMTA3LjQ4NSA5MC44IDEwNC44NTNWOTAuODQ3QzkwLjggODguMjE1IDkyLjkxNSA4Ni4xIDk1LjUgODYuMUgxMDAuMlY3Ni43SDc2LjdWODYuMUg4MS40QzgzLjk4NSA4Ni4xIDg2LjEgODguMjE1IDg2LjEgOTAuODQ3VjEwNC44NTNDODYuMSAxMDcuNDg1IDgzLjk4NSAxMDkuNiA4MS40IDEwOS42SDYyLjZDNjAuMDE1IDEwOS42IDU3LjkgMTA3LjQ4NSA1Ny45IDEwNC44NTNWOTAuODQ3QzU3LjkgODguMjE1IDYwLjAxNSA4Ni4xIDYyLjYgODYuMUg2Ny4zVjc2LjdINDMuOFY4Ni4xSDQ4LjVDNTEuMDg1IDg2LjEgNTMuMiA4OC4yMTUgNTMuMiA5MC44NDdWMTA0Ljg1M0M1My4yIDEwNy40ODUgNTEuMDg1IDEwOS42IDQ4LjUgMTA5LjZIMjkuN0MyNy4xMTUgMTA5LjYgMjUgMTA3LjQ4NSAyNSAxMDQuODUzVjkwLjg0N0MyNSA4OC4yMTUgMjcuMTE1IDg2LjEgMjkuNyA4Ni4xSDM0LjRWNzYuN0MzNC40IDcxLjUzIDM4LjYzIDY3LjMgNDMuOCA2Ny4zSDY3LjNWNTcuOUg2Mi42QzYwLjAxNSA1Ny45IDU3LjkgNTUuNzg1IDU3LjkgNTMuMTUzVjM5LjE0N0M1Ny45IDM2LjUxNSA2MC4wMTUgMzQuNCA2Mi42IDM0LjRIODEuNEM4My45ODUgMzQuNCA4Ni4xIDM2LjUxNSA4Ni4xIDM5LjE0N1Y1My4xNTNDODYuMSA1NS43ODUgODMuOTg1IDU3LjkgODEuNCA1Ny45SDc2LjdWNjcuM0gxMDAuMkMxMDUuMzcgNjcuMyAxMDkuNiA3MS41MyAxMDkuNiA3Ni43Vjg2LjFaIiBmaWxsPSJ3aGl0ZSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzEwMjVfMyIgeDE9IjE0Mi41IiB5MT0iMTQzIiB4Mj0iMy45OTk5OSIgeTI9IjkuNDk5OTkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iIzAwMDgyRSIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMyRTMwNjciLz4KPC9saW5lYXJHcmFkaWVudD4KPC9kZWZzPgo8L3N2Zz4K
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "subnets"]]
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "subnets"], ["spec", "peers"], ["spec", "routes"]]
secrets:
exclude: []
include: []