Глава 22. Мультиоблачная архитектура
«Мультиоблако — это не выбор технологий, а выбор абстракций.»
Большинство организаций уже работают в нескольких облаках: вычисления в AWS, данные в GCP BigQuery, идентичность в Azure Entra ID. Согласно Flexera State of the Cloud 2025, 89% организаций используют мультиоблачную стратегию. Проблема: каждый облачный провайдер предлагает собственный набор Zero Trust-инструментов, несовместимых между собой. Без единого слоя абстракции каждая пара «облако ↔ облако» порождает ad hoc-интеграции, а количество таких пар растёт как O(n²).
22.1 Зачем: проблема фрагментации
Три измерения фрагментации
| Измерение | AWS | GCP | Azure | Проблема |
|---|---|---|---|---|
| Идентичность | IRSA, Pod Identity, IAM Roles Anywhere | Workload Identity Federation | Managed Identity, Entra WIF | Разные форматы токенов, разные API |
| Политики | IAM JSON, Cedar | IAM allow-only | RBAC + Conditional Access | Разные языки, разная семантика |
| Сеть | VPC Lattice, Security Groups | Firewall Policies, Secure Tags | NSG, ASG, Private Link | Разные модели сегментации |
Подробное сравнение облачных механизмов идентичности рабочих нагрузок — в Главе 7, раздел «Идентичность рабочих нагрузок в облаках».
Реальная цена фрагментации
Uber (5 000+ микросервисов, 4 облака: GCP, OCI, AWS, on-prem) обнаружили, что управляют 150 000 секретами в 25 хранилищах, потому что каждое облако требовало собственных credentials. Решение: унификация через SPIRE + переход к secretless-архитектуре. К 2025 году платформа автоматически ротирует ~20 000 секретов в месяц и движется к полному отказу от статических credentials через workload identity federation.
Источники: Uber Blog: Multi-Cloud Secrets Management, Uber Blog: SPIFFE/SPIRE at Scale
Подход: три открытых слоя абстракции
Принцип: один открытый стандарт на каждый слой — SPIFFE/SPIRE для идентичности (CNCF Graduated, сентябрь 2022), OPA для политик (CNCF Graduated, январь 2021), Terraform/OpenTofu для инфраструктуры. Каждый слой работает поверх облачных API, не заменяя их.
22.2 Единая идентичность: SPIRE federation
Основы федерации SPIRE (протокол обмена trust bundles, профили https_web и https_spiffe, конфигурация federates_with) рассмотрены в Главе 7, раздел «Федерация между доменами доверия». Интеграция SPIRE с Istio service mesh — в Главе 12. Здесь мы сосредоточимся на практической архитектуре для трёх облаков.
Архитектура: один trust domain на облако
Почему отдельные trust domains, а не вложенный SPIRE? Отдельные домены обеспечивают организационную автономию: команды каждого облака управляют своим SPIRE Server независимо. Вложенный (nested) SPIRE — единый trust domain с иерархией CA — лучше подходит для single-org multi-cluster в одном облаке (см. документацию SPIRE по nested topology).
Выбор Node Attestor по облаку
| Облако | Рекомендуемый attestor | Альтернатива | Ключевые selectors |
|---|---|---|---|
| AWS EKS | aws_iid | k8s_psat | aws_iid:iamrole, aws_iid:tag:env:prod, aws_iid:sg:id |
| GCP GKE | gcp_iit | k8s_psat | gcp_iit:project-id, gcp_iit:sa, gcp_iit:label:env:prod |
| Azure AKS | azure_msi | k8s_psat, azure_imds (v1.14.0+) | azure_msi:subscription-id, azure_msi:vm-name:rg:name |
| Любой K8s | k8s_psat | — | k8s_psat:cluster, k8s_psat:agent_ns, k8s_psat:agent_sa |
k8s_psat — единственный облако-агностичный attestor, работающий одинаково на всех платформах. Используйте его, когда облачные selectors не нужны. Начиная с SPIRE v1.12.x, устаревший k8s_sat удалён.
Начиная с SPIRE v1.13.1, aws_iid поддерживает валидацию принадлежности к EKS-кластеру — сервер проверяет, что нода принадлежит указанному EKS-кластеру через Auto Scaling Group.
Источники: aws_iid plugin, gcp_iit plugin, azure_msi plugin
Конфигурация federation через Helm
Используем SPIRE Helm charts hardened v0.28.1 (январь 2026), CRD API: spire.spiffe.io/v1alpha1.
Кластер A (EKS, trust domain aws.example.com):
# values-eks.yaml
global:
spire:
clusterName: eks-production
trustDomain: aws.example.com
spire-server:
nodeAttestor:
aws_iid:
enabled: true
plugin_data:
validate_eks_cluster_membership:
eks_cluster_names: ["eks-production"]
federation:
enabled: true
ingress:
enabled: true # Expose bundle endpoint
controllerManager:
identities:
clusterFederatedTrustDomains:
gcp-domain:
trustDomain: gcp.example.com
bundleEndpointURL: https://spire-federation.gcp.example.com
bundleEndpointProfile:
type: https_web # WebPKI — проще для начала
azure-domain:
trustDomain: azure.example.com
bundleEndpointURL: https://spire-federation.azure.example.com
bundleEndpointProfile:
type: https_web
clusterSPIFFEIDs:
default:
federatesWith:
- gcp.example.com
- azure.example.comКластер B (GKE, trust domain gcp.example.com):
# values-gke.yaml
global:
spire:
clusterName: gke-production
trustDomain: gcp.example.com
spire-server:
nodeAttestor:
gcp_iit:
enabled: true
plugin_data:
projectid_allow_list: ["my-gcp-project"]
use_instance_metadata: true
federation:
enabled: true
ingress:
enabled: true
controllerManager:
identities:
clusterFederatedTrustDomains:
aws-domain:
trustDomain: aws.example.com
bundleEndpointURL: https://spire-federation.aws.example.com
bundleEndpointProfile:
type: https_web
azure-domain:
trustDomain: azure.example.com
bundleEndpointURL: https://spire-federation.azure.example.com
bundleEndpointProfile:
type: https_web
clusterSPIFFEIDs:
default:
federatesWith:
- aws.example.com
- azure.example.comКластер C (AKS) конфигурируется аналогично с azure_msi node attestor и federates_with двух других доменов.
Источники: SPIRE Helm Charts Federation, SPIRE Helm Chart Repo
OIDC Discovery Provider: мост к облачным API
SPIRE JWT-SVID сам по себе не авторизует доступ к AWS S3 или GCP BigQuery. Для этого нужен OIDC Discovery Provider — компонент, который публикует JWKS-endpoint, понятный облачным STS.
Настройка для каждого облака:
| Облако | Конфигурация | Audience |
|---|---|---|
| AWS | IAM OIDC Identity Provider → AssumeRoleWithWebIdentity | sts.amazonaws.com |
| GCP | Workload Identity Pool + OIDC Provider → STS exchange | https://iam.googleapis.com/... |
| Azure | Entra Federated Identity Credentials → token exchange | api://AzureADTokenExchange |
OIDC Discovery Provider развёртывается Helm chart'ом автоматически при установке SPIRE. Требуется публичный DNS для JWKS-endpoint (или приватный DNS с Cloud DNS peering / Route53 PHZ sharing).
Источники: SPIRE OIDC Discovery Provider, AWS OIDC Federation with SPIRE
22.3 Единая политика: OPA bundles across clouds
Основы OPA, Rego v1 и интеграция с Kubernetes (Gatekeeper, Kyverno) рассмотрены в Главе 19. Здесь — дистрибуция политик в мультиоблачной среде.
Проблема: политики живут в каждом облаке отдельно
Без единого слоя:
- AWS: IAM Policies (JSON) + Security Group rules
- GCP: IAM allow policies (YAML) + Firewall rules
- Azure: RBAC assignments + NSG rules + Conditional Access
Результат: одна и та же логика (например, «только production workloads могут читать PII») реализована тремя разными способами, три разных места для аудита, три вектора ошибки.
Решение: OPA как единый policy engine
OPA v1.13.1 (январь 2026) — текущая стабильная версия. Начиная с OPA 1.0 (декабрь 2024), Rego v1 обязателен: ключевые слова if и contains используются во всех правилах.
Bundle: единица дистрибуции политик
OPA Bundle — gzip-архив, содержащий .rego-файлы, data.json/data.yaml и .manifest. Bundles загружаются по HTTP/S из облачного хранилища или OCI-реестра.
Конфигурация OPA для AWS (S3):
services:
s3:
url: https://zt-policy-bundles.s3.eu-central-1.amazonaws.com
credentials:
s3_signing:
environment_credentials: {}
bundles:
authz:
service: s3
resource: bundles/authz/bundle.tar.gz
polling:
min_delay_seconds: 30
max_delay_seconds: 120Конфигурация OPA для GCP (GCS):
services:
gcs:
url: https://storage.googleapis.com/storage/v1/b/zt-policy-bundles/o
credentials:
gcp_metadata:
scopes:
- https://www.googleapis.com/auth/devstorage.read_only
bundles:
authz:
service: gcs
resource: "bundles/authz/bundle.tar.gz?alt=media"
polling:
min_delay_seconds: 30
max_delay_seconds: 120Конфигурация OPA для Azure (Blob Storage):
services:
blob:
url: https://ztpolicybundles.blob.core.windows.net
credentials:
oauth2:
token_url: "https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token"
client_id: "${CLIENT_ID}"
client_secret: "${CLIENT_SECRET}"
scopes:
- "https://storage.azure.com/.default"
bundles:
authz:
service: blob
resource: policy-container/bundles/authz/bundle.tar.gz
polling:
min_delay_seconds: 30
max_delay_seconds: 120OCI-реестры как универсальная альтернатива
Начиная с OPA v0.40.0, bundles можно хранить в OCI-реестрах (ECR, GAR, ACR, GHCR). Преимущество: единый формат дистрибуции для всех облаков, версионирование через теги, подпись через Cosign.
# Сборка и публикация bundle
opa build -b policy/ -o bundle.tar.gz
oras push ghcr.io/myorg/zt-policy:v1.2.0 \
--artifact-type application/vnd.oci.image.layer.v1.tar+gzip \
bundle.tar.gzCI pipeline для мультиоблачных политик
# .github/workflows/policy-ci.yaml
name: Policy CI
on:
push:
paths: ['policy/**']
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install OPA
run: |
curl -L -o opa https://openpolicyagent.org/downloads/v1.13.1/opa_linux_amd64_static
chmod +x opa && sudo mv opa /usr/local/bin/
- name: Rego format check
run: opa fmt --check policy/
- name: Run tests
run: opa test policy/ -v --coverage --format=json > coverage.json
- name: Build bundle
run: opa build -b policy/ -o bundle.tar.gz
distribute:
needs: test
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
# AWS
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/policy-deployer
aws-region: eu-central-1
- run: aws s3 cp bundle.tar.gz s3://zt-policy-bundles/bundles/authz/
# GCP
- uses: google-github-actions/auth@v3
with:
workload_identity_provider: projects/123/locations/global/...
- run: gsutil cp bundle.tar.gz gs://zt-policy-bundles/bundles/authz/Источники: OPA Bundles, OPA Releases
22.4 Terraform: единая инфраструктура
Multi-provider паттерн
Terraform v1.14.4 (январь 2026, BSL 1.1) и OpenTofu v1.11.4 (январь 2026, MPL 2.0, CNCF Sandbox) поддерживают multi-provider конфигурации. Ключевой принцип: один модуль на ресурс, отдельные state-файлы на облако.
infrastructure/
├── modules/
│ ├── spire-eks/ # AWS EKS + SPIRE
│ ├── spire-gke/ # GCP GKE + SPIRE
│ └── spire-aks/ # Azure AKS + SPIRE
├── environments/
│ ├── production/
│ │ ├── aws/
│ │ │ ├── main.tf # module "spire-eks" { ... }
│ │ │ └── backend.tf # S3 + DynamoDB
│ │ ├── gcp/
│ │ │ ├── main.tf # module "spire-gke" { ... }
│ │ │ └── backend.tf # GCS
│ │ └── azure/
│ │ ├── main.tf # module "spire-aks" { ... }
│ │ └── backend.tf # Azure Storage
│ └── staging/
└── policy/ # OPA policies for Terraform plansState management: отдельный state на облако
| Backend | Locking | Шифрование | Примечание |
|---|---|---|---|
| S3 | DynamoDB table | KMS | Самый зрелый; encrypt = true |
| GCS | Built-in (object metadata) | Customer-Managed Encryption Keys | Не требует отдельного lock-ресурса |
| Azure Storage | Native blob lease | Storage Service Encryption | Интеграция с Azure RBAC |
Не используйте единый state для нескольких облаков. Blast radius единого state-файла слишком велик: ошибка в GCP-модуле может заблокировать деплой в AWS из-за lock contention.
Ephemeral resources (Terraform 1.10+)
Для Zero Trust критически важно: секреты не должны сохраняться в state-файле. Ephemeral resources решают эту проблему:
# Секрет запрашивается при apply и НЕ сохраняется в state
ephemeral "aws_secretsmanager_secret_version" "db_password" {
secret_id = "production/db/password"
}
resource "aws_db_instance" "main" {
# ...
password = ephemeral.aws_secretsmanager_secret_version.db_password.secret_string
}OPA-валидация Terraform-планов
Перед применением каждого плана — проверка OPA-политиками (подробнее в Главе 19):
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
conftest test tfplan.json --policy policy/Пример политики для мультиоблака — запрет публичных endpoints:
package terraform.multicloud
import rego.v1
# Запретить публичные S3 бакеты
deny contains msg if {
resource := input.resource_changes[_]
resource.type == "aws_s3_bucket_public_access_block"
resource.change.after.block_public_acls == false
msg := sprintf("S3 bucket '%s': публичный доступ запрещён", [resource.name])
}
# Запретить GCS бакеты с allUsers
deny contains msg if {
resource := input.resource_changes[_]
resource.type == "google_storage_bucket_iam_member"
resource.change.after.member == "allUsers"
msg := sprintf("GCS bucket '%s': allUsers запрещён", [resource.name])
}
# Запретить Azure Storage без private endpoint
deny contains msg if {
resource := input.resource_changes[_]
resource.type == "azurerm_storage_account"
resource.change.after.public_network_access_enabled == true
msg := sprintf("Storage account '%s': публичный доступ запрещён", [resource.name])
}Источники: Terraform Releases, OpenTofu Releases, OPA Terraform Integration
22.5 Cloud-native vs Open Source: матрица выбора
Сравнительная таблица
| Слой | AWS | GCP | Azure | Open Source |
|---|---|---|---|---|
| Идентичность (workload) | IRSA, Pod Identity | Workload Identity Federation | Managed Identity, WIF | SPIFFE/SPIRE |
| Привилегированный доступ | Secrets Manager | Secret Manager | Key Vault | Vault (BSL 1.1) |
| Политики | IAM JSON, Cedar | IAM allow-only | RBAC + Conditional Access | OPA/Rego |
| Микросегментация | Security Groups, VPC Lattice | Firewall Policies | NSG + ASG | Cilium, Calico |
| Service mesh | Cloud Service Mesh | AKS Istio add-on | Istio, Linkerd | |
| IaC | CloudFormation | Deployment Manager | Bicep/ARM | Terraform / OpenTofu |
| Lock-in | Высокий | Высокий | Высокий | Нет |
| Multi-cloud | Нет | Нет | Нет | Да |
Когда cloud-native, когда open source
Cloud-native (один провайдер):
- Проще настройка, managed operations
- Глубокая интеграция с облачными сервисами
- Подходит для single-cloud организаций
- Пример: EKS Pod Identity → S3 доступ в 3 строки YAML
Open source (мультиоблако):
- Единый набор инструментов для всех платформ
- Переносимость между облаками и on-prem
- Инвестиции в экспертизу переиспользуются
- Пример: SPIRE → любое облако + on-prem через один API
Гибридный подход (рекомендуемый):
- Open source для кросс-облачных слоёв (идентичность, политики)
- Cloud-native для специфичных сервисов (VPC Lattice для AWS-internal traffic, Cloud Service Mesh для GCP-internal)
- SPIRE OIDC Discovery Provider как мост между SPIFFE-идентичностями и облачными IAM
Service mesh: текущее состояние
Облачные провайдеры переосмысливают managed mesh:
| Провайдер | Текущий статус | Направление |
|---|---|---|
| AWS | App Mesh EOL 30.09.2026 (новые клиенты не принимаются с 24.09.2024) | Миграция на VPC Lattice (для EKS) или ECS Service Connect |
| GCP | Cloud Service Mesh (Anthos SM + Traffic Director объединены) | Единый глобальный control plane, автоматические обновления |
| Azure | AKS Istio add-on (managed sidecar mode) | Ambient mode на roadmap, без сроков |
Для мультиоблачного service mesh: самостоятельный Istio с SPIRE CA обеспечивает единообразие. SPIRE выдаёт сертификаты для workloads, Istio использует их через SDS API. Подробная конфигурация SPIRE+Istio — в Главе 12.
Источники: AWS App Mesh EOL, GCP Cloud Service Mesh, AKS Istio add-on
22.6 Lab: SPIRE federation между двумя кластерами
В этой лабораторной работе мы создадим два kind-кластера, имитирующих разные облака с разными trust domains, настроим SPIRE federation между ними и продемонстрируем cross-domain mTLS.
Предварительные требования
Шаг 1: Создание двух кластеров
# Кластер A — «AWS EKS»
cat <<EOF | kind create cluster --name cluster-aws --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
podSubnet: "10.10.0.0/16"
serviceSubnet: "10.11.0.0/16"
nodes:
- role: control-plane
- role: worker
EOF
# Кластер B — «GCP GKE»
cat <<EOF | kind create cluster --name cluster-gcp --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
podSubnet: "10.20.0.0/16"
serviceSubnet: "10.21.0.0/16"
nodes:
- role: control-plane
- role: worker
EOFШаг 2: Установка SPIRE на оба кластера
# Установка CRD (одинаково для обоих)
for ctx in kind-cluster-aws kind-cluster-gcp; do
kubectl --context $ctx create namespace spire-system 2>/dev/null || true
helm upgrade --install --kube-context $ctx -n spire-system \
spire-crds spire-crds \
--repo https://spiffe.github.io/helm-charts-hardened/
done
# Кластер A (trust domain: aws.lab.example)
helm upgrade --install --kube-context kind-cluster-aws \
-n spire-system spire spire \
--repo https://spiffe.github.io/helm-charts-hardened/ \
--set global.spire.clusterName=cluster-aws \
--set global.spire.trustDomain=aws.lab.example \
--set spire-server.federation.enabled=true \
--wait
# Кластер B (trust domain: gcp.lab.example)
helm upgrade --install --kube-context kind-cluster-gcp \
-n spire-system spire spire \
--repo https://spiffe.github.io/helm-charts-hardened/ \
--set global.spire.clusterName=cluster-gcp \
--set global.spire.trustDomain=gcp.lab.example \
--set spire-server.federation.enabled=true \
--waitШаг 3: Обмен trust bundles
# Экспорт bundle из кластера A
kubectl --context kind-cluster-aws -n spire-system exec \
spire-server-0 -- \
/opt/spire/bin/spire-server bundle show -format spiffe \
> /tmp/aws-bundle.json
# Экспорт bundle из кластера B
kubectl --context kind-cluster-gcp -n spire-system exec \
spire-server-0 -- \
/opt/spire/bin/spire-server bundle show -format spiffe \
> /tmp/gcp-bundle.json
# Импорт bundle B в кластер A
kubectl --context kind-cluster-aws -n spire-system \
cp /tmp/gcp-bundle.json spire-server-0:/tmp/gcp-bundle.json
kubectl --context kind-cluster-aws -n spire-system exec \
spire-server-0 -- \
/opt/spire/bin/spire-server bundle set \
-format spiffe \
-id spiffe://gcp.lab.example \
-path /tmp/gcp-bundle.json
# Импорт bundle A в кластер B
kubectl --context kind-cluster-gcp -n spire-system \
cp /tmp/aws-bundle.json spire-server-0:/tmp/aws-bundle.json
kubectl --context kind-cluster-gcp -n spire-system exec \
spire-server-0 -- \
/opt/spire/bin/spire-server bundle set \
-format spiffe \
-id spiffe://aws.lab.example \
-path /tmp/aws-bundle.jsonШаг 4: Регистрация рабочих нагрузок с федерацией
# В кластере A: зарегистрировать workload, которому доверяет кластер B
kubectl --context kind-cluster-aws -n spire-system exec \
spire-server-0 -- \
/opt/spire/bin/spire-server entry create \
-spiffeID spiffe://aws.lab.example/ns/demo/sa/frontend \
-parentID spiffe://aws.lab.example/spire/agent/k8s_psat/cluster-aws \
-selector k8s:ns:demo \
-selector k8s:sa:frontend \
-federatesWith spiffe://gcp.lab.example
# В кластере B: зарегистрировать workload
kubectl --context kind-cluster-gcp -n spire-system exec \
spire-server-0 -- \
/opt/spire/bin/spire-server entry create \
-spiffeID spiffe://gcp.lab.example/ns/demo/sa/backend \
-parentID spiffe://gcp.lab.example/spire/agent/k8s_psat/cluster-gcp \
-selector k8s:ns:demo \
-selector k8s:sa:backend \
-federatesWith spiffe://aws.lab.exampleШаг 5: Проверка федерации
# В кластере A: проверить, что workload получает оба trust bundles
kubectl --context kind-cluster-aws -n demo exec \
deploy/frontend -- \
/opt/spire/bin/spire-agent api fetch x509 \
-socketPath /spiffe-workload-api/spire-agent.sock
# Ожидаемый вывод:
# SVID: spiffe://aws.lab.example/ns/demo/sa/frontend
# Bundles:
# spiffe://aws.lab.example (N certificates)
# spiffe://gcp.lab.example (N certificates) ← федерированный bundleНаличие gcp.lab.example в списке bundles подтверждает, что рабочая нагрузка в кластере A может верифицировать SVID-сертификаты из кластера B — кросс-доменный mTLS готов.
Шаг 6: Очистка
kind delete cluster --name cluster-aws
kind delete cluster --name cluster-gcpСвязь с другими главами
| Тема | Глава | Связь |
|---|---|---|
| SPIFFE/SPIRE основы | Гл. 7 | Идентичность рабочих нагрузок, федерация, облачные WIF |
| Service Mesh + SPIRE | Гл. 12 | Istio + SPIRE CA, cross-cluster mTLS |
| Kubernetes deep dive | Гл. 14 | RBAC, Vault CSI, SPIRE на K8s |
| Политика как код | Гл. 19 | OPA/Rego, Conftest, GitOps |
| Легаси и гибридные | Гл. 23 | ZT proxy для legacy apps, AD→Entra миграция |
| Новые горизонты | Гл. 24 | PQC для мультиоблачного mTLS, AI-агенты |
| Эталонная архитектура | Гл. 25 | Greenfield/brownfield сценарии, ADR |
Итоги
- SPIRE federation обеспечивает единую идентичность рабочих нагрузок через все облака: каждый кластер — свой trust domain, обмен trust bundles через federation API
- OIDC Discovery Provider — мост между SPIFFE-идентичностями и облачными STS (AWS, GCP, Azure), позволяющий workloads получать временные облачные credentials без статических секретов
- OPA bundles обеспечивают единые политики: один Rego-код в Git, дистрибуция в S3/GCS/Azure Blob или OCI-реестры
- Terraform multi-provider + отдельные state-файлы + OPA-валидация планов = безопасная инфраструктура для всех облаков
- Гибридный подход оптимален: open source для кросс-облачных слоёв (SPIRE, OPA), cloud-native для внутриоблачных сервисов (VPC Lattice, Cloud Service Mesh)
- Начните с двух кластеров и
https_web-профиля — это минимальный happy-path, который можно расширить до трёх и более облаков