Documentation
¶
Overview ¶
Package proxytuple holds the ONE declaration of what a resource-owning module may write into the authorization model through kaname's FGA proxy — RegisterResource / UnregisterResource. (A third RPC, WriteCreatorTuple, shared this rule and was retired with zero callers — #788.)
WHY THIS LIVES IN THE SHARED FOUNDATION AND NOT UNDER THE OWNER'S `internal/`. The rule has two sides that must never disagree: kaname decides whether to ACCEPT a delivered tuple, and five consumers decide what to EMIT. While the rule lived under `services/iam/internal/`, Go's visibility rule forbade a consumer from importing it, so every consumer knew the rule only as prose — six files repeated it in comments, and the single probe that asserted the receiving side's contract carried a HAND-WRITTEN COPY of the set because importing was impossible. A copy that cannot be compiled against the original drifts silently, and the cost of that drift is not a failing test: a relation the owner refuses is refused on EVERY delivery, the queue classifies the refusal as retryable, and the row wedges its partition for the whole retry window (data-integrity.md §«Межсервисное намерение — контракт ПРИНИМАЮЩЕЙ стороны»). Moving the rule here makes the copy unnecessary: the owner imports it into its accept-check, consumers import it into their intent builders, and the tree gate reads the same declaration.
WHAT THE RULE DECIDES — A TRIPLE, NOT A RELATION. Acceptance is a verdict about «subject, relation, object type» together. A check that knows only the relation set is WRONG in the permissive direction as well as the strict one: public read is expressed by the pair `user:* #v_get`, which no hierarchical relation covers and for which no separate «public» flag exists — the tuple IS the visibility. Do not reintroduce a bare relation-membership gate on the write path; ValidateTuple is the whole rule.
Index ¶
- Constants
- Variables
- func ForbiddenObjectTypes() []string
- func Hierarchical(r Relation) bool
- func IsProxyWritable(subject, relation string) bool
- func IsPublicReadGrant(subject, relation string) bool
- func PublicReadObjectTypes() []string
- func PublicReadSubjects() []string
- func ValidateTuple(callerDomain, subject, relation, object string, opts ...Option) error
- type Option
- type Relation
- type TypeOwner
Constants ¶
const ( PublicReadRelation Relation = "v_get" // Тип назван явно: в группе констант вторая строка без типа унаследовала бы // не тип соседа, а нетипизированную константу — и `PublicReadSubject` стал бы // строкой там, где рядом стоит `Relation`. Читается как «оба одного типа», // проверяется компилятором как разные. PublicReadSubject string = "user:*" // PublicReadClassSubject — второе входное слово публикации: класс «аноним» // (corelib#116). Написание совпадает с хранимым субъектом публикации у // принимающей стороны. PublicReadClassSubject string = "system_class:anonymous" )
PublicReadRelation / PublicReadSubject / PublicReadClassSubject — единственная НЕиерархическая пара, которую принимает прокси: «объект модуля читает всякий».
Публичность выражена самим кортежем: служба реестра пишет `<слово> #v_get @registry_repository:<reg>/<repo>`, и этот кортеж И ЕСТЬ видимость, которую разрешает анонимное чтение, — отдельного флага нет. Без пары намерение было бы отправлено и отвергнуто целиком, и публичного репозитория не существовало бы ни при какой конфигурации.
Входных слов ДВА, перечень закрыт (PublicReadSubjects): подстановка `user:*` — переходное написание, класс `system_class:anonymous` — то, чем публикацию хранит и о чём спрашивает принимающая сторона (corelib#116). Оба слова значат одно — «всякий, в том числе без входа», — поэтому принятое одним снимается другим: хранимая форма публикации у принимающей стороны одна.
Сужение — по СУБЪЕКТУ, а не по отношению: ни одно из слов не называет отдельного получателя, поэтому модуль по-прежнему не может выдать чтение конкретному пользователю или сервисному аккаунту — это остаётся за выдачей прав, где грант перечислим, ограничен областью и отзываем. Класс «вошедший» (`system_class:authenticated`) словом публикации не является: «читает всякий вошедший» — не возможность ресурса. Прочие ограничения (домен объекта, запрещённые типы, закрытый список публикуемых типов) действуют без изменений.
Variables ¶
var ErrRefused = errors.New("proxy tuple refused")
ErrRefused is the single verdict this rule produces: the tuple is not one this module may write. It carries NO reason — which clause refused is deliberately not observable to the caller (fail-closed, no oracle) — and it is transport-free, so the rule stays importable by a domain layer that may not depend on gRPC (architecture.md dependency rule). The owner maps it to PermissionDenied at its transport boundary, in ONE place, and a test there locks the code and the text.
Functions ¶
func ForbiddenObjectTypes ¶
func ForbiddenObjectTypes() []string
ForbiddenObjectTypes returns the closed forbidden set, sorted. Derived from the map the write path evaluates rather than written out a second time: a hand-written copy of a set cannot be compiled against the original and drifts silently — which is the whole reason the rule was moved into this package. Exported for the tree gate that requires every entry to name a type the model actually declares.
func Hierarchical ¶
Hierarchical reports whether r is one of the owner-hierarchy relations. It is NOT the accept rule — a tuple also has to satisfy the object-type and domain constraints of ValidateTuple, and a legitimate public-read pair is not hierarchical at all. Exported for the census of the tree gate and for the removal direction (IsProxyWritable), never as a write-path gate on its own.
func IsProxyWritable ¶
IsProxyWritable — could THIS relation have been written by a module through the proxy?
The same closed set ValidateTuple decides «accept this write» with, asked in the other direction: «is this one of ours» when the object is torn down. One predicate for both directions — otherwise removal drifts away from acceptance, and it drifts silently: a set that was accepted but not removed is access left behind.
Relations outside the set are deliberately not claimed here: per-object verbs are derived by the reconciler from grants, and removing those is its work, not this path's. A second place deciding the same question is a race and a divergence, not a safety net.
func IsPublicReadGrant ¶
IsPublicReadGrant — пара есть «объект читает всякий»: `v_get` с одним из входных слов публикации (`user:*` либо `system_class:anonymous`).
От намерения иерархии она отличается родом: состояния ресурса не описывает (ни родительской области, ни меток) — только открывает чтение. Применяющая сторона поэтому обязана считать её ЧИСТЫМ кортежем и не трогать проекцию ресурса: иначе регистрация обнулила бы родительскую область, а снятие удалило бы строку проекции живого ресурса. Экспортирован, чтобы правило и путь применения делили ОДИН предикат — и для обоих слов сразу.
func PublicReadObjectTypes ¶
func PublicReadObjectTypes() []string
PublicReadObjectTypes returns the closed list above, for censuses and gates.
func PublicReadSubjects ¶ added in v1.15.0
func PublicReadSubjects() []string
PublicReadSubjects — закрытый перечень входных слов публикации, копией: правка потребителя правила не меняет.
func ValidateTuple ¶
ValidateTuple constrains the proxy write path to least privilege: a module writes an owner-hierarchy tuple (plus the publication for anonymous reading — see PublicReadRelation) ONLY onto an object of its own domain. callerDomain is the service short name from the verified mTLS SAN (vpc/compute/nlb/registry); empty (domain unknown) disables the domain binding, while the relation set, the subject narrowing of a publication and of a hierarchy link, and the forbidden object types apply always. Any violation → PermissionDenied, fail-closed, without leaking which clause refused.
Types ¶
type Option ¶
type Option func(*policy)
Option — необязательная настройка правила.
Вариативная форма, а не второй аргумент: подпись ValidateTuple читают семь служб и пять наборов проб, и её смена ради необязательной величины сделала бы ломающим изменением то, что ломающим не является.
func WithTypeOwner ¶
WithTypeOwner подаёт правилу словарь владения типом.
Не подан — владение судится ПРИСТАВКОЙ, как и раньше. Сделав словарь обязательным, мы отвергли бы всё у всякого вызывающего, его не завёдшего, — причём отказом ОПАКОВЫМ by design, то есть с худшей возможной диагностикой.
type Relation ¶
type Relation string
Relation is one relation name of the authorization model as it appears on the wire of a proxy write. It is a distinct type rather than a bare string so a consumer's intent builder names the SAME constant the owner's accept-check evaluates; `string(RelationProject)` is a constant expression, so a consumer keeps its own untyped constant without re-typing the value.
const ( RelationProject Relation = "project" RelationParent Relation = "parent" RelationOwner Relation = "owner" )
The owner-hierarchy relations a module may write through the proxy. ONLY ownership / parent links: the resource belongs to a scope (`project`, `parent`) or was created by somebody (`owner`). Privilege relations (`system_admin`/`admin`/`editor`/`viewer`/`v_*`/`fga_writer`/`use`) are absent on purpose — those are authored by the AccessBinding flow, where a grant is enumerable, scoped and revocable, never by a module speaking for itself.
`account` USED TO BE HERE AND IS NOT, and its absence is load-bearing rather than an oversight. No module owns a resource whose containment pointer is an account: iam writes its own account links directly, on its own object types, without the proxy. An accepted relation nobody emits cannot be observed either working or broken — nothing reaches it — while the next reader takes it for a live capability of the product and builds a resource on it. Held by TestEveryAcceptedRelationHasAnEmitter, which reddens on any entry that loses its producer, and by TestProxyRegistrationTriplesAreAcceptedByOwner, which reddens with a coordinate the moment somebody emits a relation that is not here — so putting the tier back when a resource actually needs it is a deliberate edit.
func HierarchicalRelations ¶
func HierarchicalRelations() []Relation
HierarchicalRelations returns the accepted owner-hierarchy relations, sorted the way they are declared. Consumers of the census (gates, reports) get a copy.
type TypeOwner ¶
TypeOwner — ПОРТ: чей это тип объекта. Владельца подаёт ВЫЗЫВАЮЩИЙ.
Порт, а не таблица здесь: полный перечень типов живёт в закрытой таблице iam (`services/iam/internal/authzmap`), за границей видимости этого пакета, и вторая его копия разошлась бы с первой МОЛЧА. Пакет импортируют все семь служб, поэтому «перенести правило туда, где перечень виден» невыразимо; вызывающий у ValidateTuple в прод-коде ровно ОДИН, и он в iam — значит подать словарь может он.
Ответ — МОДУЛЬ КАТАЛОГА, а не короткое имя службы: у балансировщика они различны (`nlb` / `loadbalancer`), и сравнение по имени службы не совпало бы для него НИКОГДА, отняв три живых типа. Наблюдаемо это было бы как «ресурс создан, доступа нет».
ok=false означает «платформа этого типа не знает» — и это НЕ отказ: см. ValidateTuple, ветвь возврата к приставке.