Security и service mesh¶
Эта страница описывает текущую целевую модель безопасности stage-окружения Univex: секреты, service mesh, Tailscale-сегментацию, сетевые политики, hardening контейнеров и обязательные проверки перед релизом.
Базовая модель¶
| Контур | Требование |
|---|---|
| Внешний трафик | Только HTTPS/WSS через ingress с cert-manager TLS |
| Внутренний трафик | Istio sidecar + STRICT mTLS для stage workloads |
| Межсервисный доступ | AuthorizationPolicy allowlist по service account и порту |
| Секреты | Только Kubernetes Secrets / SealedSecrets; plaintext secrets не коммитятся |
| Контейнеры | Non-root UID/GID 65532, allowPrivilegeEscalation: false, RuntimeDefault seccomp, drop all capabilities |
| ServiceAccount token | automountServiceAccountToken: false для приложений, которые не вызывают Kubernetes API |
| Админ-доступ | JWT + Casbin RBAC; для high-risk операций требуется step-up 2FA |
| Клиентская пагинация | offset валидируется, limit нормализуется до максимума 200 |
Istio service mesh¶
Stage namespace stage должен быть включен в mesh:
Целевая политика находится в исходных репозиториях:
univex/stage-deployments/istio-mtls.yamlexchange/deployment/istio-mtls.yaml
Ключевые правила:
- namespace-level
PeerAuthenticationиспользуетSTRICT; - у каждого stage
Serviceесть selector-scopedPeerAuthenticationвSTRICT; - общий
DestinationRuleвключаетISTIO_MUTUALдля*.stage.svc.cluster.local; - чувствительные сервисы закрываются
AuthorizationPolicyallowlist-ами.
Минимальная проверка live-кластера:
Ожидаемо:
PeerAuthenticationесть для namespace default и для workload selector-ов;DestinationRule stage-mesh-mtlsесть;AuthorizationPolicyесть для привилегированных destinations:exchange-admin,exchange-engine,ledger,p2p,id-service,exchange-integration,sumsub-integration,trades-aggregator,redis.
Drift check
Наличие PeerAuthentication STRICT не заменяет AuthorizationPolicy. mTLS подтверждает identity и шифрует трафик, но без destination allowlist любой workload внутри mesh может попытаться обратиться к внутреннему сервису.
Destination allowlist¶
Для privileged services используется модель allow-by-source-principal. Пример для exchange-admin:
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: exchange-admin-trusted-callers
namespace: stage
spec:
selector:
matchLabels:
app: exchange-admin
action: ALLOW
rules:
- from:
- source:
principals:
- cluster.local/ns/stage/sa/exchange-integration
to:
- operation:
ports:
- "1337"
Для сервисов с метриками отдельное правило может разрешать только metrics-port без source-principal, если этот endpoint не содержит секретов и не выполняет state-changing действий.
Публичные web-frontends (univex-frontend, univex-admin-frontend) тоже живут
внутри namespace stage, но не должны быть частью внутреннего trust boundary.
Для них действует отдельная модель:
- входящий HTTP разрешён только от
istio-ingressgateway; automountServiceAccountToken: false, non-root UID/GID, dropped capabilities иseccompProfile: RuntimeDefaultобязательны;- прямые вызовы этих service accounts к workload'ам namespace
stageзапрещены DENY-политикойfrontend-workloads-deny-stage-egress; - backend endpoints должны вызываться браузером через публичные домены
api.univex.nostress.dev,api.exchange.nostress.devиadmapi.univex.nostress.dev, а не через cluster-local services.
Tailscale segmentation¶
Univex stage infrastructure uses dedicated Tailscale tags:
- k8s nodes:
tag:univex-k8s; - DB host:
tag:univex-db; - operator group:
group:univex-admin.
tag:univex-k8s is allowed to talk to other Univex k8s nodes only on k3s,
etcd, kubelet and flannel ports. It can talk to tag:univex-db only on
explicit DB ports: PgBouncer 6100-6110 and direct Temporal PostgreSQL
6120. Runtime Temporal traffic stays inside k8s through
temporal-frontend.temporal.svc.cluster.local:7233. Cross-project Tailscale
grants should not be added to these tags.
The workspace kubeconfig points to https://univex-k8s.tailb72375.ts.net.
Server SSH should use tailscale ssh ubuntu@nsc-kub-1, nsc-kub-2,
nsc-kub-3 and nsc-db-1. Public SSH is temporary during cutover; final
public exposure should be limited to k8s 80/tcp and 443/tcp.
PostgreSQL clients must use TLS over the tailnet. Service databases use
TLS-required PgBouncer on 100.123.97.86:6100-6110. Temporal's dedicated
PostgreSQL uses direct TLS on 100.123.97.86:6120, without PgBouncer.
Kubernetes hardening¶
Каждый stage workload должен задавать pod/container hardening явно:
spec:
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
containers:
- securityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
allowPrivilegeEscalation: false
seccompProfile:
type: RuntimeDefault
capabilities:
drop:
- ALL
Если контейнеру нужно слушать порт ниже 1024, добавляется только NET_BIND_SERVICE; остальные capabilities остаются сброшенными.
Проверка live-кластера:
KUBECONFIG=../config kubectl -n stage get deploy \
-o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.template.spec.automountServiceAccountToken}{"\t"}{range .spec.template.spec.containers[*]}{.name}:{.securityContext.runAsNonRoot}:{.securityContext.allowPrivilegeEscalation}:{.securityContext.seccompProfile.type}:{.securityContext.capabilities.drop}{" "}{end}{"\n"}{end}'
Секреты и конфиги¶
Правила:
- plaintext
Secret, private keys, DB passwords, JWT secrets, admin keys и provider API keys не коммитятся; - секреты хранятся как
SealedSecretmanifests; - startup logging не должен сериализовать полный config со значениями секретов;
- поля config struct с секретами должны иметь
json:"-"или выводиться только через safe summary.
Обновление SealedSecret:
kubectl --kubeconfig ../config -n stage create secret generic nsc-secrets \
--from-literal=KEY_NAME='value' \
--dry-run=client -o yaml \
| kubeseal --kubeconfig ../config \
--controller-name sealed-secrets-controller \
--controller-namespace kube-system \
--format yaml \
--merge-into stage-deployments/sealed-secrets/nsc-secrets.yaml
Admin step-up 2FA¶
Admin Gateway использует JWT + Casbin RBAC как базовый контроль доступа. Для high-risk действий должен быть дополнительный step-up factor:
- hotwallet transfer;
- manual topup / withdraw;
- AML bypass, AML clean/refund, set refund address;
- withdraw limits и withdraw availability;
- fiat settlement actions;
- forced auth reset;
- admin create/delete, role change, session revoke;
- API-key delete;
- risk category changes.
Требование: middleware должен не только проверять TOTP, если 2FA включена, но и отклонять high-risk операцию, если у администратора 2FA не включена.
Pagination guard¶
Клиентские offset и limit валидируются на границе HTTP/gRPC:
offset < 0отклоняется;offset > 100000отклоняется;limit < 0отклоняется;limit > 200нормализуется до200.
Это снижает риск resource exhaustion через большие смещения и выборки. Для новых маршрутов правило простое: не обходить gateway/admin-gateway pagination guard и не добавлять ручные Limit/Offset без такого же clamp/validation.
External HTTP clients¶
Любой внешний HTTP-клиент должен иметь:
- общий request timeout;
- transport timeouts, если клиент long-lived;
io.LimitReaderили эквивалентный response body cap;- отсутствие логирования provider response body с секретами или PII.
Это особенно важно для security-critical путей: SMS/2FA, KYC/KYB, AML и withdrawals.
Release checklist¶
Перед деплоем:
mkdocs build --strictдля документации.govulncheck ./...в Go-репозиториях с актуальным toolchain.- Проверить live
PeerAuthentication,DestinationRule,AuthorizationPolicy. - Проверить pod hardening и
automountServiceAccountToken. - Проверить, что новые high-risk admin routes требуют mandatory 2FA.
- Проверить, что новые публичные list endpoints соблюдают pagination guard.
- Проверить, что новые внешние HTTP clients имеют timeout и response body limit.