Ваши микросервисы используют общую VPC, поэтому доверяют друг другу. Это доверие — окружающее полномочие: разрешение на вызов сервиса предоставляется не криптографическим доказательством, а сетевой топологией. Злоумышленник, проникший за периметр, унаследует всё это.
Сервисы абсолютно могут аутентифицироваться без окружающего полномочия. Более сложный вопрос — почему ваша платформа делает это похожим на предательство.
Как выглядит окружающее полномочие в продакшене
Большая часть «аутентификации» между сервисами — это на самом деле просто сегментация сети. Сервисы живут в частной подсети, за API-шлюзом или внутри кластера Kubernetes с сервисной сетью. Предположение простое: если запрос приходит из внутреннего IP, он легитимен.
Эта модель рушится, когда рушится периметр. Скомпрометированный под, боковое перемещение после утечки учётных данных или внедрение в цепочку поставок помещает злоумышленника внутрь зоны доверия. В этот момент каждый сервис доступен, и большинство не потребуют доказательства идентичности. Злоумышленнику не нужно воровать дополнительные учётные данные. Он просто унаследует окружающее полномочие сети.
Настоящая аутентификация между сервисами означает, что каждый вызывающий доказывает, кто он, и каждый вызываемый проверяет это доказательство. Сеть — это просто транспорт. Она не должна быть учётными данными.
Как работает аутентификация сервисов на основе полномочий
Есть два практических подхода: криптографическая идентичность (у каждого сервиса есть доказуемое имя) и токены полномочий (каждый запрос несёт неподделываемую, ограниченную авторизацию).
Криптографическая идентичность с mTLS
При взаимном TLS и клиент, и сервер предъявляют сертификаты X.509. Сервер проверяет сертификат клиента перед обработкой запроса. Клиент проверяет сертификат сервера перед отправкой данных. Ни одна сторона не доверяет другой из-за IP-адреса.
Инфраструктурная задача — распределение сертификатов. SPIFFE и SPIRE решают это, давая каждой рабочей нагрузке криптографическую идентичность, производную от её свойств, а не от её расположения. Под, запускающий payments-api, получает SPIFFE ID вида spiffe://prod.example.com/payments-api. Сертификат недолговечен и автоматически ротируется.
Вот как это выглядит на Go:
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"log"
"net/http"
"os"
)
// loadClientTLS returns a config that verifies the server AND presents a client cert.
func loadClientTLS(caCert, clientCert, clientKey []byte) *tls.Config {
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(caCert)
cert, err := tls.X509KeyPair(clientCert, clientKey)
if err != nil {
log.Fatalf("failed to load client cert: %v", err)
}
return &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: pool,
// Do not skip verification based on IP or DNS name alone.
InsecureSkipVerify: false,
}
}
func main() {
caCert, _ := os.ReadFile("ca.crt")
clientCert, _ := os.ReadFile("service.crt")
clientKey, _ := os.ReadFile("service.key")
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: loadClientTLS(caCert, clientCert, clientKey),
},
}
resp, err := client.Get("https://orders-api.internal:8443/orders")
if err != nil {
log.Fatal(err)
}
fmt.Println(resp.Status)
}
Критическая строка — InsecureSkipVerify: false. Без неё вы возвращаетесь к окружающему доверию. С ней клиент отказывается подключаться, если сервер не предъявит действительный сертификат, подписанный CA. Сервер делает то же самое для клиента.
Токены полномочий для ограниченной авторизации
Сертификаты доказывают идентичность. Полномочия доказывают авторизацию. Во многих системах нужны оба: докажите, кто вы, а затем докажите, что вам разрешено делать это конкретное действие.
Токен полномочий связывает идентичность, действие и ресурс в неподделываемый пакет. Сервис выпускает его. Сервис проверяет его. Нет центрального хранилища сессий для запроса.
import hmac
import hashlib
import secrets
import time
SECRET = secrets.token_bytes(32)
def mint_service_capability(
caller_id: str,
service: str,
action: str,
resource: str,
ttl_seconds: int = 300
) -> str:
"""Mint a time-bound capability for a specific service action."""
expires = int(time.time()) + ttl_seconds
payload = f"{caller_id}:{service}:{action}:{resource}:{expires}"
sig = hmac.new(SECRET, payload.encode(), hashlib.sha256).hexdigest()[:24]
return f"{payload}:{sig}"
def verify_service_capability(
token: str,
expected_service: str,
expected_action: str,
expected_resource: str
) -> bool:
"""Verify a capability token is unexpired and correctly scoped."""
try:
caller_id, svc, action, resource, expires, sig = token.rsplit(":", 5)
except ValueError:
return False
if int(expires) < time.time():
return False
if svc != expected_service or action != expected_action or resource != expected_resource:
return False
payload = f"{caller_id}:{svc}:{action}:{resource}:{expires}"
expected_sig = hmac.new(SECRET, payload.encode(), hashlib.sha256).hexdigest()[:24]
return hmac.compare_digest(sig, expected_sig)
# Service A requests a capability to call Service B.
token = mint_service_capability("payments-api", "orders-api", "read", "order-123")
# Service B verifies the capability before handling the request.
if not verify_service_capability(token, "orders-api", "read", "order-123"):
raise PermissionError("Invalid or expired capability")
Токен самодостаточен и недолговечен. Сервису B не нужно обращаться к серверу авторизации. Он просто проверяет HMAC и срок действия. Область действия явна: этот токен действителен только для чтения order-123 из orders-api.
Почему это сложнее, чем должно быть
Технология существует. Сложность — в эксплуатации.
Первоначальное установление доверия — первая проблема. Если каждому сервису нужен сертификат, что-то должно выпускать эти сертификаты. Это что-то — центр сертификации или сервер SPIRE — становится новой мишенью. Вы не устранили доверие. Вы его сконцентрировали.
Есть также проблема отзыва. Если сервис скомпрометирован, как вы аннулируете его идентичность? Сертификаты mTLS могут быть недолговечными, что помогает, но отзыв конкретного сертификата всё равно требует распределения списка отзыва сертификатов или использования OCSP. Токены полномочий быстро истекают, что ограничивает ущерб, но украденный ключ подписи позволяет злоумышленнику выпускать неограниченное количество токенов.
Производительность — ещё одна проблема. Рукопожатия TLS добавляют задержку. В сервисной сети с сайдкарами накладные расходы обычно приемлемы (несколько миллисекунд), но на путях с высокой пропускной способностью эти миллисекунды накапливаются. Токены полномочий избегают обратного рейса к серверу аутентификации, но проверка HMAC бесплатной в масштабе не является.
Где окружающее полномочие всё ещё имеет смысл
Не каждый внутренний вызов нуждается в криптографическом доказательстве. Задание планировщика, запускаемое в том же контейнере, что и клиент базы данных, не нуждается в mTLS для общения с localhost. Проверки работоспособности между балансировщиком нагрузки и узлом не нуждаются в токенах полномочий.
Цель — не нулевое окружающее полномочие. Цель — знать, где находится ваше окружающее полномочие, и приемлем ли радиус поражения. Общая VPC с 200 сервисами и без внутренней аутентификации — это большой, невидимый радиус поражения. Локальный сокет Unix с правами доступа к файлу 0600 — это маленький, явный радиус поражения.
Как начать убирать окружающее полномочие из вызовов сервисов
Вам не нужна сервисная сеть с первого дня. Начните с сервисов, которые касаются конфиденциальных данных или находятся на границах доверия.
Во-первых, определите своих заместителей. Какие сервисы обладают привилегиями, которых нет у других? Какие внутренние API были бы опасны, если бы их вызвал не тот сервис? Это ваши кандидаты.
Во-вторых, добавьте mTLS на уровень API. Если вы используете обратный прокси вроде Envoy или NGINX, вы можете терминировать mTLS там, не меняя код приложения. Прокси передаёт бэкенду заголовок с проверенной идентичностью клиента. Бэкенд проверяет этот заголовок.
В-третьих, ограничивайте область действия ваших токенов. Если вы уже используете JWT для аутентификации сервисов, перестаньте помещать role: service в содержимое токена. Помещайте allowed_services: ["orders-api"] и allowed_resources: ["order:*"]. Делайте авторизацию такой же конкретной, как и операция.
Часто задаваемые вопросы об аутентификации сервисов без окружающего полномочия
Достаточно ли mTLS, или мне нужны также полномочия?
mTLS доказывает идентичность. Полномочия доказывают авторизацию. Они решают разные проблемы. Сервис с действительным сертификатом всё равно не должен иметь права удалять произвольные данные. Используйте mTLS для безопасности транспорта и идентичности, полномочия — для контроля доступа.
А что насчёт сервисных сетей вроде Istio или Linkerd?
Сервисные сети автоматизируют mTLS и идентичность между подами. Это практичный способ убрать окружающее полномочие, не переписывая каждый сервис. Сетка управляет ротацией сертификатов, аттестацией идентичности и шифрованием трафика. Плата — сложность и новая плоскость управления, которую нужно защитить.
Работает ли это для бессерверных функций?
Да, но сложнее. Бессерверные функции эфемерны, поэтому ротация сертификатов и аттестация идентичности должны происходить во время вызова. Облачные провайдеры предлагают идентичность рабочей нагрузки для этого (AWS IAM Roles for Service Accounts, GCP Workload Identity). Это похоже на полномочия: функция получает токен, ограниченный тем, что ей разрешено делать, а не универсальные учётные данные.
В чём разница между сервисной учётной записью и полномочием?
Сервисная учётная запись — это идентичность, которая по умолчанию несёт окружающее полномочие. Если Сервис A работает как payments-sa, он обычно может делать всё, что разрешено payments-sa. Полномочие ограничено конкретным действием над конкретным ресурсом. Даже если полномочие украдено, его нельзя применить повторно против другой цели.
Начните с периметра, который у вас уже есть
Ваша VPC никогда не была границей безопасности. Это была сетевая граница, которую мы притворялись границей безопасности. Устранение окружающего полномочия означает обращение с каждым вызовом сервиса так, как будто он пересекает эту границу. Криптографическая идентичность и ограниченные полномочия — это инструменты. Сложная часть — признать, что старая модель была удобством, замаскированным под безопасность.
Выберите одно внутреннее API. Добавьте проверку клиентского сертификата. Посмотрите, что сломается. Чаще всего ломается предположение, о котором вы не знали, что ваш код делает.