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 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:*" )
PublicReadRelation / PublicReadSubject — the only NON-hierarchical pair the proxy accepts: «anybody reads this module's resource».
Publicness is expressed by the tuple itself: kacho-registry writes `user:* #v_get @registry_repository:<reg>/<repo>` and that tuple IS the visibility an anonymous pull resolves — there is no separate flag. Without the pair the intent would be emitted and refused whole, and a public repository would not exist under any configuration.
The narrowing is by SUBJECT, not by relation: the wildcard `user:*` names NO individual recipient, so a module still cannot hand read access to a particular user or service account — that stays with AccessBinding, where it is enumerable, scoped and revocable. Every other constraint (object domain, forbidden types) applies unchanged.
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 reports whether the pair is «anybody reads this resource» (`user:* #v_get`).
It differs from a hierarchical intent in kind: it does not describe the state of the resource (no parent scope, no labels) — it only opens reading. The applying side must therefore treat it as a PURE tuple and leave the resource projection alone: a register would otherwise blank the parent scope and an unregister would delete the projection row of a live resource. Exported so the policy and the apply path share ONE predicate.
func PublicReadObjectTypes ¶
func PublicReadObjectTypes() []string
PublicReadObjectTypes returns the closed list above, for censuses and gates.
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 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, ветвь возврата к приставке.