observability

package
v1.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 10, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// InternalMTLSEnabled — листенер есть и проверяет клиентские сертификаты.
	InternalMTLSEnabled = "true"
	// InternalMTLSDisabled — листенер есть и работает БЕЗ mTLS.
	InternalMTLSDisabled = "false"
	// InternalMTLSNotApplicable — внутреннего листенера у процесса НЕТ ни одного:
	// защищать нечего, потому что входной поверхности нет. Не путать с Disabled.
	InternalMTLSNotApplicable = "n/a"
)

Величины поля internal_mtls — ТРИ объявленных состояния внутреннего gRPC-листенера, и все три именованы.

ПОЧЕМУ СТРОКА, А НЕ bool (решение задачи #1024). У края внутреннего листенера больше нет вовсе, и «слушателя нет» обязано быть отличимо от «слушатель есть и НЕ защищён»: схлопнуть их значило бы разрешить незащищённый листенер молчанием. Булево поле такого не выражает — состояний у него два, и нулевое НЕМО: процесс, забывший заполнить структуру, печатает ровно то же, что процесс, честно доложивший «выключено».

Указатель (*bool) три состояния выражает, но кладёт послабление в НУЛЕВОЕ значение: забывший заполнить получал бы «слушателя нет», то есть забывчивость расслабляла бы гейт. У строки нулевое значение — пустая — не совпадает ни с одной из трёх объявленных, поэтому гейт посадки судит её ОТКАЗОМ наравне с отсутствием ключа. Забыть здесь можно только в строгую сторону.

Форма выбрана зеркальной уже существующим в этом же самоотчёте DBSSLMode и IdentityProvider: третьего идиома «измерение неприменимо» в одной строке не заводится.

Литералы парсит гейт (deploy/scripts/assert-production-posture.sh) → это ХАРД-КОНТРАКТ, менять только вместе с ним.

View Source
const (
	// OwnRESTFrontTLS — фронт поднят, и его провод под транспортом.
	OwnRESTFrontTLS = "true"
	// OwnRESTFrontPlaintext — фронт поднят и работает ОТКРЫТЫМ ТЕКСТОМ.
	OwnRESTFrontPlaintext = "false"
	// OwnRESTFrontNotRaised — фронта НЕТ: адрес не объявлен профилем развёртывания
	// либо собственной HTTP-поверхности у этого процесса не бывает вовсе.
	// Не путать с Plaintext: защищать нечего, потому что поверхности нет.
	OwnRESTFrontNotRaised = "n/a"
)

Величины полей own_rest_public_tls / own_rest_internal_tls — ТРИ объявленных состояния СОБСТВЕННОГО REST-фронта процесса, и все три именованы.

ЧТО ТАКОЕ «СОБСТВЕННЫЙ REST-ФРОНТ». HTTP-поверхность, которую процесс поднимает НАД СВОИМИ ЖЕ gRPC-слушателями: запрос по HTTP проходит ровно ту цепочку звеньев, что и тот же запрос по gRPC. Край платформы под это определение не подпадает by construction — он проксирует ЧУЖИЕ слушатели, а своего gRPC-API не имеет; его величина — OwnRESTFrontNotRaised.

ПОЧЕМУ СТРОКА, А НЕ bool. Состояний три, а не два: «фронта нет вовсе» обязано быть отличимо от «фронт поднят и работает открытым текстом». Схлопнуть их значило бы разрешить открытый текст молчанием. У булева поля состояний два, и нулевое НЕМО: процесс, забывший заполнить структуру, печатает ровно то же, что процесс, честно доложивший «фронта нет». У строки нулевое значение — пустая — не совпадает ни с одной из трёх объявленных, поэтому гейт посадки судит её ОТКАЗОМ наравне с отсутствием ключа. Забыть здесь можно только в строгую сторону. Форма зеркальна уже существующим в этом же самоотчёте DBSSLMode, InternalMTLS и IdentityProvider: третьего идиома «измерение неприменимо» в одной строке не заводится.

ПОЧЕМУ ОСЬ ПРО ТРАНСПОРТ, А НЕ ПРО «ПОДНЯТ ЛИ». Посадка прочих поверхностей читается в этой строке именно транспортом (public_mtls, internal_mtls), и «поднят ли» из величины восстанавливается: всё, что не n/a, — поднятый фронт. Ось, отвечающая только «поднят ли», не имела бы НЕВЕРНОГО значения, то есть гейту нечего было бы оценивать, и она осталась бы напечатанной, но не судимой.

Литералы парсит гейт (deploy/scripts/assert-production-posture.sh) → это ХАРД-КОНТРАКТ, менять только вместе с ним.

View Source
const BootPostureMsg = "boot security posture"

BootPostureMsg — сообщение единственной boot-строки, в которой процесс САМ отчитывается о posture, с которой он реально стартовал.

Зачем это существует: production-posture гейт раньше читал ХРАНИМЫЙ конфиг (values/ConfigMap) и потому рапортовал успех, пока сервис фактически работал в dev-режиме с незашифрованным DB-соединением. Гейт обязан утверждать на НАБЛЮДАЕМОМ факте — на самоотчёте живого процесса, а не на намерении.

Сообщение и имена полей парсятся гейтом (jq) → это ХАРД-КОНТРАКТ: переименование ключа или msg молча ослепляет гейт. Менять только вместе с гейтом.

View Source
const BuildStampUnstamped = "unstamped"

BuildStampUnstamped — то, что метка витрины говорит, когда сборка величину НЕ ПРОСТАВИЛА.

Пустая метка читается как «версии нет», правдоподобное `dev` — как имя ветки; оба неотличимы от «величину не измеряли». Отдельное слово делает это состояние НАБЛЮДАЕМЫМ: по нему пишется тревога, и оно не притворяется ответом.

Слово совпадает с тем, которым отвечает служба доступа (`services/iam/internal/observability/metrics`.BuildInfoUnstamped): дежурный сверяет витрины разных процессов не пересчитывая, а два написания одного состояния дали бы две тревоги об одном предмете. Совпадение держится пробой `TestBuildStampWordMatchesTheAccessServiceWord` — общего объявления у двух модулей быть не может, они разные модули (`polyrepo.md` §Build-граф).

View Source
const DBSSLModeNotApplicable = "n/a"

DBSSLModeNotApplicable — значение поля db_sslmode для сервиса БЕЗ базы (api-gateway). Литерал, а не пустая строка: гейт отличает «БД нет» от «поле не заполнено».

View Source
const IdentityProviderNotApplicable = "n/a"

IdentityProviderNotApplicable — значение поля identity_provider для сервиса, который личность человека не проверяет вовсе (vpc, compute, storage, nlb, geo, registry: они принимают уже проверенного вызывающего).

Литерал, а не пустая строка, по той же причине, что и у базы: «измерения нет» обязано быть отличимо от «поле не заполнено». Гейт посадки, увидевший пустую строку, не смог бы сказать, работает ли перед ним сервис без этого измерения или сервис, забывший его заполнить.

Variables

This section is empty.

Functions

func InternalMTLSFrom

func InternalMTLSFrom(mtlsEnabled bool) string

InternalMTLSFrom — величина измерения для процесса, у которого внутренний листенер ЕСТЬ: остаётся сказать, проверяет ли он клиентские сертификаты.

Функция, а не тернарник на месте: вызывающих семь, и семь раз написанное «если включено — одна строка, иначе другая» есть семь мест об одном предикате. Разойдутся они молча — обе ветки вернут непустую строку, и гейт увидит законное значение, сказанное о неверном состоянии.

Случая «листенера нет» здесь НЕТ намеренно: он не выводится из ручки, потому что ручки у такого процесса не существует вовсе. Он называется константой InternalMTLSNotApplicable прямо в композиционном корне — там, где видно, что поверхность не поднимается.

func LogBootPosture

func LogBootPosture(logger *slog.Logger, p BootPosture)

LogBootPosture пишет BootPosture единственной структурированной строкой. Единственное место, где живут msg и имена полей контракта — поэтому восемь сервисов не могут разъехаться по форме.

func NewSlogger

func NewSlogger(w io.Writer) *slog.Logger

NewSlogger создает структурированный JSON-логгер, пишущий в w. Минимальный уровень — Info. Back-compat обертка над NewSloggerLevel для вызывающих, которым не нужен настраиваемый уровень.

func NewSloggerLevel

func NewSloggerLevel(w io.Writer, level slog.Leveler) *slog.Logger

NewSloggerLevel создает структурированный JSON-логгер, пишущий в w, с заданным минимальным уровнем. level — slog.Leveler (slog.Level или динамический *slog.LevelVar). Композиционный корень парсит оператор-сконфигурированный уровень и передает его сюда.

func NormalizeBuildStamp

func NormalizeBuildStamp(version, commit string) (string, string)

NormalizeBuildStamp приводит штамп сборки к тому, что витрина вправе утверждать.

Зачем это отдельная функция, а не строка в каждом адаптере

Ряд `*_build_info` держат ПЯТЬ процессов платформы, и предикат «величина не проставлена» у всех один. Выписанный пятью копиями, он разъехался бы молча — как разъехались бы любые пять копий одного предиката, — и разъехался бы именно там, где расхождение не видно: на витрине, которую читают в три часа ночи.

Что считается НЕ-величиной, и почему трёх форм мало не бывает

  • пустая строка — аргумент сборки не передан вовсе;
  • `dev`/`unknown` — умолчания ОБЪЯВЛЕНИЯ в композиционном корне: их видит всякая сборка без `-ldflags` (`go run`, `go test`, сборка руками);
  • пробельная строка — аргумент передан пустым через оболочку.

Все три означают ОДНО, и потому обязаны читаться одинаково: у одного состояния не бывает трёх написаний, иначе тревога по нему тоже пишется трижды и две из трёх однажды забудут.

Чего она НЕ делает

Не судит, ПРАВДУ ли говорит проставленная величина: `-X` с литералом доедет до двоичного файла и покажет не про это дерево. Что значение взято из аргумента, которым клеймится образ, держат гейты сборки — `internal/repohygiene` `TestBuildStampReachesEveryBinaryThatDeclaresIt` и его собрат у службы доступа.

func OwnRESTFrontFrom

func OwnRESTFrontFrom(front OwnRESTFront) string

OwnRESTFrontFrom — величина оси, ВЫВЕДЕННАЯ из того же объявления поверхности, по которому она поднимается.

Почему выводится, а не вписывается: самоотчёт о посадке и доклад поверхности при подъёме — два утверждения об одном предмете. Выведенные из ОДНОГО объявления, разойтись они не могут by construction; вписанные порознь — разойдутся молча, и верным окажется одно.

Случая «фронта нет вовсе» здесь НЕТ намеренно, ровно как у InternalMTLSFrom: он не выводится из объявления, потому что объявления у такого процесса не существует. Он называется константой OwnRESTFrontNotRaised прямо в композиционном корне — там, где видно, что поверхность не поднимается.

Types

type BootPosture

type BootPosture struct {
	// Service — каноническое короткое имя сервиса: vpc | compute | nlb | iam |
	// geo | registry | storage | api-gateway.
	Service string
	// AuthMode — режим, который процесс реально принял (production |
	// production-strict | dev).
	AuthMode string
	// DBSSLMode — sslmode, который реально уходит в DSN/пул (у сервиса без БД —
	// DBSSLModeNotApplicable).
	DBSSLMode string
	// PublicMTLS — фактическое состояние mTLS на ПУБЛИЧНОМ gRPC-листенере (:9090).
	PublicMTLS bool
	// InternalMTLS — состояние cluster-internal листенера (:9091): одна из трёх
	// объявленных величин InternalMTLS* выше. Строка, а не bool, потому что
	// состояний три, а не два — разбор при константах.
	//
	// Заполняется тем, что процесс РЕАЛЬНО поднял, а не тем, что объявлено в
	// настройках: у процесса без внутреннего листенера это
	// InternalMTLSNotApplicable, и ручки, которую можно было бы прочитать, у
	// него нет вовсе.
	InternalMTLS string
	// AuthZCheck — проведён ли per-RPC authz-Check (непустой адрес / не-nil клиент).
	AuthZCheck bool
	// TrustedForwarders — сужен ли круг отправителей, которым процесс разрешает
	// ПЕРЕДАВАТЬ личность конечного пользователя (x-kacho-principal-*), то есть
	// непуст ли список, реально уехавший в grpcsrv.WithTrustedForwarders.
	//
	// Почему это отдельное измерение, а не следствие mTLS: contract corelib
	// (principalIsTrusted) сужает круг ТОЛЬКО на непустом списке — на пустом любой
	// пир, прошедший проверку сертификата, может представиться кем угодно, и решение
	// о правах будет принято от имени названного им пользователя. Значит «mTLS есть»
	// НЕ влечёт «личность нельзя подменить»: false здесь при public_mtls=true — это
	// осмысленное, а не противоречивое сочетание.
	//
	// Заполняется тем же значением, что уходит в проводку (не сырым полем конфига),
	// иначе отчёт снова описывал бы намерение вместо исхода.
	//
	// Как читать false (важно для гейта): поле отвечает на УЗКИЙ вопрос — «непуст ли
	// список WithTrustedForwarders», а не «безопасен ли сервис» и не «пинит ли сервис
	// хоть какие-нибудь личности сертификатов». false означает «ЭТОТ круг не сужен» и
	// покрывает ДВА разных случая:
	//   (а) сервис принимает переданную личность и никого не пинит — это дефект;
	//   (б) сервис переданную личность не принимает вовсе — сужать нечего. Так у
	//       api-gateway: он личность НЕ форвардит, а чеканит из проверенного токена и
	//       вырезает весь клиентский x-kacho-*. При этом свой SPIFFE-список у него
	//       ЕСТЬ (кого пускать на внутренний листенер) — просто это другое измерение,
	//       и данное поле про него ничего не говорит.
	// Гейт, который валит сборку по этому полю, обязан различать эти два случая —
	// иначе он покрасит сервис, у которого измерения нет. Список участвующих сервисов
	// ведётся в deploy/scripts/assert-production-posture.sh.
	TrustedForwarders bool
	// IdentityProvider — ПОСАДКА ЛИЧНОСТИ, которую процесс реально принял:
	// проверяет ли человека внешний поставщик удостоверений или наша
	// собственная чеканка (задача #1125).
	//
	// Зачем это в самоотчёте, а не в карте настроек: посадка разводит
	// ТРЕБОВАНИЯ СТАРТА, поэтому «какую посадку выбрал стенд» — вопрос, на
	// который обязан отвечать ЖИВОЙ процесс. Карта настроек отвечает на него
	// намерением: ручки приезжают через envFrom и читаются один раз при старте,
	// так что правка карты меняет её, а не процесс.
	//
	// Заполняется значением, ПРИНЯТЫМ проверкой настройки, а не сырым полем:
	// незаданное и негодное значения до этой строки не доживают — процесс на
	// них не стартует.
	//
	// У сервиса, который личность человека не проверяет,
	// IdentityProviderNotApplicable.
	IdentityProvider string
	// OwnRESTPublicTLS — состояние СОБСТВЕННОГО ПУБЛИЧНОГО REST-фронта: одна из
	// трёх объявленных величин OwnRESTFront* выше.
	//
	// Заполняется тем, что процесс РЕАЛЬНО объявил поверхностью, а не сырой
	// ручкой: у процесса без собственного фронта это OwnRESTFrontNotRaised, и
	// ручки транспорта, которую можно было бы прочитать, у него нет вовсе.
	OwnRESTPublicTLS string
	// OwnRESTInternalTLS — то же для СОБСТВЕННОГО ВНУТРЕННЕГО REST-фронта.
	//
	// Ось отдельная, а не общая с публичной: фронты поднимаются разными ручками,
	// досягаемы из разных мест и несут разный материал, поэтому одно значение на
	// двоих скрывало бы ровно тот случай, ради которого ось заведена, — один
	// фронт под транспортом, другой открытым текстом.
	OwnRESTInternalTLS string
}

BootPosture — самоотчёт процесса о принятой security-posture.

ВСЕ поля обязаны отражать то, что процесс РЕАЛЬНО собирается использовать (провалидированный config-struct / уже собранная проводка), а не сырое env и не константу. Заполняется в composition root каждого сервиса ПОСЛЕ secure-by-default boot-guard'ов и ДО старта листенеров.

type OwnRESTFront

type OwnRESTFront interface {
	// Enabled — поднимается ли поверхность (адрес объявлен значением).
	Enabled() bool
	// UnderTLS — идёт ли её провод под транспортом.
	UnderTLS() bool
}

OwnRESTFront — ровно то, что самоотчёту нужно от ОБЪЯВЛЕНИЯ поверхности: поднимается ли она и под транспортом ли её провод.

Узкий интерфейс здесь не украшение. Он (а) оставляет пакет самоотчёта листом графа импортов — иначе он потянул бы за собой весь пакет объявления поверхностей; (б) делает непредставимой перестановку доводов: два bool одного типа менялись бы местами молча, и в счастливом случае оба истинны, поэтому перестановка не показала бы себя ничем.

type ShutdownFn

type ShutdownFn func(context.Context) error

ShutdownFn — функция завершения работы провайдера телеметрии.

func InitOtel

func InitOtel(ctx context.Context, serviceName string) (ShutdownFn, error)

InitOtel инициализирует экспорт телеметрии по endpoint'у из KACHO_OTEL_EXPORTER_OTLP_ENDPOINT и возвращает ShutdownFn для graceful-flush.

Если endpoint не задан — телеметрия отключена, возвращается no-op. Если endpoint задан, но OTLP-exporter в этой сборке не подключен, функция НЕ делает вид, что телеметрия работает: пишет явный WARN (чтобы оператор не считал, что трейсы уходят) и возвращает no-op shutdown. Это честный контракт вместо «тихого» no-op, который ранее молча терял телеметрию при настроенном endpoint'е.

Directories

Path Synopsis
Package health — ЕДИНСТВЕННЫЙ в дереве носитель разведённых живости и готовности.
Package health — ЕДИНСТВЕННЫЙ в дереве носитель разведённых живости и готовности.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL