diff --git a/AGENTS.md b/AGENTS.md index a4a44b8..15f14dd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -44,8 +44,16 @@ Fleet standard — [handbook#68](https://github.com/obin-ai/handbook/issues/68). - **Never hardcode what the system resolves** — injected environment, derived ports, service addresses, credentials copied out of another component. This SDK is on the resolving side of that line: a facade takes a gateway and reads - `BaseURL()` off it rather than holding a URL, and `moduleauthority` takes - both seams from the consumer rather than defaulting either. Typing one of those in encodes something true + `BaseURL()` off it rather than holding a URL. `moduleauthority` takes its + gateway and authority seams from the consumer and defaults neither address: + a zero `Authority` builds a mint-only client that fails with + `ErrNoAuthority`, never one that dials an address nobody configured. The one + value it does resolve is a credential source the consumer opts into, the + projected token's path: `WorkloadTokenFile()` reads it from the variable a + native run injects (`CODEFLY__WORKLOAD_TOKEN_FILE`) and otherwise falls back + to the mount a cell's render declares, and a consumer whose path comes from + elsewhere passes `ProjectedTokenFile(path)`, which has no default. Typing one + of those in encodes something true on one machine for ten minutes, and it fails quietly — a runtime missing a credential can skip registration *silently*, so the service boots, serves, and is simply absent. diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fff76a..0c0e96c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,11 @@ the mount minted nothing there. The variable and the path are exported as `WorkloadTokenFileVariable` and `PodWorkloadTokenFile`; codefly-dev/solution-runtime-go reads the same variable. It resolves the - path only: the token is still read before every mint. + path only: the token is still read before every mint. Its scope is the + services a native run names the variable for — `module-identity` without + `workload-identity`; a `workload-identity` service receives its path as + `TOKEN_FILE` in its `workload-identity` configuration and passes + `ProjectedTokenFile(path)` itself. ## v0.1.2 — core v0.19.1; Work Contexts are core's, carried opaque diff --git a/README.md b/README.md index 9d89ffc..7bc89e2 100644 --- a/README.md +++ b/README.md @@ -292,13 +292,18 @@ The SDK surfaces are: `AllowInsecureHTTP` asserts the hop is mesh-protected. SaaS owns the installed binding's audience, scopes, lifetime, verification, and audit record. - `WorkloadTokenFile()` is where Codefly projected the token for the process - it runs: the file `CODEFLY__WORKLOAD_TOKEN_FILE` names when that is set — - a native `codefly run` (CLI v0.1.187 and later) stands in for the cell and - projects each module's token to a file of its own, since a process cannot - read the pod mount — and the pod mount, `/var/run/secrets/codefly/token`, - otherwise. A consumer with another projection passes - `ProjectedTokenFile(path)` instead, which has no default. The host requires audience + `WorkloadTokenFile()` is where Codefly projected the token for a + module-identity service: the file `CODEFLY__WORKLOAD_TOKEN_FILE` names when + that is set, and the pod mount, `/var/run/secrets/codefly/token`, otherwise. + A native `codefly run` (CLI v0.1.187 and later) stands in for the cell and + projects each such service's token to a file of its own, since a process + cannot read the pod mount, but it sets that variable only for a service that + declares `module-identity` and does **not** declare `workload-identity`. A + `workload-identity` service gets its token's path as `TOKEN_FILE` in its + `workload-identity` configuration instead, so for it the helper falls back + to the pod path, which does not exist under a native run; it passes + `ProjectedTokenFile(path)` with that configured path. A consumer with any + other projection does the same; `ProjectedTokenFile` has no default. The host requires audience `accounts` and a token lifetime of at most 600 seconds, and verifies the pod, workload and approved build. The SDK rereads the source for every actual mint (including a module Work Context refresh), trims whitespace, and rejects an diff --git a/moduleauthority/broker_transport_errors_test.go b/moduleauthority/broker_transport_errors_test.go index 68b3436..0923b4b 100644 --- a/moduleauthority/broker_transport_errors_test.go +++ b/moduleauthority/broker_transport_errors_test.go @@ -173,7 +173,7 @@ func brokerParserTransport(t *testing.T, wire string) *http.Transport { done := make(chan struct{}) go func() { defer close(done) - defer server.Close() + defer func() { _ = server.Close() }() req, err := http.ReadRequest(bufio.NewReader(server)) if err != nil { return diff --git a/moduleauthority/client.go b/moduleauthority/client.go index e9042d1..8c1aa92 100644 --- a/moduleauthority/client.go +++ b/moduleauthority/client.go @@ -164,8 +164,9 @@ type ProjectedTokenSource interface { // one exists because moduleauthority does not import sdk-go/workcontext: it // carries Work Contexts as opaque strings and reads them only through core. // -// A module running under Codefly passes WorkloadTokenFile(), which names the -// path Codefly projected the token to wherever it runs. +// A module-identity service running under Codefly passes WorkloadTokenFile(), +// which names the path Codefly projected its token to in a cell's pod and in +// a native run; see WorkloadTokenFile for the one case it does not cover. type ProjectedTokenFile string // WorkloadTokenFileVariable is the environment variable that names the @@ -180,10 +181,20 @@ const WorkloadTokenFileVariable = "CODEFLY__WORKLOAD_TOKEN_FILE" // projected ServiceAccount token at, read-only, for audience `accounts`. const PodWorkloadTokenFile = "/var/run/secrets/codefly/token" -// WorkloadTokenFile is where this module's projected token is, wherever -// Codefly runs it: the file WorkloadTokenFileVariable names when the variable -// is set to a non-blank value, and PodWorkloadTokenFile otherwise. A process -// under a native run is told; a pod is not, and reads its mount. +// WorkloadTokenFile is where a module-identity service's projected token is: +// the file WorkloadTokenFileVariable names when the variable is set to a +// non-blank value, and PodWorkloadTokenFile otherwise. A process under a native +// run is told; a pod is not, and reads its mount. +// +// Its scope is exactly the services a native run names the variable for: a +// service that declares module-identity and does not declare +// workload-identity (codefly-dev/cli cmd/run/native_cluster.go). A service +// that declares workload-identity is not given the variable; a native run +// delivers its token's path as TOKEN_FILE in its workload-identity +// configuration instead, so for it this helper falls back to +// PodWorkloadTokenFile, a path that does not exist on the machine running it. +// Such a service passes ProjectedTokenFile with the path that configuration +// names. // // It resolves the path, never the token: the file is still read before every // mint, because the projection rotates under a running process.